EF Core Найти метод эквивалентно для нескольких записей? - PullRequest
0 голосов
/ 03 июня 2018

EF Core DbSet имеет метод с именем Find, который:

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

Мне нужно вернуть несколько элементов на основе заданного массива значений первичного ключа, все в одном запросе, конечно.Есть ли способ сделать это в EF Core?

Обновление : я знаю, что могу использовать предложение Where в обычных сценариях.Но я создаю вспомогательную утилиту, которая является общей, и в ней у меня нет доступа к строго типизированным свойствам моей модели.Таким образом, я не могу использовать предложение Where(x => ids.Contains(x.Id)).

Обновление 2 : желательный метод может иметь простую подпись, которая получает список значений long и возвращает список T.public static List<T> FindSet(List<long> ids), который можно использовать так:

var foundRecords = dbset.FindSet(new List<long> { 5, 17, 93, 178, 15400 });

Ответы [ 2 ]

0 голосов
/ 05 июня 2018

Как упомянуто в комментариях, использование Find наивным способом (например, циклически просматривая все значения ключей) приведет к выполнению запроса для каждого отдельного значения, так что это не то, что вы хотели бы сделать.Правильное решение - использовать запрос Where, который выбирает все элементы одновременно.Проблема здесь в том, что вам нужно динамически запрашивать это для первичного ключа.

Конечно, сам контекст базы данных знает , что является первичным ключом для данного типа сущности.Внутренний способ Find заключается в том, что он использует эту информацию для построения динамического запроса, в котором проверяется равенство первичного ключа.Поэтому, чтобы получить FindAll, нам нужно сделать то же самое.

Ниже приведено быстрое решение для этого.Это в основном создает запрос dbSet.Where(e => keyValues.Contains(e.<PrimaryKey>)) для вас.

Обратите внимание, что, как я его создаю, он работает только для одного первичного ключа для каждого типа объекта.Если вы попытаетесь использовать его с составными ключами, он выдаст NotSupportedException.Вы абсолютно можете расширить это, хотя бы добавить поддержку составных ключей;Я просто не делал этого, потому что это все усложняет (особенно если вы не можете использовать Contains тогда).

public static class DbContextFindAllExtensions
{
    private static readonly MethodInfo ContainsMethod = typeof(Enumerable).GetMethods()
        .FirstOrDefault(m => m.Name == "Contains" && m.GetParameters().Length == 2)
        .MakeGenericMethod(typeof(object));

    public static Task<T[]> FindAllAsync<T>(this DbContext dbContext, params object[] keyValues)
        where T : class
    {
        var entityType = dbContext.Model.FindEntityType(typeof(T));
        var primaryKey = entityType.FindPrimaryKey();
        if (primaryKey.Properties.Count != 1)
            throw new NotSupportedException("Only a single primary key is supported");

        var pkProperty = primaryKey.Properties[0];
        var pkPropertyType = pkProperty.ClrType;

        // validate passed key values
        foreach (var keyValue in keyValues)
        {
            if (!pkPropertyType.IsAssignableFrom(keyValue.GetType()))
                throw new ArgumentException($"Key value '{keyValue}' is not of the right type");
        }

        // retrieve member info for primary key
        var pkMemberInfo = typeof(T).GetProperty(pkProperty.Name);
        if (pkMemberInfo == null)
            throw new ArgumentException("Type does not contain the primary key as an accessible property");

        // build lambda expression
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = Expression.Call(null, ContainsMethod,
            Expression.Constant(keyValues),
            Expression.Convert(Expression.MakeMemberAccess(parameter, pkMemberInfo), typeof(object)));
        var predicateExpression = Expression.Lambda<Func<T, bool>>(body, parameter);

        // run query
        return dbContext.Set<T>().Where(predicateExpression).ToArrayAsync();
    }
}

Использование выглядит так:

// pass in params
var result = await dbContext.FindAllAsync<MyEntity>(1, 2, 3, 4);

// or an object array
var result = await dbContext.FindAllAsync<MyEntity>(new object[] { 1, 2, 3, 4 });

Я также добавил некоторую базовую проверку, чтобы такие вещи, как context.FindAllAsync<MyEntity>(1, 2, "foo"), не сработали раньше.

0 голосов
/ 03 июня 2018

Если вы хотите создать универсальный метод поиска, который находит все строки, соответствующие списку первичных ключей, вы можете добиться этого, унаследовав эти типы сущностей от базового класса, в котором они имеют одинаковое имя для столбца первичного ключа.Подумайте об этом так: как поведет себя этот метод, если ваша сущность (таблица базы данных) имеет составной ключ?Так что, если вы можете соответствовать этому типу дизайна, следующая реализация демонстрирует простую логику для достижения этого с .NET Core.(На самом деле, вы можете добиться того же поведения и с EF6)

public class MyBaseEntity
{
    public int Id { get; set; }
}

public class MyTable : MyBaseEntity
{
    public string MyProperty { get; set; }
}

public static class RepositoryExtensions
{
    public static IQueryable<T> FindMacthes<T>(this DbContext db, IEnumerable<int> keys)
        where T : MyBaseEntity
        => db.Set<T>().Where(x => keys.Contains(x.Id));

}

class Program
{
    static void Main(string[] args)
    {
        // Initialize your own DbContext.
        var db = new DbContext(null);
        // Usage:
        var lookupKeys = new[] { 1, 2, 3 };
        var results = db.FindMacthes<MyTable>(lookupKeys).ToList();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...