【问题标题】:entity framework static cache实体框架静态缓存
【发布时间】:2016-02-15 08:53:30
【问题描述】:

我正在开发监控应用程序。例如,打印机,记录在用户配置文件中。目标是获取每台打印机的用户列表。管理员可能必须按用户、PC、默认打印机...过滤结果...并以 CSV 格式导出结果。因此,当管理员进行导出时,csv 需要与活动过滤器相关联。我尝试使用 EF 6 创建一个静态缓存,以保留这些过滤器。

缓存类

public class PrintersCache
{
    public static AuditprinterDBEntities1 db = new AuditprinterDBEntities1();
    public static IQueryable<AuditPrinter> auditPrinterCache = null;
    public static IQueryable<AuditPrinter> AuditPrinterCache
    {
        get
        {
            if (auditPrinterCache == null) auditPrinterCache = db.AuditPrinter.Include(a => a.Pc).Include(a => a.PrintersConfig).Include(a => a.Users);
            return auditPrinterCache;
        }
    }
}

在控制器中,我正在调用我的类

static IQueryable<AuditPrinter> auditPrinter = PrintersCache.AuditPrinterCache; 

然后,在过滤方法的开头:

auditPrinter = PrintersCache.AuditPrinterCache;

以及导出方式:

public void ExportCSV()
{
    var sw = new StringWriter();
    sw.WriteLine(String.Format("{0};{1};{2};{3}", "PcName", "Date", "ActivityName", "UserName"));

    foreach (var record in auditPrinter)        
    {
        sw.WriteLine(String.Format("{0};{1};{2};{3}", record.Pc.PcName, record.Date, record.Activity.ActivityName, record.Users.UserName));
    }
    Response.Clear();
    Response.AddHeader("Content-Disposition", "attachment; filename=Export.csv");
    Response.ContentType = "text/csv";
    Response.Write(sw);
    Response.End();
}

它正在工作......但搜索速度有点慢,例如,如果我很快点击搜索按钮 5 或 6 次,我会得到一个 'System.Data.Entity.Core.EntityException'The underlying provider failed on Open.我需要二级缓存吗?

编辑:搜索过滤方法

public ActionResult LaunchSearch(string keyword, string keyword2, int chx, int pid, int fid)
    {
        auditPrinter = PrintersCache.AuditPrinterCache;
        string returnpartial = "";
        switch (chx)
        {
            case 1:
                if(pid!=0)
                {
                    auditPrinter = auditPrinter.Where(a => a.Printers.PrinterId == pid);
                }
                returnpartial = "Indexprinter";
                break;
            case 2:
                if (pid != 0)
                {
                    auditPrinter = auditPrinter.Where(a => a.UserId == pid);
                } else
                {
                    auditPrinter = auditPrinter.OrderBy(a => a.Users.UserName).ThenBy(a => a.Pc.PcName);
                }
                returnpartial = "Indexvuser";
                break;
            case 3:
                if (pid != 0)
                {
                    auditPrinter = auditPrinter.Where(a => a.Pc.PcId == pid);
                }
                else
                {
                    auditPrinter = auditPrinter.OrderBy(a => a.Pc.PcName).ThenBy(a => a.Users.UserName);
                }
                returnpartial = "Indexvpc";
                break;
        }

        if (keyword != "")
        {
            switch (chx)
            {
                case 1:
                    auditPrinter = auditPrinter.Where(a => a.Users.UserName.Contains(keyword)).OrderBy(a => a.Users.UserName).ThenBy(a => a.Pc.PcName);
                    break;
                case 2:
                    auditPrinter = auditPrinter.Where(a => a.Users.UserName.Contains(keyword)).OrderBy(a => a.Users.UserName).ThenBy(a => a.Pc.PcName);
                    break;
                case 3:
                    auditPrinter = auditPrinter.Where(a => a.Pc.PcName.Contains(keyword)).OrderBy(a => a.Pc.PcName).ThenBy(a => a.Users.UserName);
                    break;
            }
        }

        if (keyword2 != "")
        {
            switch (chx)
            {
                case 1:
                    auditPrinter = auditPrinter.Where(a => a.Users.UserName.Contains(keyword) && a.Pc.PcName.Contains(keyword2)).OrderBy(a => a.Pc.PcName).ThenBy(a => a.Users.UserName);
                    break;
                case 2:
                    auditPrinter = auditPrinter.Where(a => a.Users.UserName.Contains(keyword) && a.Pc.PcName.Contains(keyword2)).OrderBy(a => a.Pc.PcName).ThenBy(a => a.Users.UserName);
                    break;
                case 3:
                    auditPrinter = auditPrinter.Where(a => a.Pc.PcName.Contains(keyword) && a.Users.UserName.Contains(keyword2)).OrderBy(a => a.Users.UserName).ThenBy(a => a.Pc.PcName);
                    break;
            }
        }

        if (fid != 0)
        {
            switch (fid)
            {
                case 1:
                    auditPrinter = auditPrinter.Where(a => a.PrintersConfig.IsDefault == true);
                    break;
                case 2:
                    auditPrinter = auditPrinter.Where(a => a.PrintersConfig.IsDefault == false);
                    break;
            }
        }
        return PartialView(returnpartial, auditPrinter.ToList());
    } 

