使用实体框架缓存和延迟加载

 jysghyb 发布于 2023-02-14 03:45

假设我有一个应用程序,例如一个网站,我的objectcontext在请求期间离开.我应该缓存一些使用EF加载的数据,以避免读取数据库并提高性能.

好吧,我用EF读取数据,我将对象放在缓存中(说AppFabric,而不是内存缓存),但是可以延迟加载的相关数据现在为空(对此属性的访问会导致nullreferenceexception).我不想在一个请求中加载所有内容,因为它会太长,所以我希望按需加载,一旦读取,我想用新获取的数据完成缓存.

注意 :

只读操作,没有创建/更新/删除.

不想使用Jarek Kowalski制作的"EF Provider Wrappers"等二级缓存

我怎样才能做到这一点 ?

编辑:我用northwind数据库构建了这个样本,它正在工作:

class Program
{
    static void Main(string[] args)
    {
        // normal use
        List allProductCached = null;
        using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
        {
            allProductCached = db.Products.ToList().Clone>();
            foreach (var product in db.Products.Where(e => e.UnitPrice > 100))
            {
                Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
            }
        }

        // try to use cache, but missing Suppliers
        using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
        {
            foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
            {
                if (product.Suppliers == null)
                    product.Suppliers = db.Suppliers.FirstOrDefault(s => s.SupplierID == product.SupplierID).Clone();
                Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
            }
        }

        // try to use full cache
        using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
        {
            foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
            {
                Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
            }
        }
    }
}

public static class Ext
{
    public static List Clone(this List list)
    {
        return list.Select(obj =>
            new Products
            {
                ProductName = obj.ProductName,
                SupplierID = obj.SupplierID,
                UnitPrice = obj.UnitPrice
            }).ToList();
    }

    public static Suppliers Clone(this Suppliers obj)
    {
        if (obj == null)
            return null;
        return new Suppliers
        {
            SupplierID = obj.SupplierID,
            CompanyName = obj.CompanyName
        };
    }
}

问题是我必须复制所有内容(不丢失属性)并在属性为null时测试到处并加载所需的属性.我的代码当然越来越复杂,如果我错过了某些东西,这将是一个问题.没其他解决方案?

1 个回答
  • 如果没有a 或a,则无法访问EF中的数据库.ObjectContextDbContext

    仍然有效地使用缓存,即使你没有原来的语境了.

    也许您的场景是这样的......想象一下,您有一些经常使用的参考数据.您不希望每次需要时都访问数据库,因此将其存储在缓存中.您还拥有不想缓存的每用户数据.您具有从用户数据到参考数据的导航属性.您希望从数据库加载用户数据,并让EF 自动"修复"导航属性以指向参考数据.

    对于请求:

      创建一个新的DbContext.

      从缓存中检索参考数据.

      制作参考对象的深层副本.(您可能不希望同时将多个上下文连接到相同的实体.)

      将每个引用对象附加到上下文.(例如DbSet.Attach())

      执行加载每用户数据所需的任何查询.EF将自动"修复"对参考数据的引用.

      识别可以缓存的新加载的实体.确保它们不包含对不应缓存的实体的引用,然后将它们保存到缓存中.

      处理上下文.

    克隆对象和延迟加载

    EF中的延迟加载通常使用动态代理来完成.我们的想法是,您可以创建所有可能动态加载虚拟的属性.每当EF创建实体类型的实例时,它实际上替换了派生类型,并且该派生类型在其属性的重写版本中具有延迟加载逻辑.

    这一切都很好,但在这种情况下,您将实体对象附加到非EF创建的上下文中.您使用一个名为的方法创建了它们Clone.您实例化了真正的POCO实体,而不是一些神秘的EF动态代理类型.这意味着您不会在这些实体上进行延迟加载.

    解决方案很简单.该Clone方法必须采取另一个参数:DbContext.不要使用实体的构造函数来创建新实例.相反,使用DbSet.Create().这将返回动态代理.然后初始化其属性以创建引用实体的克隆.然后将其附加到上下文中.

    以下是您可能用于克隆单个Products实体的代码:

    public static Products Clone(this Products product, DbContext context)
    {
        var set = context.Set<Products>();
        var clone = set.Create();
        clone.ProductName = product.ProductName;
        clone.SupplierID = product.SupplierID;
        clone.UnitProce = product.UnitPrice;
    
        // Initialize collection so you don't have to do the null check, but
        // if the property is virtual and proxy creation is enabled, it should get lazy loaded.
        clone.Suppliers = new List<Suppliers>();
    
        return clone;
    }
    

    代码示例

    namespace EFCacheLazyLoadDemo
    {
        using System;
        using System.Collections.Generic;
        using System.ComponentModel.DataAnnotations.Schema;
        using System.Data.Entity;
        using System.Linq;
    
        class Program
        {
            static void Main(string[] args)
            {
                // Add some demo data.
                using (MyContext c = new MyContext())
                {
                    var sampleData = new Master 
                    { 
                        Details = 
                        { 
                            new Detail { SomeDetail = "Cod" },
                            new Detail { SomeDetail = "Haddock" },
                            new Detail { SomeDetail = "Perch" }
                        } 
                    };
    
                    c.Masters.Add(sampleData);
                    c.SaveChanges();
                }
    
                Master cachedMaster;
    
                using (MyContext c = new MyContext())
                {
                    c.Configuration.LazyLoadingEnabled = false;
                    c.Configuration.ProxyCreationEnabled = false;
    
                    // We don't load the details here.  And we don't even need a proxy either.
                    cachedMaster = c.Masters.First();
                }
    
                Console.WriteLine("Reference entity details count: {0}.", cachedMaster.Details.Count);
    
                using (MyContext c = new MyContext())
                {
                    var liveMaster = cachedMaster.DeepCopy(c);
    
                    c.Masters.Attach(liveMaster);
    
                    Console.WriteLine("Re-attached entity details count: {0}.", liveMaster.Details.Count);
                }
    
                Console.ReadKey();
            }
        }
    
        public static class MasterExtensions
        {
            public static Master DeepCopy(this Master source, MyContext context)
            {
                var copy = context.Masters.Create();
                copy.MasterId = source.MasterId;
    
                foreach (var d in source.Details)
                {
                    var copyDetail = context.Details.Create();
                    copyDetail.DetailId = d.DetailId;
                    copyDetail.MasterId = d.MasterId;
                    copyDetail.Master = copy;
                    copyDetail.SomeDetail = d.SomeDetail;
                }
    
                return copy;
            }
        }
    
        public class MyContext : DbContext
        {
            static MyContext()
            {
                // Just for demo purposes, re-create db each time this runs.
                Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
            }
    
            public DbSet<Master> Masters { get { return this.Set<Master>(); } }
    
            public DbSet<Detail> Details { get { return this.Set<Detail>(); } }
        }
    
        public class Master
        {
            public Master()
            {
                this.Details = new List<Detail>();
            }
    
            public int MasterId { get; set; }
    
            public virtual List<Detail> Details { get; private set; }
        }
    
        public class Detail
        {
            public int DetailId { get; set; }
    
            public string SomeDetail { get; set; }
    
            public int MasterId { get; set; }
    
            [ForeignKey("MasterId")]
            public Master Master { get; set; }
        }
    }
    

    这是一个与您的不同的示例模型,它说明了如何使其在原理上正常工作.

    2023-02-14 04:21 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有