Лямбда-выражение с двумя входными параметрами к лямбда-выражению с одним входным параметром

#c# #linq #generics #lambda

#c# #linq #обобщения #лямбда

Вопрос:

У меня есть метод расширения, который имеет следующую подпись:

 public static class GenericSeeder
{
    public static void Seed<TSeed, TEntity>(this DbContext context, IEnumerable<TSeed> seeds, Expression<Func<TEntity, TSeed, bool>> predicate)
    {
        // code that I'm looking for goes here
    }
}
  

Чтобы лучше понять, что делает метод, вот как его следует использовать:

 context.Seed<SeedClass, EntityClass>(seeds, (entity, seed) => entity.Name == seed.OtherProperty);
  

Итак, по сути, я использую предикат, чтобы проверить, было ли начальное значение уже применено. Однако, чтобы выполнить проверку, я должен использовать Where или FirstOrDefault из Linq to Entities, который принимает следующее в качестве параметра:

 Expression<Func<TEntity, bool>> predicate
  

Итак, мое лямбда-выражение является функцией 2 входных параметров (TSeed, TEntity) и 1 выходного параметра (bool). Мне нужно выполнить итерацию по предоставленной коллекции объектов TSeed, и для каждого из них использовать этот объект в качестве параметра для моего лямбда-выражения, чтобы сгенерировать лямбда-выражение LINQ 2 Entities, которое имеет 1 входной параметр (TEntity) и 1 выходной параметр (bool).

Есть ли способ выполнить частичный вызов лямбда-выражения / функции, чтобы получить другое лямбда-выражение / функцию?

Комментарии:

1. Хотя я думаю, что знаю, о чем вы просите, я не уверен на 100%. Вы можете выполнять лямбды с несколькими операторами, используя (x,y)=>{x.Frob();return y==x;} что-то вроде или что-то в этом роде (скомпилировано нормально в моей голове, возможно, не с помощью реального компилятора). Это позволило бы вам внутренне выполнять работу только с одним свойством, а затем с другим. Я не могу гарантировать, что любой поставщик linq, кроме linq to objects, сможет что-то с этим делать (и, действительно, ожидал бы, вероятно, нет).

2. Сравнение в предикате всегда будет == ?

Ответ №1:

Благодаря использованию LinqKit, позволяющего вызывать выражения таким образом, чтобы они были преобразованы в другие выражения, реализация вашего метода становится довольно тривиальной:

 public static IQueryable<TEntity> Seed<TSeed, TEntity>(
    this DbContext context,
    IEnumerable<TSeed> seeds,
    Expression<Func<TEntity, TSeed, bool>> predicate)
{
    return context.Set<TEntity>()
            .AsExpandable()
            .Where(entity => seeds.Any(seed => predicate.Invoke(entity, seed)));
}
  

Комментарии:

1. Извините, но, хотя ваш ответ имеет значение (таким образом, повышающий голос) для конкретных сценариев, для моего сценария из-за остальной реализации он не является более оптимальным. Возможно, вы экономите время на преобразовании выражения в T-SQL, но, с другой стороны, мне пришлось бы снова перебирать результаты вашего запроса и находить соответствующий объект для каждого из начальных значений (поскольку вы хватаете все сразу, а запрос становится потенциально безумно большим, если используется слишком много начальных значений).

2. @BarisaPuter Вы можете легко сгруппировать объекты по начальному значению, если ваш метод принимает соответствующую проекцию в качестве параметра или просто ожидая, что вызывающий объект сгруппирует начальные значения с учетом выходных данных. Усилия, затраченные на группировку значений в любом случае, не особенно велики. Если у вас много начальных значений, то выполнение большого количества обходов БД для извлечения их всех является большой проблемой. Каждый из этих обходов будет очень дорогим.

Ответ №2:

Я понятия не имею, что вы делаете, но именно так вы выполняете частичное применение на c#:

 Func<int,bool, string> twoInFunc= (int a, bool b) => a.ToString()   b.ToString();

int number = 7;

Func<bool, string> oneInFunc= (bool b) => twoInFunc(number,b);
  

Комментарии:

1. К сожалению, ваше решение мне не помогает, поскольку мне требуется преобразование выражения из-за требований объектов Linq 2. Если я попробую то, что вы предлагаете, я получу: тип узла выражения LINQ ‘Invoke’ не поддерживается в LINQ to Entities.

Ответ №3:

Мне удалось создать решение самостоятельно. Однако я использовал печально известную библиотеку расширений LinqKit и ее метод расширения AsExpandable() .

LinqKit можно найти здесь: ссылка на NuGet

Итак, это реализация, которая работает с объектами Linq 2:

 public static void Seed<TSeed, TEntity>(this DbContext context, IEnumerable<TSeed> seeds, Expression<Func<TEntity, TSeed, bool>> predicate)
            where TEntity : class
            where TSeed : class
        {
            foreach (TSeed seed in seeds)
            {
                Expression<Func<TEntity, bool>> matchExpression = (entity) => predicate.Invoke(entity, seed);
                TEntity existing = context.Set<TEntity>().AsExpandable().FirstOrDefault(matchExpression);

            // Rest of code is omitted as it is not related to the original question.
            // The query above is properly executed by Linq 2 Entities.
            }
        }
  

Комментарии:

1. Это вычисляет запрос до значения для каждого отдельного начального значения. Вы не должны этого делать.

2. @Servy Можете ли вы предложить лучшее решение?

3. Да, я могу предложить лучшее решение.

4. @Servy Вы не возражаете? 🙂 Хотя я не беспокоюсь о производительности, поскольку это метод, который будет вызываться только при выполнении миграции, я действительно заинтересован в просмотре лучшего подхода, чем этот 🙂