gpt4 book ai didi

entity-framework - Entity Framework 渴望加载可加载所有内容

转载 作者:行者123 更新时间:2023-12-01 05:12:22 24 4
gpt4 key购买 nike

我们正在基于Web的应用程序中使用Entity Framework + Repository Pattern来获取数据库。由于业务复杂,我们的模型有时会变得复杂,这会导致Entity Framework急切加载系统出现奇怪的行为。

请想象我们这样的真实模型。我们有桌子,在桌子上的盒子,可以在桌子上或盒子里的铅笔盒,以及可以在桌子上,盒子里或铅笔盒里的铅笔。
我们已经在这样的应用程序中对此建模。

public class Table
{
public int TableID{ get; set; }
public virtual ICollection<Box> Boxes{ get; set; }
public virtual ICollection<PencilCases> PencilCases{ get; set; }
public virtual ICollection<Pencils> Pencils{ get; set; }
}

public class Box
{
public int BoxID{ get; set; }
public int TableID{ get; set; }
[ForeignKey("TableID")]
public virtual Table Table{ get; set; }
public virtual ICollection<PencilCases> PencilCases{ get; set; }
public virtual ICollection<Pencils> Pencils{ get; set; }
}

public class PencilCases
{
public int PencilCaseID{ get; set; }
public int? BoxID{ get; set; }
public int TableID{ get; set; }
[ForeignKey("TableID")]
public virtual Table Table{ get; set; }
[ForeignKey("BoxID")]
public virtual Box Box{ get; set; }
public virtual ICollection<Pencils> Pencils{ get; set; }
}

public class Pencils
{
public int PencilID{ get; set; }
public int? PencilCaseID{ get; set; }
public int? BoxID{ get; set; }
public int TableID{ get; set; }
[ForeignKey("TableID")]
public virtual Table Table{ get; set; }
[ForeignKey("BoxID")]
public virtual Box Box{ get; set; }
[ForeignKey("PencilCaseID")]
public virtual PencilCase PancelCase{ get; set; }
}


我们的存储库模式实现与本教程 http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application类似
因此,我们这样调用get方法。

var tables = unitOfWork.TableRepository.Get(includeProperties: "Boxes, PencilCases, Boxes.Pencils");


