C# “什么是多个”的最佳实践;包括「-在实体框架中是什么?
假设数据模型中有四个实体:类别、书籍、作者和书页。还假设书籍类别、书籍作者和书籍书页关系是一对多关系 如果从数据库中检索类别实体实例(包括“Books”、“Books.BookPages”和“Books.Authors”),这将成为一个严重的性能问题。此外,不包括它们将导致“对象引用未设置为对象的实例”异常 使用多个Include方法调用的最佳实践是什么C# “什么是多个”的最佳实践;包括「-在实体框架中是什么?,c#,entity-framework,architecture,data-access-layer,ef-database-first,C#,Entity Framework,Architecture,Data Access Layer,Ef Database First,假设数据模型中有四个实体:类别、书籍、作者和书页。还假设书籍类别、书籍作者和书籍书页关系是一对多关系 如果从数据库中检索类别实体实例(包括“Books”、“Books.BookPages”和“Books.Authors”),这将成为一个严重的性能问题。此外,不包括它们将导致“对象引用未设置为对象的实例”异常 使用多个Include方法调用的最佳实践是什么 编写一个方法GetCategoryById并包含其中的所有项(性能问题) 编写一个方法GetCategoryById并发送一个要包含的关系列
- 编写一个方法GetCategoryById并包含其中的所有项(性能问题)
- 编写一个方法GetCategoryById并发送一个要包含的关系列表(可能,但似乎还不够优雅)
- 编写方法,如GetCategoryByWithBooks、GetCategoryByWithBooks和BooksPages以及GetCategoryByWithBooks和Authors(不实用)
public static Category GetCategoryById(ModelEntities db, int categoryId, params string[] includeFields)
{
var categories = db.Categories;
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories.SingleOrDefault(i => i.CategoryId == categoryId);
}
Category theCategory1 = CategoryHelper.GetCategoryById(db, 5, "Books");
Category theCategory2 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages");
Category theCategory3 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Authors");
Category theCategory4 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages", "Books.Authors");
public class Book
{
public int BookID { get; set; }
//Whatever other information about the Book...
public virtual Category Category { get; set; }
public virtual List<Author> Authors { get; set; }
public virtual List<BookPage> BookPages { get; set; }
}
public partial class BookStoreContext
{
public IQueryable<Category> GetCategoriesWithBooks()
{
return Categories.Include(c => c.Books);
}
public IQueryable<Category> GetCategoriesWith(params string[] includeFields)
{
var categories = Categories.AsQueryable();
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories;
}
// Just another example
public IQueryable<Category> GetBooksWithAllDetails()
{
return Books
.Include(c => c.Books.Authors)
.Include(c => c.Books.Pages);
}
// yet another complex example
public IQueryable<Category> GetNewBooks(/*...*/)
{
// probably you can pass sort by, tags filter etc in the parameter.
}
}
var category1 = db.CategoriesWithBooks()
.Where(c => c.Id = 5).SingleOrDefault();
var category2 = db.CategoriesWith("Books.Pages", "Books.Authors")
.Where(c => c.Id = 5).SingleOrDefault(); // custom include
调用时,我们需要这样的代码:
public static Category GetCategoryById(ModelEntities db, int categoryId, params string[] includeFields)
{
var categories = db.Categories;
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories.SingleOrDefault(i => i.CategoryId == categoryId);
}
Category theCategory1 = CategoryHelper.GetCategoryById(db, 5, "Books");
Category theCategory2 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages");
Category theCategory3 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Authors");
Category theCategory4 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages", "Books.Authors");
public class Book
{
public int BookID { get; set; }
//Whatever other information about the Book...
public virtual Category Category { get; set; }
public virtual List<Author> Authors { get; set; }
public virtual List<BookPage> BookPages { get; set; }
}
public partial class BookStoreContext
{
public IQueryable<Category> GetCategoriesWithBooks()
{
return Categories.Include(c => c.Books);
}
public IQueryable<Category> GetCategoriesWith(params string[] includeFields)
{
var categories = Categories.AsQueryable();
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories;
}
// Just another example
public IQueryable<Category> GetBooksWithAllDetails()
{
return Books
.Include(c => c.Books.Authors)
.Include(c => c.Books.Pages);
}
// yet another complex example
public IQueryable<Category> GetNewBooks(/*...*/)
{
// probably you can pass sort by, tags filter etc in the parameter.
}
}
var category1 = db.CategoriesWithBooks()
.Where(c => c.Id = 5).SingleOrDefault();
var category2 = db.CategoriesWith("Books.Pages", "Books.Authors")
.Where(c => c.Id = 5).SingleOrDefault(); // custom include
这种方法有什么明显的缺点吗?一些性能考虑是ADO.Net连接器特有的。如果您没有获得所需的性能,我会记住数据库视图或存储过程作为备份 首先,请注意
DbContext
(和ObjectContext
)对象不是线程安全的
如果您关心的是Clarity而不是性能,那么第一个选项是最简单的
另一方面,如果您担心性能,并且愿意在获取数据后处理上下文对象,那么您可以使用多个同时执行的任务(线程)查询数据,每个任务(线程)使用自己的上下文对象
如果您需要一个上下文来跟踪对数据的更改,您可以直接通过单个查询将所有项添加到上下文中,或者可以使用Attach方法“重建”原始状态,然后进行更改和保存
后者是这样的:
不幸的是,没有一种最佳实践可以满足所有情况
编写一个方法GetCategoryById并发送一个要包含的关系列表(可能,但似乎还不够优雅)
编写方法,如GetCategoryByWithBooks、GetCategoryByWithBooks和BooksPages以及GetCategoryByWithBooks和Authors(不实用)
我目前的做法是将这两种方法结合起来I知道要为每个上下文包含哪些属性,所以我宁愿手工编写它们(正如您自己所说,延迟加载并不总是一个选项,如果是,您将在从数据模型映射到DTO时重复相同的重复的include()
-类似语法)
由于这样的数据访问代码通常隐藏在服务下面,这种分离会使您更加仔细地考虑要公开哪些数据集
通过使用包含虚拟方法的基类,您可以重写以运行所需的Include()
s:
在您的情况下,您可能希望为集合的每个视图创建单独的IncludePages
和GetBooksWithPages
相似的方法对。或者只将其作为一个方法编写,IncludePages
方法是为了重用而存在的
您可以按自己喜欢的方式链接这些方法,因为它们中的每一个(以及Entity Framework的
Include()
扩展方法)都返回另一个IQueryable
,正如@Colin在注释中提到的那样,您需要在定义导航属性时使用virtual关键字,以便它们能够处理延迟加载。假设您首先使用代码,您的Book类应该如下所示:
public static Category GetCategoryById(ModelEntities db, int categoryId, params string[] includeFields)
{
var categories = db.Categories;
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories.SingleOrDefault(i => i.CategoryId == categoryId);
}
Category theCategory1 = CategoryHelper.GetCategoryById(db, 5, "Books");
Category theCategory2 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages");
Category theCategory3 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Authors");
Category theCategory4 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages", "Books.Authors");
public class Book
{
public int BookID { get; set; }
//Whatever other information about the Book...
public virtual Category Category { get; set; }
public virtual List<Author> Authors { get; set; }
public virtual List<BookPage> BookPages { get; set; }
}
public partial class BookStoreContext
{
public IQueryable<Category> GetCategoriesWithBooks()
{
return Categories.Include(c => c.Books);
}
public IQueryable<Category> GetCategoriesWith(params string[] includeFields)
{
var categories = Categories.AsQueryable();
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories;
}
// Just another example
public IQueryable<Category> GetBooksWithAllDetails()
{
return Books
.Include(c => c.Books.Authors)
.Include(c => c.Books.Pages);
}
// yet another complex example
public IQueryable<Category> GetNewBooks(/*...*/)
{
// probably you can pass sort by, tags filter etc in the parameter.
}
}
var category1 = db.CategoriesWithBooks()
.Where(c => c.Id = 5).SingleOrDefault();
var category2 = db.CategoriesWith("Books.Pages", "Books.Authors")
.Where(c => c.Id = 5).SingleOrDefault(); // custom include
这实际上会减少从数据库返回的记录,并且更易于理解
希望能有帮助 在db-first方法中,假设您创建BookStore.edmx并添加Category和Book实体,它会生成上下文,如
public partial class BookStoreContext:DbContext
,如果您可以像这样添加分部类,那么这是一个简单的好做法:
public static Category GetCategoryById(ModelEntities db, int categoryId, params string[] includeFields)
{
var categories = db.Categories;
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories.SingleOrDefault(i => i.CategoryId == categoryId);
}
Category theCategory1 = CategoryHelper.GetCategoryById(db, 5, "Books");
Category theCategory2 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages");
Category theCategory3 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Authors");
Category theCategory4 = CategoryHelper.GetCategoryById(db, 5, "Books", "Books.Pages", "Books.Authors");
public class Book
{
public int BookID { get; set; }
//Whatever other information about the Book...
public virtual Category Category { get; set; }
public virtual List<Author> Authors { get; set; }
public virtual List<BookPage> BookPages { get; set; }
}
public partial class BookStoreContext
{
public IQueryable<Category> GetCategoriesWithBooks()
{
return Categories.Include(c => c.Books);
}
public IQueryable<Category> GetCategoriesWith(params string[] includeFields)
{
var categories = Categories.AsQueryable();
foreach (string includeField in includeFields)
{
categories = categories.Include(includeField);
}
return categories;
}
// Just another example
public IQueryable<Category> GetBooksWithAllDetails()
{
return Books
.Include(c => c.Books.Authors)
.Include(c => c.Books.Pages);
}
// yet another complex example
public IQueryable<Category> GetNewBooks(/*...*/)
{
// probably you can pass sort by, tags filter etc in the parameter.
}
}
var category1 = db.CategoriesWithBooks()
.Where(c => c.Id = 5).SingleOrDefault();
var category2 = db.CategoriesWith("Books.Pages", "Books.Authors")
.Where(c => c.Id = 5).SingleOrDefault(); // custom include
注:
- 您可以阅读一些简单的(很多复杂的)存储库模式,只是为了扩展
,将commonIDbSet Categories
和Include
分组,而不是使用静态Where
。因此,您可以使用CategoryHelper
IQueryable db.Categories.WithBooks()
- 您不应该在
中包含所有子实体,因为它不会在方法名称中自我解释,如果此方法的用户不是GetCategoryById
实体的兄弟,则会导致性能问题书籍
- 即使您没有包含所有内容,但如果您使用延迟加载,您仍然有潜力
- 如果你有1000本
那么你最好像这样分页加载db.Books.Where(b=>b.CategoryId=CategoryId).Skip(Skip(Skip).Take(Take).ToList()或者更好地添加上面的方法,使其类似于书籍
db.GetBooksByCategoryId(CategoryId,Skip,Take)
我自己更喜欢显式加载实体,因为我会“知道”当前加载的是什么,但延迟加载只有在您有条件加载子实体的情况下才有用,并且应该在db上下文的小范围内使用,否则我无法控制db命中率和结果的大小。能否详细说明为什么会出现这种情况例外?惰性加载对您不起作用吗?正如您已经知道的,当一个人不使用Include方法时,他会得到这个异常。延迟加载当然有效,但使用Include的目的是什么?默认情况下,实体框架可能包含所有关系,并且由于延迟加载,相关的实体在被调用时会被加载。在这种情况下,使用Include没有任何意义。我很好奇,我可能遗漏了什么