Выражение для получения LINQ с Contains в EF для SQL IN(), где свойство дочерних объектов равно значению

#c# #entity-framework #linq #entity-framework-core

#c# #entity-framework #linq #entity-framework-core

Вопрос:

У меня есть простая необходимость отфильтровать всех родителей из возвращаемой коллекции, где нет совпадения в поле, которое вызывается по имени из строки, не соответствует представленному значению. Что мне нужно, так это то, что если у parent объекта есть child object , и это child свойство objects "foo" (вызываемое string) не имеет или действительно равно значению bar , parent объект соответствующим образом фильтруется из коллекции.

Вот мой вызов linq ef

 var field = "bar";
var values = new List<string>{"foo","fuYu"};
var dataPage = _aim_context.ae_s_bld_c.AsNoTracking();
var result = dataPage.Where(x => 
                              DbHelper.byPropertyContains(x.udfs, field, values)
                           );
// NOTE `udfs` is a ONE-to-ONE with `ae_s_bld_c`
  

То, что я ищу, чтобы увидеть, это что-то вроде SQL из

 SELECT [m].[id],[m.udfs].[bar],
FROM [dbo].[ae_s_bld_c] AS [m]
    INNER JOIN [dbo].[ae_s_bld_c_udf] AS [m.udfs]
        ON ([m].[multitenant_id] = [m.udfs].[multitenant_id])
WHERE ([m].[multitenant_id] = 1.0)
    AND ([m.udfs].[bar] IN ('foo','fuYu')) --< Goal line
  

Способ, которым я подошел к этому, состоял в том, чтобы настроить выражение для получения List<string> и создания SQL. Я прочитал около 50 статей и сообщений SO, но не понял, почему я пока этого не понимаю, поскольку у всех, похоже, разные идеи, и большинство из них, похоже, не соответствуют dotnet core 2.1 .

Вот над чем я сижу в настоящее время после многих итераций. ПРИМЕЧАНИЕ: это немного отличается от того, что мне нужно, поскольку я предоставляю свой текущий след.

Мой текущий контекст linq try

 //...
dataPage = dataPage.Where(DbHelper.byPropertyContains<ae_s_bld_c>("udfs", field, values));
//...
  

Я думаю, было бы лучше, если бы это было похоже на первый пример, который я привел, но это было то, на чем я остановился, поскольку у меня было время сопоставить его с x=>x.udfs , как с x=> funName(x.udfs) , так и x=> x.udfs.funName()

Мой статический метод для построения выражения

 public static class DbHelper
{
    public static Expression<Func<T, bool>> byPropertyContains<T>(string node, string field, List<string> value) {
//trying to take parent item and get it's property by string name because
// doing the function in linq like x=>x.udfs was not working right
// but that is the prefered I think
        var property_parameter = Expression.Parameter(typeof(T), "x");
        var property = Expression.PropertyOrField(property_parameter, node);
        var selector_parameter = Expression.Parameter(property.Type, "y");
        var selector = Expression.PropertyOrField(selector_parameter, field);
        var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] {
            typeof(string)
        });
        var list = Expression.Constant(value, typeof(List<string>));
        var body = Expression.Call(methodInfo, list, selector);
        return Expression.Lambda<Func<T, bool>>(body, selector_parameter);
    }
}
  

Обновить

По запросу @NetMage я попытался работать в обратном направлении с LINQPad. Я думаю, что я близок, но трудно сказать с выводом. Я размещаю его здесь для справки. Чтобы было понятно, имя дочернего свойства будет строкой имени. Лучший результат — у меня могло бы быть имя типа udfs.foo , где я мог бы протестировать на любом уровне, содержат ли значения по имени строки, но на самом деле все в порядке, начиная с этого,

 var result = dataPage.Where(x => 
                              DbHelper.byPropertyContains(x.udfs, field, values)
                           );
  

вывод LINQPad

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

1. Я предлагаю получить LINQPad и сравнить ваши byPropertyContains результаты с тем, как выглядит созданный вручную лямбда-код (переведенный компилятором в Expression ).

2. В LINQPad выполните Expression<Func<ae_s_bld_c, bool>> f = x => values.Contains(x.udfs.bar); а затем f.Dump() и сравните выходные данные с результатом вашего метода сборки.

3. Насколько мне известно, не вы определили тип ae_s_bld_c , вы определили values ?

4. Извините, но я не понимаю, в чем здесь вопрос / проблема. Все, что я вижу, это желаемый SQL и ваш текущий код, но не то, что вы получаете с ним, и в чем проблема — например, SQL, исключение, что-то еще?

5. @IvanStoev Я не могу сообщить вам конкретную ошибку, потому что она будет опубликована только на текущей итерации. Я продолжаю пробовать новые итерации, поэтому я даже не нахожусь на этой версии, скажем. Это отвлекло бы от вопроса, в который я верю в любом случае. Проблема в том, что, судя по всему, я должен иметь возможность выполнять contains для дочернего объекта по имени свойства, но все статьи в SO или просто поиск в Google устарели или неполны, и я продолжаю упускать ключевой фактор, чтобы заставить его работать.

Ответ №1:

Давайте начнем отсюда. Вам нужен эквивалент чего-то подобного

 var result = dataPage.Where(x => values.Contains(x.udfs.{field}));
  

где field — строка, возвращающая свойство, динамически указанное по имени.

В EF Core вам даже не нужно иметь дело с построением выражений вручную, потому что EF Core предоставляет специальную переводимую функцию SQL для доступа к простым свойствам по имени, называемому EF.Свойство.

С помощью этого метода решение настолько простое:

 var result = dataPage
   .Where(x => values.Contains(EF.Property<string>(x.udfs, field)));
  

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

1. БОЖЕ… У меня есть google ‘d и google ‘d, и я искал это прямо там все чертово время. Спасибо!!!!