Как создать условие с помощью EF Core 5, используя конкретный тип (он же «Выражение LINQ не может быть переведено»)?

#c# #entity-framework-core #linq-to-sql

Вопрос:

Я пытаюсь присоединиться, а затем отфильтровать результаты:

 var result = context.Test1
    .Join(context.Test2, q => q.Test11, q => q.Test21, (q, p) => new Tuple<Test1, Test2>(q, p))
    .Where(q => q.Item1.Test2 == "test")
    .ToList();
 

Приведенный выше запрос вызывает исключение:

 The LINQ expression 'DbSet<Test1>()
.Join(
    inner: DbSet<Test2>(), 
    outerKeySelector: t => t.Test11, 
    innerKeySelector: t0 => t0.Test21, 
    resultSelector: (t, t0) => new TransparentIdentifier<Test1, Test2>(
        Outer = t, 
        Inner = t0
    ))
.Where(ti => new Tuple<Test1, Test2>(
    ti.Outer, 
    ti.Inner
).Item1.Test2 == "test")' could not be translated
 

Проблема заключается в следующем: new Tuple<Test1, Test2>(ti.Outer, ti.Inner).Item1.Test2 == "test") . Когда я использую анонимный тип для соединения, он хорошо работает:

 var result = context.Test1
    .Join(context.Test2, q => q.Test11, q => q.Test21, (q, p) => new { q, p })
    .Where(q => q.q.Test2 == "test")
    .ToList();
 

Но проблема в том, что я строю запрос динамически (с помощью PredicateBuilder), и поэтому мне нужен конкретный тип, чтобы иметь возможность создавать запрос. Поэтому я использую такой метод, как:

 private static Expression<Func<Tuple<Test1, Test2>, bool>> GetTransactionsSearchPredicate(string[] values)
{
    var searchPredicate = PredicateBuilder.False<Tuple<Test1, Test2>>();

    foreach (var val in values)
    {
        searchPredicate = searchPredicate.Or(q => q.Item1.Test2 == val);
    }

    return searchPredicate;
}
 

Я также попытался перейти Tuple на пользовательский тип, но ошибка та же.

Могу ли я что-нибудь сделать, чтобы это сработало? Раньше он работал с EF Core 2, но я думаю, что он был просто выполнен локально…

Ответ №1:

System.Tuple поддержка постоянно меняется/прерывается между версиями ядра EF, поэтому ее использование ненадежно.

Для EFC 5.0.10 (последний официальный на данный момент) он не работает, но пользовательский универсальный тип с настраиваемыми свойствами (вместо неизменяемого типа с конструктором as System.Tuple ) работает. например, если вы замените Tuple , допустим Pair , следующую подпись:

 public class Pair<T1, T2>
{
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }
}
 

и, конечно, измените вызов конструктора на более подробный

 .Join(context.Test2, q => q.Test11, q => q.Test21,
    (q, p) => new Pair<Test1, Test2> { Item1 = q, Item2 = p }) // <--
 

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

1. Это странно, потому что я уже тестировал пользовательский класс, но он не работал. Может быть, это было не публично? В любом случае, работает как заклинание!