У меня есть групповой класс, содержащий члены, которые могут относиться к разным типам классов.
Все остальные функции, связанные с этим, работают, мне не хватает только вызова базы данных, о котором у меня закончились идеи.
Мой код выглядит следующим образом:
response = group.MapEntity<GroupDataResponse>(); //Irrelevant for this question (kept due to comment about it).
var resourceType = group.GetResourceType();
var functionType = typeof(Func<,>).MakeGenericType(typeof(IGroupMember), typeof(bool));
var expressionType = typeof(Expression<>).MakeGenericType(functionType);
var contextSetMethod = _context.GetType().GetMethod("Set", genericParameterCount: 1, Array.Empty<Type>())!.MakeGenericMethod(resourceType);
var dbSetMethod = typeof(Enumerable).GetMethods().FirstOrDefault(x => x.Name == "SingleOrDefault")!.MakeGenericMethod(expressionType);
var dynamicContext = contextSetMethod.Invoke(_context, null);
foreach (var member in group.Members)
{
var func = BuildExpression(member.MemberId);
var result = dbSetMethod.Invoke(dynamicContext, new object?[] { func });
if (result is null || result.GetType() != resourceType)
throw new Exception($"Something went wrong!!!!! {result}");
members.Add((IGroupMember)result);
}
response.Members = members;
Исключение происходит dbSetMethod.Invoke(...)
Исключение:
System.ArgumentException: объект типа «System.Linq.Expressions.Expression1»1[System.Func'2[Trasolu.Domain.Common.IGroupMember,System.Boolean]]» невозможно преобразовать в тип «System.Collections.Generic. IEnumerable'1[System.Linq.Expressions.Expression'1[System.Func`2[Trasolu.Domain.Common.IGroupMember,System.Boolean]]]'.
Я хочу сделать динамический вызов базы данных, func
в цикле — это лямбда-выражение, которое я хочу запустить на dbSetMethod
, чтобы убедиться, что я получаю только объект с заданным идентификатором.
Это моя функция, которую я пытаюсь запустить.
private Func<IGroupMember, bool> BuildExpression(int id)
{
Expression<Func<IGroupMember, int>> queryFunction = x => x.Id;
var paramExpr = Expression.Parameter(typeof(IGroupMember));
var varExpr = Expression.Constant(id, typeof(int));
var equalExpr = Expression.Equal(
left: Expression.Invoke(queryFunction, paramExpr),
right: varExpr);
var lambda = Expression.Lambda<Func<IGroupMember, bool>>(equalExpr, paramExpr);
return lambda.Compile();
}
@Charlieface group
— это класс-контейнер для участников и идентификатора группы. MapEntity
на самом деле не имеет значения для этого сценария, поскольку он используется для ответа. GetResourceType
получает System.Type
для типов членов.
Enumerable.SingleOrDefault
статичен, поэтому... вам следует называть его так: dbSetMethod.Invoke(null, new object?[] { dynamicContext, func });
также версия, отличная от Enumerable.SingleOrDefault
, принимает выражение в качестве второго параметра... я считаю, что вам лучше использовать Queryable.SingleOrDefault
@Selvin Я получил информацию отсюда и мне кажется, что она требует функции.
Кроме того, я попробовал ваше предложение, и оно дало мне исключение несоответствия количества параметров.
Func<...>
нет Expression<Func<...>>
Это потому, что вы пытаетесь использовать FirstOrDefault<TSource>(IEnumerable<TSource>)
@Selvin Не могли бы вы привести мне пример того, как мне это сделать? - Еще я добавил метод, который делает мой func
.
Это всё неправильно... вам нужно скорее что-то вроде вот этого
Вы не хотите делать это с помощью Func
... так как это будет означать, что вам нужно загрузить всю таблицу клиенту и отправить ее на сторону клиента... что ужасно
Хорошо, вам нужно показать эти функции или рассказать нам, каков результат каждой функции.
Также вы используете SQL Server, Postgres или что-то еще?
@Selvin Ваш пример сработал отлично !! К сожалению, я не могу пометить ваш комментарий как ответ, но большое спасибо!!
EF Core не поддерживает анализ произвольных скомпилированных функций. Он может анализировать только обычные деревья выражений.
Сначала вам нужно создать выражение обычным способом. Самый простой способ сделать это — использовать дженерики.
private Expression<Func<T, bool>> BuildExpression<T>(int id) where T : IGroupMember
{
Expression<Func<IGroupMember, bool>> queryFunction = x => x.Id == id;
return queryFunction;
}
Затем вы можете использовать другую функцию для ее вызова. При желании вы также можете объединить эти две функции в одну. Обратите внимание, что выражение не компилируется и не перекомпоновывается, оно передается непосредственно в SingleOrDefault
.
private static IGroupMember GetMember<T>(DbContext dbContext, int id) where T : IGroupMember
{
return dbContext.Set<T>().SingleOrDefault(BuildExpression(id));
}
Наконец, вызовите эту функцию динамически. Теперь вам нужно создать только один динамический вызов, а не всю цепочку.
var func = this.GetType()
.GetMethod(nameof(GetMember), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(resourceType)
.CreateDelegate<Func<DbContext, int, IGroupMember>>(this);
foreach (var member in group.Members)
{
var result = func.Invoke(_context, member.MemberId);
members.Add(result);
}
response.Members = members;
Однако обратите внимание, что подобный цикл неэффективен. Вероятно, для базы данных будет лучше, если вы просто дадите ей список в параметре, имеющем значение таблицы или аналогичном, а затем объедините его, чтобы получить несколько результатов.
Например, на SQL Server вы можете создать тип таблицы. Полезно иметь несколько стандартных одно- и двухколоночных типов общих столбцов.
CREATE TYPE dbo.IntList (Value int PRIMARY KEY);
Затем создайте DataTable
и вставьте его в качестве TVP через FromSql
. Обратите внимание, что FromSql
нельзя вставить в один запрос LINQ, вам нужно создать его отдельно и впоследствии составить его.
var dt = new DataTable { Columns = {
{ "Id", typeof(int) },
} };
foreach (var member in group.Members)
dt.Rows.Add(member.MemberId);
var sqlParam = new SqlParameter("@ids", dt) { TypeName = "dbo.IntList" };
var tvp = _context.Database.SqlQuery<int>($"SELECT Value FROM {sqlParam}");
// must keep these two parts separate, do not compose in one query
var results = _context.Set<T>()
.Where(x => tvp.Any(id => id == x.Id));
Интересный! - Не могли бы вы дополнить свой ответ примером того, как вы могли бы его улучшить?
Улучшить что? Вы имеете в виду ТВП? Трудно сказать, не зная, какую СУБД вы используете, и вы не отметили ею свой вопрос.
Мы используем T-SQL, сразу обновлю теги!
Улучшение заключается в том, что у вас будет один динамический вызов, а не вся цепочка. ... так что будет меньше шансов использовать неправильный тип, как вы сделали fx с .MakeGenericMethod(expressionType);
@Fakima Посмотреть новые правки
Что такое
group
и что это заMapEntity
функция и чтоGetResourceType
возвращает?