使Entity Framework缓存失效/禁用

28

我看到有很多关于EF缓存的问题,但是对于我的问题还没有找到解决方案。

直接问题是

如何完全禁用Entity Framework 6缓存?或者,我能否以编程方式告诉EF忘记缓存,因为数据发生了某些变化?

背景

首先,我继承了一个应用程序,它由EF(使用模型优先定义实体)和纯旧SQL(用于操作数据)的奇怪混合构成。我的做法是重构应用程序,以便:

  • 使简单查询(例如GetAll())使用EF6 LINQ
  • 将复杂的数据操作留在SQL中,需要时使用DbContext.Database.Connection
  • 添加Spring.Web支持以启用DI和事务(尚未完成)

当前,我已经重新组织了代码,使应用程序的主要功能(在大型数据集上运行复杂的SQL查询)与之前相同,但查找域实体的操作使用尽可能多的Entity Framework进行智能化处理。

尽可能多地使用...

我继承的页面之一是一个多选框页面,我将向您展示,以便更好地理解。我不会讨论先前实现者的选择,因为修复当前问题并稍后重构代码比阻止破损功能的开发更便宜。

页面如下所示

enter image description here

基本上,Controller方法如下:

    [HttpPost]
    public ActionResult Index(string[] codice, string[] flagpf, string[] flagpg, string[] flagammbce, string[] flagammdiv, string[] flagammest,
        string[] flagintab, string[] flagfinanz, string[] flagita, string[] flagest, string pNew){
            Sottogruppo2015Manager.ActivateFlagFor("pf", flagpf);
            Sottogruppo2015Manager.ActivateFlagFor("pg", flagpg);
            Sottogruppo2015Manager.ActivateFlagFor("ammbce", flagammbce);
            Sottogruppo2015Manager.ActivateFlagFor("ammdiv", flagammdiv);
            Sottogruppo2015Manager.ActivateFlagFor("ammest", flagammest);
            Sottogruppo2015Manager.ActivateFlagFor("intab", flagintab);
            Sottogruppo2015Manager.ActivateFlagFor("finanz", flagfinanz);
            Sottogruppo2015Manager.ActivateFlagFor("ita", flagita);
            Sottogruppo2015Manager.ActivateFlagFor("est", flagest);

            return RedirectToAction("Index", new { pNew });
 }

每个string[]参数都是表格中的一列。 ActivateFlagFor方法按顺序运行两个查询。

UPDATE table SET --param1-- = 0;
UPDATE table SET --param1-- = 1 where id in (--param2--)

当缓存生效时

以下是行为:

  • 我首先发出一个LINQ选择来加载页面:检查匹配列中的一和零
  • 我更改一个或多个检查并提交
  • 控制器发出查询以更新DB中的检查
  • 重定向(!表示新请求!)重新加载页面之前,我会检查DB,更改已应用
  • 页面重新加载,发出与上述相同的LINQ选择:旧检查显示

我确定这是一个缓存问题,因为重新加载应用程序可以解决问题。 由于应用程序的主要功能完全基于SQL,因此对查找表进行的更改会反映在主要操作中,这是正确的行为。

我知道EF缓存是性能的一个很好的特性,但在我的情况下,我只是不想要它,至少在我将整个应用程序迁移到LINQ DML之前(可能不可能)。

如何使用DbContext

当然,你们中的一些人可能会问“你如何使用你的DbContext?”“你是否正确地处理了它?”。

  • 我还没有在我的Manager类中集成Spring事务
  • 每个在数据库上执行操作的对象都是扩展BaseManagerI<Entity>Manager
  • DbContext是一个请求范围的Spring对象。我已经询问了如何处理请求范围的对象,但目前我实现了一个解决方法,虽然不好,但可以在请求结束时正确地处理DbContext。

示例代码

public class ExampleManagerImpl : BaseManager, IExampleManager
{
    public void ActivateFlagFor(string aFlag, string[] aList)
    {
        string sql = "UPDATE table SET flag" + aFlag + " = 0";
        RunStatementV1(sql);

        if (aList != null && aList.Any())
        {
            sql = "UPDATE table SET flag" + aFlag + " = 1 WHERE id in (" + aList.ToCsvApex() + ")";
            RunStatementV1(sql);
        }
    }

    public IList<Models.Example> GetAll()
    {
        return DataContext.example.ToList(); //I don't dispose of the DataContext willingly
    }
}

并且

public abstract class BaseManager {

    public DbContext DataContext { get; set; } //Autowired

    protected void RunStatementV1(string aSqlStatement)
    {
        IDbConnection connection = DataContext.Database.Connection;
        if (connection.State == ConnectionState.Closed || connection.State == ConnectionState.Broken) connection.Open(); //Needed because sometimes I found the connection closed, even if I don't dispose of it
        using (IDbCommand command = connection.CreateCommand())
        {
            command.CommandText = aSqlStatement;
            command.ExecuteNonQuery();
        }

    }
}

我尝试了什么

2个回答

57
如果您想完全忽略EF6的缓存以进行数据检索,则在查询末尾添加AsNoTracking()(在调用ToList()或执行查询之前)。

MSDN关于AsNoTracking()的说明

请注意,这样做既不会检查现有数据的缓存,也不会将数据库调用的结果添加到缓存中。此外,Entity Framework不会自动检测从数据库检索的实体的更改。如果您确实想要更改任何实体并将其保存回数据库,则需要在调用SaveChanges()之前附加更改的实体。
您当前的方法是:
public IList<Models.Example> GetAll()
{
    return DataContext.example.ToList();
}

它将会变成:

public IList<Models.Example> GetAll()
{
    return DataContext.example.AsNoTracking().ToList();
}

如果您对处理EF缓存的其他选项感兴趣,我已经写了一篇关于EF6 Cache Busting的博客文章


AsNoTracking并不总是好的选择,我曾经因为使用它而遭遇了长期的尝试和搜索,因为例如记录无法被删除。 - Hassan Faghihi
让我添加任何类型的附加都没有起作用...你在这里提到了什么 http://codethug.com/2016/02/19/Entity-Framework-Cache-Busting/ - Hassan Faghihi
1
@deadManN 如果您开一个新的问题并在此处链接它,我很乐意帮助您解决问题。但是如果没有任何细节,我们很难知道如何解决您的问题。 - CodeThug
我改变了解决问题的方式,然后解决了它…… 只是路过,想说一声。 - Hassan Faghihi
@deadManN,你使用了哪种策略来禁用缓存? - jaybro
我读了你的文章,非常有价值,谢谢!但是我想请教一下关于EF Core的问题。很让人沮丧的是,我没有找到你在EF Core中提供的某些解决方案。 我特别感兴趣的是MergeOptions解决方案或其在ef core中的对等物,如果你能帮我解决这个问题,那就太棒了。我不能使用AsNoTracking,因为我需要更新实体,我也不能使用新的DbContext,因为有一些架构设计上的要求,而Detach解决方案则需要每次都应用,这对我来说会很繁琐。 那么,UseMemoryCache方法能帮助我解决问题吗? - Hakan Fıstık

9

我也遇到了这个问题,但我解决了。

我使用仓储模式并使用 .Net Core 的默认 DI 机制。一开始我一直在使用 AddSingleton(...),但是与 DbContext 一起使用是错误的。

所以,我改用 AddScoped,就像我从文档中读到的一样:Scoped 生命周期服务每个请求只创建一次。

它解决了我的问题。

你必须阅读来自微软文档的这一节:Service Lifetimes and Registration Options


1
这帮了我很多! - PassionateDeveloper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接