2016-04-14 2 views
1

Давайте посмотрим на простых примерах класса:Как эффективно работать с ядром Entity Framework?

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 класса, но если бы у меня было сотни классов, вложенных в очень глубокий уровень, было бы очень медленно и неудобно работать.

Как именно я должен организовать работу с моей базой данных, чтобы избежать многих включений и избыточных запросов?

+0

Страницы в классной книге должны быть объявлены как виртуальная коллекция. PageTitle в bookPage должен быть объявлен int PageTitleId и должен иметь аннотацию данных ForeignKey. PageTitleId в PageTitle должен быть объявлен как int, а не строка ... как стартер. – bilpor

+0

Если вы не уверены в том, как работает EF. Сначала выполните БД. Создайте базу данных в базе данных. Убедитесь, что у вас установлены инструменты Powerivity для VS и обратного проектирования, указав на БД, и EF создаст для вас все классы и т. Д. – bilpor

+0

@bilpor, но, например [здесь] (https://ef.readthedocs.org/ru/latest/modeling/relationships.html), у нас есть список nly без какого-либо виртуального ключевого слова. И ссылается на [this] (https://ef.readthedocs.org/en/latest/modeling/relationships.html#single-navigation-property), нам не нужно определять внешние ключи. Я уверен, как это работает, я не уверен, как эффективно работать с ним. –

ответ

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: Я должен записать этот запрос несколько раз для различных идентификаторов.

Почему бы не просто реорганизовать это в метод с параметром bookId?

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

Итог - если вы оказываетесь написать то же самое много раз, что это прекрасная возможность реорганизовать свой код на более мелкие методы, которые могут быть повторно использованы.

+0

отличная идея с методами расширения, можем ли мы использовать их с IQueryable вместо DbContext? –

+0

@YuriyN. Конечно, вы можете создавать методы расширения практически для любого типа. –

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 
     }); 
} 
+0

У нас нет ленивой загрузки в Entity Framework Core, это проблема, только Include, которую мы можем использовать. Итак, ваш пример не работает. –

+0

ОК, попробуйте с помощью include(). Вот почему я придерживаюсь EF6 на данный момент :) –

+0

да, это работает, но не имеет значения с примером вопроса :) –

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()], если у меня было два отношения внешнего ключа на тех же двух классах. В настоящее время я использую только текущие сопоставления API для составных первичных ключей (таблицы m: n) и для определения дискриминатора в структуре 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(), и вы смешиваете типы NULL с нулевыми значениями.

.Where(p => notNullableBookIdList.Contains(p.NullableBookId)) // client side 
Смежные вопросы