数据加载EF中和数据加载关系最

📅 2026/7/3 1:12:32
数据加载EF中和数据加载关系最
我们常用的ToList()很像只是它不创建列表只是把数据缓存到EF Context中。12varproductGet context.SetProduct().Where(rr.Id 1).ToList();context.SetProduct().Where(rr.Id 1).Load();第一行代码我们把数据加载到EF Context中并创建一个列表并返回第二个方法我们只是把数据加载到EF Context中。默认情况下我们很少会直接用到Load方法一般ToList或First这样的方法就帮我们完成加载数据操作了。延迟加载EF默认使用延迟加载获取导航属性关联的数据。还是以之前用过的产品和发票为例。通过这个下面代码和注释很容易理解这个特性。1234//此时不会加载Invoice属性关联的对象varproductGet context.SetProduct().First(rr.Id 1);//直到用到Invoice时才会新起一个查询获取Invoicevardate productGet.Invoice.CreateDate;作为默认配置的延迟加载需要满足以下几个条件context.Configuration.ProxyCreationEnabled true;context.Configuration.LazyLoadingEnabled true;导航属性被标记为virtual这三个条见缺一不可。如果不满足条件延迟加载则不会启用这时候我们必须使用手动加载的方式来获取关联数据否则程序在访问到导航属性又没法进行延迟加载时就会报空引用异常。手动加载就是通过DbReferenceEntry的Load方法来实现。我们把设置context.Configuration.LazyLoadingEnabled false;全局禁用延迟加载以便在没有延迟加载的环境进行测试。把导航属性virtual去掉可以禁用单个实体的延迟加载。12345//此时不会加载Invoice属性关联的对象varproductGet context.SetProduct().First(rr.Id 1);//手动加载Invoicecontext.Entry(productGet).Reference(p p.Invoice).Load();vardate productGet.Invoice.CreateDate;与自动延迟加载一样手动加载也是两条独立的SQL分别获取数据。手动加载集合属性也类似就是把Reference方法换成Collection方法。以ProductPhoto为例12345//此时不会加载Invoice属性关联的对象varproductGet context.SetProduct().First(rr.Id 1);//手动加载Photos集合context.Entry(productGet).Collection(p p.Photos).Load();varcount productGet.Photos.Count;预加载延迟加载包括手动加载这些方式中获取关联数据都需要两条独立的SQL。如果我们确实同时需要一个对象及其关联数据可以使用预加载以使它们通过一条SQL获取。在之前测试关联的代码中我们已多次使用到预加载。1varproduct context.SetProduct().Include(pp.Invoice).FirstOrDefault();这是之前用于测试的一条语句。我们同时再加产品及其发票生成的SQL中使用了JOIN由两个表获取数据。预加载就是使用Include方法并传入需要同时获取的关联属性。我们也可以使用字符串传入属性的名称如1varproduct context.SetProduct().Include(Invoice).FirstOrDefault();但这样肯定没有使用lambda更有利于避免输入错误。预加载也支持同时加载二级属性比如我们给Invoice增加一个开票人属性这是一个Employee对象。1234567891011121314publicclassInvoice{publicintId {get;set; }publicstringInvoiceNo {get;set; }publicDateTime CreateDate {get;set; }publicvirtualEmployee Drawer {get;set; }}publicclassEmployee{publicintId {get;set; }publicstringName {get;set; }publicstringEmpNo {get;set; }}如下代码我们可以在查询Product同时加载Invoice和Employee。1varproduct context.SetProduct().Include(pp.Invoice.Drawer).FirstOrDefault();同样字符串参数也是支持的1varproduct context.SetProduct().Include(Invoice.Drawer).FirstOrDefault();此时生成的SQL会含有2次JOIN代码太长就不列出了。并发本着实用的原则其实主要原因是博主的理论知识也只是自己心里明白做不到给大家讲明白的程度这部分就不讲太多关于数据库隔离级别以及不同隔离级别并发时出现的结果等等。我们使用最简单的Product类进行测试先写入一条数据123varproduct newProduct() { Name 投影仪, Description 高分辨率};context.SetProduct().Add(product);context.SaveChanges();然后我们编写一个并发测试类来模拟2个用户同时编辑同一个Product的情况1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162publicclassConcurrencyTest : IDisposable{privatereadonlyDbContext _user1Context;privatereadonlyDbContext _user2Context;publicConcurrencyTest(){_user1Context newCodeFirstForBlogContext();_user2Context newCodeFirstForBlogContext();}publicvoidEditProductConcurrency(){User1Edit();User2Edit();User2Save();User1Save();}privatevoidUser1Edit(){varproduct _user1Context.SetProduct().First();product.Name product.Name edited by user1 at DateTime.Now.ToString(MM-dd HH:mm:ss);}privatevoidUser1Save(){_user1Context.SaveChanges();}privatevoidUser2Edit(){varproduct _user2Context.SetProduct().First();product.Name product.Name edited by user2 at DateTime.Now.ToString(MM-dd HH:mm:ss);}privatevoidUser2Save(){_user2Context.SaveChanges();}publicvoidDispose(){Dispose(true);GC.SuppressFinalize(this);}protectedvirtualvoidDispose(booldisposing){if(disposing true){_user1Context.Dispose();_user2Context.Dispose();}}~ConcurrencyTest(){Dispose(false);}}我们之前看到的那些Fluent API配置都没有启用并发支持我们在没有并发支持的情况下看看这段代码的执行情况1234using(ConcurrencyTest test newConcurrencyTest()){test.EditProductConcurrency();}