Исключение при попытке сделать динамический вызов базы данных

У меня есть групповой класс, содержащий члены, которые могут относиться к разным типам классов.

Все остальные функции, связанные с этим, работают, мне не хватает только вызова базы данных, о котором у меня закончились идеи.

Мой код выглядит следующим образом:

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();
}

Что такое group и что это за MapEntity функция и что GetResourceType возвращает?

Charlieface 24.06.2024 14:03

@Charlieface group — это класс-контейнер для участников и идентификатора группы. MapEntity на самом деле не имеет значения для этого сценария, поскольку он используется для ответа. GetResourceType получает System.Type для типов членов.

Fakima 24.06.2024 14:13
Enumerable.SingleOrDefault статичен, поэтому... вам следует называть его так: dbSetMethod.Invoke(null, new object?[] { dynamicContext, func });
Selvin 24.06.2024 14:39

также версия, отличная от Enumerable.SingleOrDefault, принимает выражение в качестве второго параметра... я считаю, что вам лучше использовать Queryable.SingleOrDefault

Selvin 24.06.2024 14:48

@Selvin Я получил информацию отсюда и мне кажется, что она требует функции.

Fakima 24.06.2024 14:51

Кроме того, я попробовал ваше предложение, и оно дало мне исключение несоответствия количества параметров.

Fakima 24.06.2024 14:52
Func<...> нет Expression<Func<...>>
Selvin 24.06.2024 14:53

Это потому, что вы пытаетесь использовать FirstOrDefault<TSource>(IEnumerable<TSource>)

Selvin 24.06.2024 14:53

@Selvin Не могли бы вы привести мне пример того, как мне это сделать? - Еще я добавил метод, который делает мой func.

Fakima 24.06.2024 14:57

Это всё неправильно... вам нужно скорее что-то вроде вот этого

Selvin 24.06.2024 15:08

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

Selvin 24.06.2024 15:21

Хорошо, вам нужно показать эти функции или рассказать нам, каков результат каждой функции.

Charlieface 24.06.2024 15:32

Также вы используете SQL Server, Postgres или что-то еще?

Charlieface 24.06.2024 15:38

@Selvin Ваш пример сработал отлично !! К сожалению, я не могу пометить ваш комментарий как ответ, но большое спасибо!!

Fakima 24.06.2024 15:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
14
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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));    

Интересный! - Не могли бы вы дополнить свой ответ примером того, как вы могли бы его улучшить?

Fakima 24.06.2024 15:55

Улучшить что? Вы имеете в виду ТВП? Трудно сказать, не зная, какую СУБД вы используете, и вы не отметили ею свой вопрос.

Charlieface 24.06.2024 15:57

Мы используем T-SQL, сразу обновлю теги!

Fakima 24.06.2024 15:59

Улучшение заключается в том, что у вас будет один динамический вызов, а не вся цепочка. ... так что будет меньше шансов использовать неправильный тип, как вы сделали fx с .MakeGenericMethod(expressionType);

Selvin 24.06.2024 16:00

@Fakima Посмотреть новые правки

Charlieface 24.06.2024 16:14

Другие вопросы по теме