#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 Вы не возражаете? 🙂 Хотя я не беспокоюсь о производительности, поскольку это метод, который будет вызываться только при выполнении миграции, я действительно заинтересован в просмотре лучшего подхода, чем этот 🙂