DbSet не имеет метода Find в EF7

Я пытаюсь создать общий репозиторий для доступа к моей базе данных. В EF6 я смог сделать это, чтобы получить определенный объект:

protected IDbSet<T> dbset;

public T Get(object id)
{
    return this.dbset.Find(id);
}

В DbSet в EF7 отсутствует метод Find. Есть ли способ реализовать приведенный выше фрагмент кода?


person Lyudmil Dimitrov    schedule 13.03.2015    source источник
comment
Это отслеживается здесь: github.com/aspnet/EntityFramework/issues/797.   -  person Nikolay Kostov    schedule 26.07.2016
comment
НОВОСТИ: сегодня в EF Core 1.1.0 добавлен метод Find.   -  person Gerardo Grignoli    schedule 18.11.2016


Ответы (11)


Вот очень грубая, неполная и непроверенная реализация .Find() в качестве метода расширения. Если ничего другого, это должно указать вам правильное направление.

Реальная реализация отслеживается #797.

static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues)
    where TEntity : class
{
    var context = ((IAccessor<IServiceProvider>)set).Service.GetService<DbContext>();

    var entityType = context.Model.GetEntityType(typeof(TEntity));
    var key = entityType.GetPrimaryKey();

    var entries = context.ChangeTracker.Entries<TEntity>();

    var i = 0;
    foreach (var property in key.Properties)
    {
        var keyValue = keyValues[i];
        entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValue);
        i++;
    }

    var entry = entries.FirstOrDefault();
    if (entry != null)
    {
        // Return the local object if it exists.
        return entry.Entity;
    }

    // TODO: Build the real LINQ Expression
    // set.Where(x => x.Id == keyValues[0]);
    var parameter = Expression.Parameter(typeof(TEntity), "x");
    var query = set.Where((Expression<Func<TEntity, bool>>)
        Expression.Lambda(
            Expression.Equal(
                Expression.Property(parameter, "Id"),
                Expression.Constant(keyValues[0])),
            parameter));

    // Look in the database
    return query.FirstOrDefault();
}
person bricelam    schedule 16.03.2015
comment
Привет Брайс, просто любопытно, есть ли у вас обновленная версия этого. Я застрял на линии var entityType = context.Model.GetEntityType(typeof(TEntity));; поскольку контекст не имеет свойства Model. Я изменил var context = ((IAccessor<IServiceProvider>)set).Service.GetService<DbContext>(); на var context = ((IAccessor<IServiceProvider>)set).Service.GetService(typeof(DbContext)); - person Lynn Crumbling; 16.07.2015
comment
@LynnCrumbling Я не думаю, что Model сдвинулся с тех пор, как мы его туда положили; это все еще в ночных сборках. Чтобы получить общую версию GetService, добавьте using Microsoft.Framework.DependencyInjection; - person bricelam; 16.07.2015
comment
Большое спасибо! Я нашел общий IAccessor в Microsoft.Data.Entity.Infrastructure, но это имеет гораздо больше смысла, поскольку мне нужно использовать GetService(), который возвращает больше, чем просто объект. Ваше здоровье! - person Lynn Crumbling; 16.07.2015
comment
Если ваше поле идентификатора не называется Id, измените Expression.Property(parameter, "Id"), на Expression.Property(parameter, key.Properties[0].Name),. - person Sean; 08.01.2016
comment
Я добавил Microsoft.Data.Entity.Infrastructure и изменил строку на var context = ((IInfrastructure‹IServiceProvider›)set).Service.GetService‹DbContext›();. Тем не менее у меня есть ошибки: «DbSet‹TEntity›» не содержит определения для «Service» и не может быть найден метод расширения «Service», принимающий первый аргумент типа «DbSet‹TEntity›» (вы пропустили директиву using или ссылка на сборку?) и «IModel» не содержит определения для «GetEntityType», и не может быть найден метод расширения «GetEntityType», принимающий первый аргумент типа «IModel» (вам не хватает... - person Jaryn; 16.03.2016
comment
@LynnCrumbling Я не смог найти универсальный IAccessor в Microsoft.Data.Entity.Infrastructure или где-либо еще, если на то пошло. Я пропустил какую-то ссылку? - person Shaul Behr; 20.04.2016

Если вы используете EF 7.0.0-rc1-final, ниже вы найдете небольшое обновление кода, представленного @bricelam в предыдущем ответе. Кстати, большое спасибо @bricelam - ваш код был для меня чрезвычайно полезен.

Вот мои зависимости в "project.config":

"dependencies": {
    "EntityFramework.Commands": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta8",
    "Microsoft.Framework.ConfigurationModel": "1.0.0-beta4",
    "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4",
    "Microsoft.Framework.DependencyInjection": "1.0.0-beta8"
}

Ниже приведен метод расширения для DbSet.Find(TEntity):

using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Microsoft.Data.Entity.Extensions
{
    public static class Extensions
    {
        public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
        {
            var context = ((IInfrastructure<IServiceProvider>)set).GetService<DbContext>();

            var entityType = context.Model.FindEntityType(typeof(TEntity));
            var key = entityType.FindPrimaryKey();

            var entries = context.ChangeTracker.Entries<TEntity>();

            var i = 0;
            foreach (var property in key.Properties)
            {
                entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
                i++;
            }

            var entry = entries.FirstOrDefault();
            if (entry != null)
            {
                // Return the local object if it exists.
                return entry.Entity;
            }

            // TODO: Build the real LINQ Expression
            // set.Where(x => x.Id == keyValues[0]);
            var parameter = Expression.Parameter(typeof(TEntity), "x");
            var query = set.Where((Expression<Func<TEntity, bool>>)
                Expression.Lambda(
                    Expression.Equal(
                        Expression.Property(parameter, "Id"),
                        Expression.Constant(keyValues[0])),
                    parameter));

            // Look in the database
            return query.FirstOrDefault();
        }
    }
}
person Roger Santana    schedule 10.12.2015
comment
Спасибо, это то, что мне было нужно :) - person devqon; 17.12.2015
comment
Предложение: у вас есть захват модифицированного замыкания, что делает этот перерыв. Вы должны изменить цикл foreach на: var i1 = i; entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i1]);i++ - person Shaul Behr; 20.04.2016

