当前位置: 首页 > 编程语言 > C#

实体框架中多个“包含”的最佳实践是什么?在

时间:2023-04-10 23:31:05 C#

实体框架中共享多个“包含”的最佳实践是什么?假设我们的数据模型中有四个实体:Categories、Books、Authors和BookPages。还假设Categories-Books、Books-Authors和Books-BookPages关系是一对多的。如果从数据库中检索类别实体实例(包括“Books”、“Books.BookPages”和“Books.Authors”),这将成为一个严重的性能问题。此外,不包括它们将导致“对象引用未设置为对象的实例”异常。使用多个Include方法调用的最佳做法是什么?编辑:第二个选项我的意思是这样的:foreach(includeFields中的字符串includeField){categories=categories.Include(includeField);}returncategories.SingleOrDefault(i=>i.CategoryId==categoryId);调用时我们需要这样的代码:CategorytheCategory1=CategoryHelper.GetCategoryById(db,5,"Books");分类theCategory2=CategoryHelper.GetCategoryById(db,5,"Books","Books.Pages");类别theCategory3=CategoryHelper.GetCategoryById(db,5,"Books","Books.Authors");类别theCategory4=CategoryHelper.GetCategoryById(db,5,"Books","Books.Pages","Books.Authors");这种方法有什么明显的缺点吗?编写一个方法GetCategoryById并发送一个关系列表,其中包含(可能,但似乎不够优雅)编写方法,如GetCategoryByIdWithBooks、GetCategoryByIdWithBooksAndBooksPages和GetCategoryByIdWithBooksAndAuthors(不实用)目前我的方法是这两者的组合。我知道我想为每个上下文包含哪些属性,所以我宁愿手动编写它们(正如你自己所说,延迟加载并不总是一个选项,如果是的话,你会重复相同的重复包含()fromthedata类似于模型映射到DTO时的语法。这种分离让您更多地考虑要公开的数据集,因为数据访问代码通常隐藏在服务下。通过使用包含虚方法的基类,您可以覆盖Include()以运行您需要的内容:usingSystem.Data.Entity;publicclassDataAccessBase{//例如将其重定向到DbContext.Set()。公共IQueryable数据集{得到;私有集;}publicIQueryableInclude(Func,IQueryable>include=null){if(include==null){//如果省略,应用默认的Include()方法//(当它存在时将调用覆盖的Include())include=Include;}返回包括(数据集);}publicvirtualIQueryableInclude(IQueryableentities){//提供可选的entities.Include(f=>f.Foo)必须包含在所有实体中returnentities;然后您可以按原样实例化和使用此类,或扩展它:usingSystem.Data.Entity;publicclassBookAccess:DataAccessBase{//重写以指定要为每本书运行的Include()s}//一个单独的Include()方法privateIQueryableIncludePages(IQueryableentities){returnentities.Include(e=>e.Pages);}//从外部访问此方法以检索每本书的所有页面publicIEnumerableGetBooksWithPages(){varbooks=Include(IncludePages);现在您可以实例化BookAccess并对其调用方法:varbookAccess=newBookAccess();varallBooksWithoutNavigationProperties=bookAccess.DataSet;varallBooksWithAuthors=bookAccess.Include();varallBooksWithAuthorsAndPages=bookAccess.GetBooksWithPages();在您的情况下,您可能希望为集合的每个视图创建单独的IncludePages和GetBooksWithPages-类似方法对,或者只是将其编写为一个方法,存在IncludePages方法以实现可重用性。您可以随心所欲地链接这些方法,因为每个方法(以及EntityFramework的Include()扩展方法)都会返回另一个IQueryable。正如@Colin在评论中提到的,您需要在定义导航属性时使用virtual关键字,以便它们使用延迟加载。假设您使用代码优先,您的Book类应该如下所示:publicclassBook{publicintBookID{get;放;}//关于这本书的任何其他信息...publicvirtualCategoryCategory{get;放;}publicvirtualListAuthors{get;放;}publicvirtualListBookPages{get;当然,如果您正在创建一本新书,它将无法延迟加载,并且如果您尝试迭代BookPages只会抛出NullReferenceException。这就是为什么您应该做以下两件事之一:定义一个包含BookPages=newList();的Book()构造函数;BookPages=newList();(对于作者也是如此)或者当你创建一个新条目时确保只有“newBook()”,你会立即将其保存到数据库中然后丢弃而不试图从中获取任何东西。我个人更喜欢第二种选择,但我知道很多人更喜欢第一种。我找到了第三个选项,即使用DbSet类的Create方法。这意味着您可以调用myContext.Books.Create()而不是newBook()。有关详细信息,请参阅此Q+A:DbSet.Create与newentity()不同现在,延迟加载可以中断的另一种方法是将其关闭。(我假设ModelEntities是您的DbContext类的名称。)要关闭它,您可以设置ModelEntities.Configuration.LazyLoadingEnabled=false;非常不言自明,不是吗?最重要的是,您不应在任何地方使用Include()。它实际上意味着更多的优化,而不是代码运行的要求。过度使用Include()会导致性能非常差,因为您最终会从数据库中获得比您真正需要的更多的东西,因为Include()将始终引入所有相关记录。假设您正在加载一个类别,并且您有1000本书属于该类别。使用Include()函数时,您无法将其过滤为仅包含JohnSmith撰写的书籍。但是,您可以(启用延迟加载)执行以下操作:Categorycat=ModelEntities.Categorys.Find(1);varbooks=cat.Books.Where(b=>b.Authors.Any(a=>a.Name=="JohnSmith"));这实际上导致从数据库返回的记录更少并且更容易理解。希望有帮助!;)某些性能注意事项是特定于ADO.Net连接器的。如果您没有获得所需的性能,我会记住数据库视图或存储过程作为备份。首先,请注意DbContext(和ObjectContext)对象不是线程安全的。如果您担心对性能苛刻,那么第一个选项是最简单的。另一方面,如果您关心性能-并且愿意在获取数据后处理上下文对象-那么您可以使用多个并发任务(线程)使用它们自己的上下文对象来查询数据。如果您需要上下文来跟踪数据的更改,您可以使用单个查询以直接的方式将所有项目添加到上下文,或者您可以使用Attach方法“重建”原始状态,然后更改并保存.后者类似于:using(vardbContext=newDbContext()){varcategoryToChange=newCategories(){//将属性设置为原始数据};dbContext.Categories.Attach(categoryToChange);//设置更改的属性dbContext.SaveChanges();不幸的是,没有适合所有情况的单一最佳实践。在dbfirst方法中,假设您创建了BookStore.edmx并添加了Category和Book实体,并且它生成了像publicpartialclassBookStoreContext:DbContext这样的上下文,所以如果您可以像这样添加部分类,这是一个简单的好做法:publicpartialclassBookStoreContext{publicIQueryableGetCategoriesWithBooks(){returnCategories.Include(c=>c.Books);}publicIQueryableGetCategoriesWith(paramsstring[]includeFields){varcategories=Categories.AsQueryable(;foreach(stringincludeFieldinincludeFields){categories=categories.Include(includeField);}returncategories;}//只是另一个例子publicIQueryableGetBooksWithAllDetails(){returnBooks.Include(c=>c.Books.Authors).(c=>c.Books.Pages);}//另一个复杂的例子publicIQueryableGetNewBooks(/*...*/){//也许你可以在参数中传递排序依据、标签过滤器等。}}然后你可以这样使用它:varcategory1=db.CategoriesWithBooks().Where(c=>c.Id=5).SingleOrDefault();varcategory2=db.CategoriesWith("Books.Pages","Books.Authors").Where(c=>c.Id=5).SingleOrDefault();//自定义包含注意:我自己更喜欢显式加载实体,因为我会“知道”当前加载的是什么,但延迟加载仅有条件地加载子实体仅在以下情况下有用,并且应该在数据库上下文的小范围内使用,否则我无法控制数据库的命中率和结果有多大如果分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: