如何有效地使用Entity Framework Core?

时间:2016-04-14 13:41:11

标签: c# entity-framework entity-framework-core

让我们来看看简单的类示例:

public class Book
{
    [Key]
    public string BookId { get; set; }
    public List<BookPage> Pages { get; set; }
    public string Text { get; set; }
} 

public class BookPage
{
    [Key]
    public string BookPageId { get; set; }
    public PageTitle PageTitle { get; set; }
    public int Number { get; set; }
}

public class PageTitle
{
    [Key]
    public string PageTitleId { get; set; }
    public string Title { get; set; }
}

所以,如果我想获得所有PageTitiles,如果我只知道BookId,我需要写一些包含,如下所示:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
    .Book
    .Include(x => x.Pages)
    .ThenInclude(x => x.PageTitle)//.ThenInclude(x => x.Select(y => y.PageTitle)) Shouldn't use in EF Core
    .SingleOrDefault(x => x.BookId == "some example id")
    .Pages
    .Select(x => x.PageTitle);
}

如果我想让PageTitles与其他书籍连接,我需要再次重写此方法,除了BookId之外没有任何改变!这是使用数据库的非常低效的方法,在这个例子中我有3个类,但是如果我有数百个类,嵌套到非常深的层次,那么工作会非常缓慢和不舒服。

我应该如何组织使用我的数据库,以避免许多包含和冗余查询?

4 个答案:

答案 0 :(得分:5)

问题1:我每次都要添加一堆Includes

嗯,由于您必须在EF中明确包含相关数据,因此无法解决这个问题,但您可以轻松创建扩展方法以使其更清晰:

public static IQueryable<Book> GetBooksAndPages(this BookContext db)
{
    return db.Book.Include(x => x.Pages);
}

public static IQueryable<Book> GetBooksAndPagesAndTitles(this BookContext db)
{
    return GetBooksAndPages(db).ThenInclude(p => p.PageTitle)

}

然后你可以这样做:

var bookPages = dbContext
    .GetBooksAndPagesAndTitles()
    .SingleOrDefault(x => x.BookId == "some example id")
    .Pages
    .Select(x => x.PageTitle);

问题2:我必须多次为不同的ID编写此查询。

为什么不将它重构为具有bookId参数的方法?

public IEnumerable<PageTitle> GetPageTitlesForBook(BookContext dbContext, int bookId)
{
    return dbContext
        .GetBooksAndPagesAndTitles()
        .SingleOrDefault(x => x.BookId == bookId)
        .Pages
        .Select(x => x.PageTitle);
}

结论 - 如果你发现自己多次写同样的东西,那么这是一个很好的机会,可以将你的代码重构为可以重复使用的小方法。

答案 1 :(得分:1)

我不知何故错过了这是EF Core(尽管标题)。试试这个:

public class BookPage
{
    [Key]
    public string BookPageId { get; set; }
    public int Number { get; set; }
    public PageTitle PageTitle { get; set; }
    public Book Book { get; set; }   // Add FK if desired
}

现在获取一本书的所有页面标题:

// pass the book you want in as a parameter, viewbag, etc.
using (var dbContext = new BookContext())
{
    var bookPages = dbContext.BookPages
        .Include(p => p.Book)
        .Include(p => p.PageTitle)
        .Where(p => p.Book.BookId == myBookId)
        .Select(p => new { 
            Bookid = p.Book.BookId,
            Text = p.Book.Text,
            PageNumber = p.Number,
            PageTitle = p.PageTitle.Title
        });
}

答案 2 :(得分:1)

我会建立这样的模型:

    public class Book
    {
        // a property "Id" or ClassName + "Id" is treated as primary key. 
        // No annotation needed.
        public int BookId { get; set; }

        // without [StringLenth(123)] it's created as NVARCHAR(MAX)
        [Required]
        public string Text { get; set; }

        // optionally if you need the pages in the book object:
        // Usually I saw ICollections for this usage.
        // Without lazy loading virtual is probably not necessary.
        public virtual ICollection<BookPage> BookPages { get; set; }
    }

    public class BookPage
    {
        public int BookPageId { get; set; }

        // With the following naming convention EF treats those two property as 
        // on single database column. This automatically corresponds
        // to ICollection<BookPage> BookPages of Books.
        // Required is not neccessary if "BookId" is int. If not required use int?
        // A foreign key relationship is created automatically. 
        // With RC2 also an index is created for all foreign key columns.
        [Required]
        public Book Book { get; set; }
        public int BookId { get; set; }

        [Required]
        public PageTitle PageTitle { get; set; }
        public int PageTitleId { get; set; }

        public int Number { get; set; }
    }

    public class PageTitle
    {
        public int PageTitleId { get; set; }

        // without StringLenth it's created as NVARCHAR(MAX)
        [Required]
        [StringLength(100)]
        public string Title { get; set; }
    }

由于BookPage中有Book的集合,因此在BookPage中创建了一个外键。在我的模型中,我在BookPage中明确地公开了这一点。而且不仅使用对象Book,还使用密钥BookId。创建的表格完全相同,但现在您可以在不使用BookId表的情况下访问Book

    using (var dbContext = new BookContext())
    {
        var pageTitles = dbContext.BookPages
            .Include(p => p.PageTitle)
            .Where(p => p.BookId == myBookId)
            .Select(p => p.PageTitle);
    }

我建议激活日志记录或使用分析器来检查实际执行的SQL语句。

关于@bilpor的评论: 我发现我不需要很多DataAnnotations,几乎没有流畅的API映射。如果使用指定的命名约定,则会自动创建主键和外键。对于外键关系,如果我在同一两个类上有两个外键关系,我只需要集合[InverseProperty()]。目前,我只对复合主键(m:n表)使用了流畅的API映射,并在TPH结构中定义了鉴别器。

提示: 目前,EF Core存在漏洞导致客户端对约束的评估。

.Where(p => p.BookId == myBookId)  // OK 
.Where(p => p.BookId == myObject.BookId) // client side 
.Where(p => p.BookId == myBookIdList[0]) // client side 

使用Contains()并且混合可空和非可空数据类型时也是如此。

.Where(p => notNullableBookIdList.Contains(p.NullableBookId)) // client side 

答案 3 :(得分:1)

所给出的示例都不需要任何Include语句。如果您在查询结尾处使用select并且仍在使用诸如DbSet之类的IQueryable,则Entity Framework将执行所谓的“投影”,并将运行包含所有必需字段的查询自动。

例如,您的原始代码:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
        .Book
        .Include(x => x.Pages)
        .ThenInclude(x => x.PageTitle)//.ThenInclude(x => x.Select(y => y.PageTitle)) Shouldn't use in EF Core
        .SingleOrDefault(x => x.BookId == "some example id")
        .Pages
        .Select(x => x.PageTitle);
}

您可以这样重写:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
        .Book
        .Where(x => x.BookId == "some example id")
        .SelectMany(x => x.Pages.Select(y => y.PageTitle))
        .ToList();
}

以下是Entity Framework将采取的措施:

  1. 我们告诉实体框架,我们将查看书籍表中的条目
  2. 然后我们告诉Entity Framework我们只想要具有特定ID的书籍(当然应该只是一个记录)
  3. 从那里,我们告诉实体框架我们想要所有书籍页面的列表(再次,由于Where声明,这将只是一本书的页面)
  4. 然后我们告诉Entity Framework我们只需要每页的PageTitle
  5. 最后,我们告诉实体框架使用我们刚刚提供的所有信息来生成查询并执行它
  6. 如果您想了解Entity Framework如何执行它所做的事情,那么最后一步是至关重要的一步。在您的示例中,当您调用SingleOrDefault时,您正在指示实体框架执行查询,这就是您需要包含的原因。在您的示例中,您实际上并未在运行查询时告知Entity Framework您需要这些页面,因此您必须使用Include手动请求它们。

    在我发布的示例中,您可以看到,当您运行查询时(ToList是触发查询执行的内容)Entity Framework从您的Select表达式中知道它将需要页面,以及他们的头衔。更好 - 这意味着实体框架甚至不会在它生成的SELECT语句中包含未使用的列。

    我强烈建议调查预测,这可能是我所知道的最好的方法,可以删除连续手动包含内容的要求。