C # Entity-Framework: Як я можу поєднати a .ind і .lude на модельному об'єкті?


145

Я роблю навчальний посібник з практики mvcmusicstore. Щось я помітив під час створення ешафоту для менеджера альбомів (додати видалити редагування).

Я хочу писати код елегантно, тому шукаю чистий спосіб написати це.

FYI, я роблю магазин більш загальним:

Альбоми = Елементи

Жанри = Категорії

Виконавець = Бренд

Ось як отримують індекс (генерується MVC):

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

Ось спосіб отримання елемента для видалення:

Item item = db.Items.Find(id);

Перший повертає всі елементи та заповнює категорії та моделі бренда всередині моделі товару. Другий - не містить категорії та бренду.

Як я можу написати другий, щоб зробити знаходження І заповнити те, що знаходиться всередині (бажано, в 1 рядок) ... теоретично - щось на зразок:

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

Якщо комусь потрібно зробити це в загальному in.net-core, дивіться мою відповідь
johnny 5

Відповіді:


162

Include()Спочатку потрібно скористатися , а потім отримати один об'єкт із отриманого запиту:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
Я б дуже рекомендував використовувати останню (SingleOrDefault), ToList спочатку отримає всі записи, а потім вибере один
Sander Rijken

5
Це руйнується, якщо у нас є складений первинний ключ і використовуємо відповідне перевантаження знаходження.
jhappoldt

78
Це працювало б, але є різниця між використанням "Знайти" та "SingleOrDefault". Метод "Знайти" повертає об'єкт з локального відстежуваного магазину, якщо він існує, уникаючи зворотної поїздки до бази даних, коли використання "SingleOrDefault" все одно змусить запит до бази даних.
Іраванчі

3
@Iravanchi правильний. Це, можливо, спрацювало для користувача, але операція та її побічні ефекти не є рівнозначними Find, наскільки я знаю.
mwilson

3
Насправді не відповідає на операційне запитання, оскільки він не використовує. Назад
Пол Швець

73

Відповідь Денніса використовує Includeі SingleOrDefault. Останній переходить до баз даних.

Альтернативою є використання Findв поєднанні з Loadдля явного завантаження пов'язаних об'єктів ...

Нижче прикладу MSDN :

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

Звичайно, Findповертається негайно, не надсилаючи запит у магазин, якщо ця сутність вже завантажена контекстом.


30
Цей метод використовує Findтак, якщо сутність присутня, немає жодного зворотного звернення до БД для самої сутності. АЛЕ, у вас буде зворотній шлях для кожного з ваших стосунків Load, тоді як SingleOrDefaultкомбінація Includeзавантажує все за один раз.
Іраванчі

Коли я порівнював 2 у програмі SQL, пошук / завантаження був кращим для мого випадку (у мене було відношення 1: 1). @Iravanchi: ти маєш на увазі сказати, якби я мав відношення 1: m, це називало б m разів крамницею? ... тому що не мало б такого сенсу.
Учень

3
Не 1: m відношення, а множинні відносини. Кожен раз, коли ви викликаєте Loadфункцію, відношення має бути заповнене, коли дзвінок повертається. Тож якщо ви зателефонуєте Loadкілька разів на декілька відносин, кожен раз відбуватиметься туди і назад. Навіть для одного відношення, якщо Findметод не знаходить сутність у пам’яті, він здійснює два зворотні подорожі: один за Findі другий для Load. Але Include. SingleOrDefaultпідхід виявляє сутність та відносини за один раз, наскільки я знаю (але я не впевнений)
Iravanchi

1
Було б добре, якби могли якось слідувати дизайну Включити, а не по-різному ставитися до колекцій та посилань. Це ускладнює створення фасаду GetById (), який просто приймає необов'язкову колекцію Expression <Func <T, object >> (наприклад, _repo.GetById (id, x => x.MyCollection))
Дерек



0

Не працював для мене. Але я вирішив це, зробивши так.

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

Не знаю, чи це нормальне рішення. Але інший, який Денніс дав, дав мені помилку в "булінгу" .SingleOrDefault(x => x.ItemId = id);


4
Рішення Денніса теж має спрацювати. Можливо, у вас є ця помилка SingleOrDefault(x => x.ItemId = id)лише через неправильний сингл =замість подвійного ==?
Слаума

6
так, схоже, що ти використовував = not == Помилка синтаксису;)
Ральф N

Я спробував їх обидва == і = все одно дав мені помилку в .SingleOrDefault (x => x.ItemId = id); = / У моєму коді повинно бути щось інше, що неправильно. Але те, що я зробив - це поганий спосіб? Можливо, я не розумію, що ти маєш на увазі, що Денніс має сингел = у своєму коді.
Йоган

0

Немає реального простого способу відфільтрувати знахідку. Але я придумав близький спосіб повторити функціонал, але, будь ласка, врахуйте кілька речей для мого рішення.

Це рішення дозволяє фільтрувати загально, не знаючи первинного ключа в .net-core

  1. Пошук принципово відрізняється тим, що він отримує сутність, якщо він присутній у відстеженні перед запитом у базі даних.

  2. Крім того, він може фільтрувати об'єкт, щоб користувач не знав первинного ключа.

  3. Це рішення призначене для EntityFramework Core.

  4. Для цього потрібен доступ до контексту

Ось кілька методів розширення, додати які допоможуть вам відфільтрувати за первинним ключем

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

Після використання цих методів розширення ви можете фільтрувати так:

query.FilterByPrimaryKey(this._context, id);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.