所以问题是结果与我的期望有很大不同;我希望仅会提取 BoxesPencilCasesBoxes.Pencils集合,但是所有从数据库中获取的Pencil实体(包括 PencilsPencilCases.Pencils和< cc>。由于数据量,此递归提取导致 Boxes.PencilCases.Pencils

我不明白为什么实体框架会提取Boxes.Pencils以外的所有Pencil。我也尝试指定用Expression而不是Query Path包括列表,但结果没有改变。

最佳答案

首先-我本人对EF还是陌生的,因此,如果以下内容并非100%准确,请原谅。但是,几天前我已经解决了这个完全相同的问题,因此希望对您有所帮助。
问题在于,当EF加载特定实体时,它将将该实体添加到它出现在其中的数据模型的每个部分中-而不仅仅是显式加载的部分。
这意味着即使没有特别要求,也会自动解析Pencil的ICollection中Boxes.Pencils中的每个Table.Pencils
事实本身并不构成问题,甚至可以对用户驱动的MVC应用程序有所帮​​助。
当您尝试执行通过数据实体进行递归的所有操作时,这一切都会出错,例如,尝试将自递归数据实体映射到业务模型,或者尝试将自递归数据实体转换为JSON / XML。
现在,有几种解决此问题的方法:
实现一个映射器/编码器,该映射器哈希/记住每个对象,并且只添加一次:
这一问题的原因在于,它可能导致一些难以预测的结果,尤其是当您需要/需要在多个位置放置对象时。此外,哈希和比较每个对象可能会很昂贵。
实现可配置为忽略某些属性的映射器/编码器
相对简单-如果您可以指定完全不希望映射或编码Pencil,则不会有任何问题。不利之处当然是,如果您对指定忽略的属性不保持警惕,则仍然可能会遇到stackoverflow。
实现具有指定递归深度的映射器/编码器
这是一个非常简单且相当不错的解决方案-只需对全局或每个类型设置递归深度的硬限制,就不会有更多的stackoverflows。缺点是您仍然会得到不需要的元素,从而获得不必要的膨胀对象。
实施自定义业务实体
这可能是最好的解决方案-只需创建一个新的业务实体,并删除有问题的导航属性即可。主要缺点是,您将需要为不同的目的创建不同的业务实体。
这是一个例子:

// Removed Pencils
public class BusinessTable
{
public int TableID{ get; set; }
public IEnumerable<Box> Boxes{ get; set; }
public IEnumerable<PencilCases> PencilCases{ get; set; }
}

// Removed Table & PencilCases
public class BusinessBox
{
public int BoxID{ get; set; }
public int TableID{ get; set; }
public IEnumerable<Pencils> Pencils{ get; set; }
}

// Removed Table & Box & Pencils
public class BusinessPencilCases
{
public int PencilCaseID{ get; set; }
public int? BoxID{ get; set; }
public int TableID{ get; set; }
}

// Removed Table, Box, PencilCase
public class BusinessPencils
{
public int PencilID{ get; set; }
public int? PencilCaseID{ get; set; }
public int? BoxID{ get; set; }
public int TableID{ get; set; }
}

现在,当您将数据实体映射到这组业务实体时,将不会再有任何错误。
对于映射方面,有2个解决方案:手动处理/使用映射工厂 Example of Model FactoryValueInjecterAutoMapper-后两个是可用的NuGet软件包。
对于AutoMapper:
我不使用AutoMapper,但是您必须创建一个看起来像这样的配置文件:
Mapper.CreateMap<Table, BusinessTable>();
Mapper.CreateMap<Box, BusinessBox>();
Mapper.CreateMap<PencilCases, BusinessPencilCases>();
Mapper.CreateMap<Pencils, BusinessPencils>();

然后在您的查询中:
var tables = unitOfWork.TableRepository.Get(includeProperties: "Boxes, PencilCases, Boxes.Pencils");
var result = Mapper.Map<IEnumerable<Table>, IEnumerable<BusinessTable>>(tables);

要么
var tables = unitOfWork.TableRepository.Get(includeProperties: "Boxes, PencilCases, Boxes.Pencils").Project().To<IEnumerable<BusinessTable>;

有关AutoMapper的更多信息(如如何设置配置文件): https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
对于ValueInjecter:
var tables = unitOfWork.TableRepository.Get(includeProperties: "Boxes, PencilCases, Boxes.Pencils");
var result = new List<BusinessTable>().InjectFrom(tables);

要么:
var tables = unitOfWork.TableRepository.Get(includeProperties: "Boxes, PencilCases, Boxes.Pencils");
var result = tables.Select(x => new BusinessTable.InjectFrom(x).Cast<BusinessTable>());

还可能值得考虑其他ValueInjecter注入,例如 SmartConventionInjectionDeep CloningUseful InjectionsORM with ValueInjecter指南。
我还为自己的项目做了一些注入,可能对您有用,您可以找到 On my Github
例如,使用MaxDepthCloneInjector,您可以提供(属性名称,最大递归深度)的字典,并且它将仅映射字典中包含的值,并且仅映射到指定级别。
还有两点建议:

如果您希望查询具有更大的自由度,则应考虑将 Query Expression Syntax用于某些更复杂的需求。关于SO的此答案中也有一些很好的信息: How to limit number of related data with Include
如果您打算运行包含导航属性的查询,例如您的示例中的查询:STICK WITH EAGER LOADING。像在延迟加载中那样的查询将导致 N + 1问题。根据经验:


如果您不需要立即获得整个结果集,请使用“延迟加载”,例如,如果您正在开发一个应用程序,其中数据需求自然会根据用户与应用程序的交互而扩展。




如果您需要立即使用整个结果集(例如,在Web Api中或需要与完整实体一起使用的应用程序中),请使用“急切加载”。



祝你好运
费利克斯

关于entity-framework - Entity Framework 渴望加载可加载所有内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23649314/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com