【问题讨论】:

    标签: c# entity-framework caching static-methods


    【解决方案1】:

    实体框架上下文不应该是静态的,它的生命周期应该尽可能短。

    删除您的缓存类并将上下文创建和查询放在您的 LaunchSearch 方法中,不要忘记释放此上下文。

    仅在存在真正的性能问题时才考虑缓存,缓存复杂对象(如实体)很少是一个好主意。如果您需要缓存,请尝试使用 HTTP 缓存、客户端或服务器端。


    public ActionResult LaunchSearch(string keyword, string keyword2, int chx, int pid, int fid)
    {
        using(var db = new AuditprinterDBEntities1())
        {
            var auditPrinter = db.AuditPrinter.Include(a => a.Pc).Include(a => a.PrintersConfig).Include(a => a.Users);
    
            // Do whatever you need to do and return result ...
        }
    }
    

    【讨论】:

    • 好的。我明白。对于 csv 中的导出,最好的缓存技术是什么?
    • 为什么你认为你需要缓存它?
    • 因为,在我的 csv 导出方法中,我需要根据查看活动过滤器导出列表。这就是为什么我认为这是唯一的解决方案,在另一种方法中使用查询结果。也许这是一个坏主意,还有一个更简单的解决方案......
    • 每次需要数据时都使用正确的过滤器来查询数据库,它真的是要这样使用的。数据库引擎已经提供了透明的缓存,但也提供了更有效的方法来搜索/过滤数据。现在如果你真的有性能问题,缓存是一种选择,但并不总是正确的解决方案。
    • 静态缓存不是个好主意...我采取了另一种方式,谢谢
    【解决方案2】:

    我建议你改变PrinterCache如下。

    public class PrintersCache
    {
        public static AuditprinterDBEntities1 db = new AuditprinterDBEntities1();
        static PrintersCache()
        {
            AuditPrinterCache = db.AuditPrinter
                                .Include(a => a.Pc)
                                .Include(a => a.PrintersConfig)
                                .Include(a => a.Users);
        }
        public static IQueryable<AuditPrinter> AuditPrinterCache
        {
            get; private set;
        }
    }
    

    这仍然没有解决问题,因为您实际上没有缓存。只是存储一个 linq 查询,当您循环访问 foreach (var record in auditPrinter) 时,它会被懒惰地调用。

    所以这将被多次调用并导致性能问题,以避免它通过使其列表来存储结果。所以这个类应该是这样的

    public class PrintersCache
    {
        public static AuditprinterDBEntities1 db = new AuditprinterDBEntities1();
        static PrintersCache()
        {
            AuditPrinterCache = db.AuditPrinter
                                .Include(a => a.Pc)
                                .Include(a => a.PrintersConfig)
                                .Include(a => a.Users).ToList;
        }
        public static List<AuditPrinter> AuditPrinterCache
        {
            get; private set;
        }
    }
    

    【讨论】:

    • 我需要使用 IQueryable,因为在我的过滤方法中我会这样查询:auditPrinter = auditPrinter.Where(a => a.PrintersConfig.IsDefault == true); .对于错误内部异常说'连接没有关闭。连接的当前状态是connecting'
    • @mrplume ,使用 Where 条件查询缓存没有问题。通过这个实现,所有关于 AuditPrinter 的信息都被检索一次。您可以使用 .Net 框架的MemoryCache 类自动更新缓存一段时间。
    【解决方案3】:

    使用内存缓存,您可以这样做:

    class PrintersCache
    {
        private const string CacheName = "MyCacheName";        
        private const string AuditPrinterKey = "AuditPrinterKey";
        private static readonly MemoryCache memoryCache = new MemoryCache(CacheName);
        private const int CacheExpirationInMinutes = 20;
    
        public static List<AuditPrinter> GetAuditPrinterCache()
        {
            // Create a lazy object to retrieve the data when the cache has expired
            var newLazyValue = new Lazy<List<AuditPrinter>>(() =>
            {
                // You should not keep an instance of your db context without disposing it. Also The instantiation of a db context is cheap.
                using (var db = new AuditprinterDBEntities1())
                {
                    return db.AuditPrinter
                        .Include(a => a.Pc)
                        .Include(a => a.PrintersConfig)
                        .Include(a => a.Users).ToList();
                }
            });
    
            // Return the instance of the Lazy object. If the cahce has expired a new instance of the Lazy object is created.
            return
                ((Lazy<List<AuditPrinter>>)
                    memoryCache.AddOrGetExisting(AuditPrinterKey, newLazyValue, new CacheItemPolicy()
                    {
                        // Defines that the cache will expired after 20min
                        AbsoluteExpiration = new DateTimeOffset(
                            DateTime.UtcNow.AddMinutes(CacheExpirationInMinutes))
                    })).Value;
        }
    }
    
    • 使用内存缓存的AddOrGetExisting 方法,如果缓存中不存在对象,您将提供某种回退。
    • 使用Lazy&lt;T&gt;,只会在缓存为空时调用数据库。
    • 使用 AbsoluteExpiration,您可以确保在数据库发生某些更改时更新您的缓存(在这种情况下每 20 分钟一次)。

    因为List&lt;T&gt; 是可查询的,所以您可以这样检索数据:

    // Initializes your query
    var query = PrintersCache.GetAuditPrinterCache().Where(a => a.Printers.PrinterId == pid);
    
    // apply your others conditions here
    ...
    query = query .Where(a => a.Users.UserName.Contains(keyword)).OrderBy(a => a.Users.UserName).ThenBy(a => a.Pc.PcName);
    
    // Return your new filtered list
    return query.ToList()
    

    【讨论】:

      猜你喜欢
      • 2012-12-13
      • 1970-01-01
      • 2018-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-09
      • 1970-01-01
      • 2013-12-17
      相关资源
      最近更新 更多