EF6使用命令树拦截器禁用查询计划缓存

Ale*_*lek 20 c# caching entity-framework

我正在使用IDbCommandTreeInterceptor软删除功能.在标准TreeCreated方法内部,我检查给定的查询命令是否包含具有软删除属性的模型.如果他们这样做并且用户也要求获取软删除对象 - 我用querySoftDeleted= 调用我的软删除访问者true.这将使我的查询返回的所有对象,那些true和那些与false价值观IsDeleted财产.

public class SoftDeleteInterceptor : IDbCommandTreeInterceptor {
    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) {
        ...            

        bool shouldFetchSoftDeleted = context != null && context.ShouldFetchSoftDeleted;

        this.visitor = new SoftDeleteQueryVisitor(ignoredTypes, shouldFetchSoftDeleted);

        var newQuery = queryCommand.Query.Accept(this.visitor);

        ...
    }
}


public class SoftDeleteQueryVisitor {

    ...

    public override DbExpression Visit(DbScanExpression expression)
    {
        // Skip filter if all soft deleted items should be fetched
        if (this.shouldFetchSoftDeleted)
            return base.Visit(expression);

        ...
        // TODO Apply `IsDeleted` filter.
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试检索所有对象(也是软删除)然后使用相同的查询后面的对象时,问题就出现了.像这样的东西:

context.ShouldFetchSoftDeleted = true;
var retrievedObj= context.Objects.Find(obj.Id);
Run Code Online (Sandbox Code Playgroud)

然后在新的上下文实例中(不在相同的上下文中)

var retrievedObj= context.Objects.Find(obj.Id);
Run Code Online (Sandbox Code Playgroud)

第二次,ShouldFetchSoftDeleted设置为false,一切都很好,但EF决定此查询与之前的查询相同,并从缓存中检索它.检索到的查询不包含过滤器,因此返回所有对象(软删除而不是).处理上下文时不会清除缓存.

现在的问题是,是否有一种方法,理想情况下,标记构造,DbCommand以便它不会被缓存.可以这样做吗?或者有没有办法强制查询重新编译?

有一些方法可以避免缓存,但我宁愿不必更改应用程序中的每个查询只是为了解决这个问题.

有关查询计划缓存的更多信息,请访问此处.

编辑1

我正在为每个请求使用新的上下文 - 对象缓存不应该是问题.

编辑2

这是数据库日志.第一次调用是软删除,第二次是w/o....部件是相同的,所以我将它们从日志中排除.您可以看到两个请求都是相同的.第一个调用CreateTree和结果树被缓存,以便在执行时从缓存中检索树,并且在应该的时候不会重新应用我的软删除标志.

Opened connection at 16.5.2015. 2:34:25 +02:00

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[IsDeleted] AS [IsDeleted], 
    ...
    FROM [dbo].[Items] AS [Extent1]
    WHERE [Extent1].[Id] = @p__linq__0


-- p__linq__0: '1' (Type = Int64, IsNullable = false)

-- Executing at 16.5.2015. 2:34:25 +02:00

-- Completed in 22 ms with result: SqlDataReader



Closed connection at 16.5.2015. 2:34:25 +02:00

The thread 0x1008 has exited with code 259 (0x103).
The thread 0x1204 has exited with code 259 (0x103).
The thread 0xf94 has exited with code 259 (0x103).
Opened connection at 16.5.2015. 2:34:32 +02:00

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[IsDeleted] AS [IsDeleted], 
    ...
    FROM [dbo].[Items] AS [Extent1]
    WHERE [Extent1].[Id] = @p__linq__0


-- p__linq__0: '1' (Type = Int64, IsNullable = false)

-- Executing at 16.5.2015. 2:34:32 +02:00

-- Completed in 16 ms with result: SqlDataReader



Closed connection at 16.5.2015. 2:34:32 +02:00

'vstest.executionengine.x86.exe' (CLR v4.0.30319: UnitTestAdapter: Running test): Loaded 'C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.DebuggerVisualizers\12.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.DebuggerVisualizers.dll'. Cannot find or open the PDB file.
Run Code Online (Sandbox Code Playgroud)

正如我已经说过的,我在自己的上下文中执行了每个请求,如下所示:

        using (var context = new MockContext())
        {
            // Test overrided behaviour 
            // This should return just deleted entity
            // Enable soft-delete retrieval
            context.ShouldFetchSoftDeleted = true;

            // Request 1 goes here
            // context.Items.Where(...).ToList()
        }

        using (var context = new MockContext())
        {
            // Request 2 goes here
            // context.Items.Where(...).ToList()
        }
Run Code Online (Sandbox Code Playgroud)

Dav*_*ich 8

区分查询计划缓存结果缓存很重要:

实体框架中的缓存

查询计划缓存

第一次执行查询时,它会通过内部计划编译器将概念查询转换为存储命令(例如,针对SQL Server运行时执行的T-SQL).如果启用了查询计划缓存,则下次执行查询时,将直接从查询计划缓存中检索存储命令以执行,从而绕过计划编译器.

查询计划缓存在同一AppDomain中的ObjectContext实例之间共享.您不需要保留ObjectContext实例以从查询计划缓存中受益.

查询缓存是优化的SQL指令计划.这些计划有助于使EF查询比"冷"查询更快.这些计划超出了特定的范围.

对象缓存:

默认情况下,在查询结果中返回实体时,就在EF实现它之前,ObjectContext将检查具有相同键的实体是否已加载到其ObjectStateManager中.如果具有相同键的实体已存在,则EF将其包含在查询结果中.尽管EF仍将针对数据库发出查询,但此行为可以绕过实现多次实体化的大部分成本.

换句话说,对象缓存是结果缓存的一种软形式.除非您明确包含,否则实体框架不提供其他类型的二级缓存.实体框架和Azure中的二级缓存

AsNoTracking

返回一个新查询,其中返回的实体不会缓存在DbContext或ObjectContext中

Context.Set<Objects>().AsNoTracking();
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用MergeOption NoTracking Option 禁用实体的对象缓存:

不会修改缓存.

context.Objects.MergeOption = MergeOption.NoTracking; 
var retrievedObj= context.Objects.Find(obj.Id);
Run Code Online (Sandbox Code Playgroud)

AppendOnly期权相对

只会附加新的(顶级唯一的)行.这是默认行为.

这是您一直在努力的默认行为


Fab*_*Luz 2

您确定您的问题出现在所有查询中吗?在您的示例中,您使用了 Find(),如果您使用 ToList() 会怎样?问题不会发生,对吧?

出于测试目的,尝试使用Where方法而不是Find(),我相信你不会有问题......

如果上述理论成立,则将 Find() 替换为某种存储库类中的Where。然后您不需要更改代码中的任何其他内容。

例如,在您的存储库类中:

public YourClass Find(id)
{
    //do not use Find here 
    return context.FirstOrDefault(i => i.Id == id); //or Where(i => i.Id == id).FirstOrDefault();
}
Run Code Online (Sandbox Code Playgroud)

在您的业务逻辑中:

var user = repository.Find(id);
Run Code Online (Sandbox Code Playgroud)

Find() 方法文档https://msdn.microsoft.com/en-us/library/system.data.entity.dbset.find%28v=vs.113%29.aspx说:

“...如果上下文中存在具有给定主键值的实体,则立即返回该实体,而不向商店发出请求...”

所以,我认为问题出在 Find() 上。使用存储库模式,用“哪里”替换“查找”,是我现在能想象到的最简单的解决方法。或者,您可以检查软删除是否已激活,而不是替换,然后选择您喜欢的方法。你对此有何看法?

更困难的方法是创建一个继承自 DbSet 的类并重写 Find(),这会太复杂。

编辑

为了帮助我们了解发生了什么,创建一个控制台应用程序并记录数据库操作,如下所示:

using (var context = new BlogContext()) 
{ 
    context.Database.Log = Console.Write; 

    // Your code here... 
    // Call your query twice, with and without softdelete
}
Run Code Online (Sandbox Code Playgroud)

把日志贴出来,我们就可以确定是sql错误还是数据被缓存了。

编辑2

好吧...不要将拦截器添加到配置类的构造函数中,而是将其添加到上下文的构造函数中,如下所示:

//the dbcontext class      

 private IDbCommandTreeInterceptor softDeleteInterceptor;
 public DataContext()
       : base("YourConnection")
 {
    //add the interceptor 
    softDeleteInterceptor = new SoftDeleteInterceptor()           
      DbInterception.Add(softDeleteInterceptor);
 }
Run Code Online (Sandbox Code Playgroud)

然后,在上下文类中创建一个删除拦截器的方法,如下所示:

public void DisableSoftDelete() 
{
     DbInterception.Remove(softDeleteInterceptor);
}
Run Code Online (Sandbox Code Playgroud)

当你想禁用软删除时调用上面的方法,context.DisableSoftDelete();