【问题标题】:Caching and lazy loading with entity framework使用实体框架进行缓存和延迟加载
【发布时间】:2013-12-14 17:21:49
【问题描述】:

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

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

注意:

  • 只有读取操作,没有创建/更新/删除。
  • 不想使用像 Jarek Kowalski 制作的“EF Provider Wrappers”这样的二级缓存

我该怎么做?

编辑:我已经用北风数据库构建了这个示例,它正在工作:

class Program
{
    static void Main(string[] args)
    {
        // normal use
        List<Products> allProductCached = null;
        using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
        {
            allProductCached = db.Products.ToList().Clone<DbSet<Products>>();
            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<Suppliers>();
                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<Products> Clone<T>(this List<Products> list)
    {
        return list.Select(obj =>
            new Products
            {
                ProductName = obj.ProductName,
                SupplierID = obj.SupplierID,
                UnitPrice = obj.UnitPrice
            }).ToList();
    }

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

问题是我必须复制所有内容(不丢失属性)并在任何地方测试该属性是否为 null 并加载所需的属性。我的代码当然越来越复杂,所以如果我错过了什么,那将是一个问题。没有其他解决方案?

【问题讨论】:

  • 为什么在使用空对象之前不做一些检查。如果没有数据 - 加载并缓存它?
  • 我没有上下文了
  • 那么如果你没有上下文了,你会用什么方法来查找数据?
  • 我希望我可以使用同一个对象而不做任何事情

标签: c# caching entity-framework-4


【解决方案1】:

无法在没有ObjectContextDbContext 的情况下访问EF 中的数据库。

可以仍然有效地使用缓存,即使您不再拥有原始上下文。

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

对于请求:

  1. 创建一个新的DbContext
  2. 从缓存中检索参考数据。
  3. 制作参考对象的deep copy。 (您可能不希望将相同的实体同时附加到多个上下文。)
  4. 将每个引用对象附加到上下文。 (例如DbSet.Attach()
  5. 执行加载每个用户数据所需的任何查询。 EF 将自动“修复”对参考数据的引用。
  6. 识别新加载的可以缓存的实体。确保它们不包含对不应缓存的实体的引用,然后将它们保存到缓存中。
  7. 处理上下文。

克隆对象和延迟加载

EF 中的延迟加载通常使用dynamic proxies 完成。这个想法是让所有可能被动态加载的属性都变成虚拟的。每当 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; }
    }
}

这是一个与您的不同的示例模型,它展示了如何在原则上使其工作。

【讨论】:

  • 感谢您的回答,我还没有完全理解,请看我的编辑
  • 感谢您的回答,这里的供应商不是列表,但我尝试使用 Order_Details 但它不起作用,我的列表是空的(属性是虚拟的,并且启用了代理创建)
  • 检查msdn.microsoft.com/en-us/library/vstudio/… 以确保您已满足延迟加载代理的所有要求。另外,在您的克隆上执行GetType().FullName 以确保它确实是代理类型。
  • 它真的是一个代理,当我执行 clone.Suppliers = context.Set().Create();但是,延迟加载不起作用,供应商中的所有内容都是空的。对于列表,clone.Order_Details = new List();不是代理,它是一个空列表:(。注意:满足要求
  • 在处理原始上下文之前,延迟加载是否适用于原始 Suppliers 对象及其 Order_Details 属性...?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
相关资源
最近更新 更多