Не могу комментировать из-за репутации, но если вы используете RC2 (или более позднюю версию?), вы должны использовать

var context = set.GetService<ICurrentDbContext>().Context;

вместо

var context = set.GetService<DbContext>();
person Ilpo Toni    schedule 07.06.2016
comment
Даже если это кажется грязным взломом с использованием чего-то, что мне не кажется нужным (ICurrentDbContext), он отлично работает внутри родительского грязного взлома. И это ТОЛЬКО там, где вы должны были поместить ответ, а не в комментарии. Это фантастика. - person damccull; 29.06.2016
comment
Это исправило это для меня, что мне было нужно, пока поиск не будет правильно реализован в EF7. - person Martin Dawson; 01.08.2016

Я взял некоторые из ранее предоставленных ответов и изменил их, чтобы исправить пару проблем:

  • Неявно захваченное закрытие
  • Ключ не должен быть жестко запрограммирован на "Id"

    public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
    {
        var context = set.GetService<DbContext>();
    
        var entityType = context.Model.FindEntityType(typeof(TEntity));
        var key = entityType.FindPrimaryKey();
    
        var entries = context.ChangeTracker.Entries<TEntity>();
    
        var i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i1]);
            i++;
        }
    
        var entry = entries.FirstOrDefault();
        if (entry != null)
        {
            // Return the local object if it exists.
            return entry.Entity;
        }
    
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var query = set.AsQueryable();
        i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            query = query.Where((Expression<Func<TEntity, bool>>)
             Expression.Lambda(
                 Expression.Equal(
                     Expression.Property(parameter, property.Name),
                     Expression.Constant(keyValues[i1])),
                 parameter));
            i++;
        }
    
        // Look in the database
        return query.FirstOrDefault();
    }
    
person Shaul Behr    schedule 20.04.2016

Найти наконец-то прибыл в ядро ​​Entity Framework.

person Sebastian Busek    schedule 17.06.2016
comment
Прохладный! Но как я могу добавить его в проект? Кажется, нет обновлений на nuget... - person Pavel Biryukov; 17.06.2016
comment
Тесты не проходят, после успешной сборки он перемещается на этап, а затем появляется в nuget, я гость. - person Sebastian Busek; 17.06.2016
comment
@PavelBiryukov Вы должны получить пререлиз из их каналов MyGet. - person Shaul Behr; 29.07.2016
comment
@ShaulBehr, как я могу установить предварительную версию из их каналов MyGet? - person Kemal Tezer Dilsiz; 10.08.2016
comment
Я нашел эту ссылку, но команда PM не работает для моей визуальной студии dotnet.myget.org/feed/aspnetcore-dev/package/nuget/ - person Kemal Tezer Dilsiz; 10.08.2016

Итак... описанные выше методы поиска отлично сработали, но если в вашей модели нет столбца с именем "Id", все это приведет к сбою в следующей строке. Я не уверен, почему OP поместил в это место жестко запрограммированное значение.

  Expression.Property(parameter, "Id"),

Вот редакция, которая исправит это для тех, кто правильно называет наши столбцы идентификаторов. :)

var keyCompare = key.Properties[0].Name;

        // TODO: Build the real LINQ Expression
        // set.Where(x => x.Id == keyValues[0]);
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var query = set.Where((Expression<Func<TEntity, bool>>)
            Expression.Lambda(
                Expression.Equal(
                    Expression.Property(parameter, keyCompare),
                    //Expression.Property(parameter, "Id"),
                    Expression.Constant(keyValues[0])),
                parameter));

        // Look in the database
        return query.FirstOrDefault();
    }

Это ЕЩЕ вполне может потерпеть неудачу, если у вас есть более одной настройки ключа для объекта сущности, и ключ, который вы ищете, не является первым, но в этом случае он должен быть немного лучше.

person Bryan - S    schedule 10.02.2016

Недостаточно репутации для комментариев, но в ответе @Roger-Santana есть ошибка при использовании его в консольном приложении/отдельной сборке:

var i = 0;
foreach (var property in key.Properties)
{
    entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
    i++;
}
var entry = entries.FirstOrDefault();

Значение 'i' фиксируется в foreach, так что при вызове entry.FirstOrDefault() keyValues[i] имеет значение (по крайней мере) keyValues[i++], которое в моем случае вылетело из-за ошибки индекса . Исправление состояло бы в том, чтобы скопировать значение «i» через цикл:

var i = 0;
foreach (var property in key.Properties)
{
   var idx =i;
    entries = entries.Where(e =>  e.Property(property.Name).CurrentValue == keyValues[idx]);
    i++;
}
var entry = entries.FirstOrDefault();
person Paul Lawrence    schedule 26.03.2016
comment
Спасибо Спасибо спасибо. Когда я столкнулся с этой ошибкой, я не надеялся выяснить это. Мгновенно решил, и мне не пришлось использовать мой мозг. Очень признателен. - person BinarySolo; 25.08.2016

Я использую linq; вместо метода Find вы можете использовать:

var record = dbSet.SingleOrDefault(m => m.Id == id)
person A-Sharabiani    schedule 03.03.2016
comment
Это отличается от Find, Find может возвращать объекты, которые еще не были отправлены в базу данных, то есть до того, как вы вызовете SaveChanges. - person John Leidegren; 06.05.2017

Позвольте мне внести исправление, которое включает построение выражения. Признаюсь, я на самом деле не проверял это ;-)

    public static TEntity Find<TEntity>(this DbSet<TEntity> dbSet, params object[] keyValues) where TEntity : class
    {
        // Find DbContext, entity type, and primary key.
        var context = ((IInfrastructure<IServiceProvider>)dbSet).GetService<DbContext>();
        var entityType = context.Model.FindEntityType(typeof(TEntity));
        var key = entityType.FindPrimaryKey();

        // Build the lambda expression for the query: (TEntity entity) => AND( entity.keyProperty[i] == keyValues[i])
        var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
        Expression whereClause = Expression.Constant(true, typeof(bool));
        uint i = 0;

        foreach (var keyProperty in key.Properties) {
            var keyMatch = Expression.Equal(
                Expression.Property(entityParameter, keyProperty.Name),
                Expression.Constant(keyValues[i++])
            );

            whereClause = Expression.And(whereClause, keyMatch);
        }

        var lambdaExpression = (Expression<Func<TEntity,bool>>)Expression.Lambda(whereClause, entityParameter);

        // Execute against the in-memory entities, which we get from ChangeTracker (but not filtering the state of the entities).
        var entries = context.ChangeTracker.Entries<TEntity>().Select((EntityEntry e) => (TEntity)e.Entity);
        TEntity entity = entries.AsQueryable().Where(lambdaExpression).First(); // First is what triggers the query execution.

        // If found in memory then we're done.
        if (entity != null) { return entity; }

        // Otherwise execute the query against the database.
        return dbSet.Where(lambdaExpression).First();
    }
person sjb-sjb    schedule 29.03.2016

вот что я использую. Не метод поиска, но работает как шарм

var professionalf = from m in _context.Professionals select m;
professionalf = professionalf.Where(s => s.ProfessionalId == id);
Professional professional = professionalf.First();
person ditsikts    schedule 16.10.2015
comment
Я уверен, что ОП знает о First. Find во многом отличается. - person Gert Arnold; 16.10.2015

Было предложено изменить «.First()» на «.FirstOrDefault()» в самой последней строке моего предыдущего поста. Редактирование было отклонено, но я согласен с ним. Я ожидаю, что функция вернет null, если ключ не найден. Я бы не хотел, чтобы это вызывало исключение. В большинстве случаев я хотел бы знать, существует ли ключ в наборе, а обработка исключения — очень медленный способ выяснить это.

person sjb-sjb    schedule 17.05.2016