Пользовательский метод расширения LINQ вызывает исключение проверки: аргумент типа IEnumerable`1[entity]’ нарушает ограничение параметра типа ‘TCollection’

#c# #entity-framework-core #asp.net-core-mvc

#c# #entity-framework-core #asp.net-core-mvc

Вопрос:

У меня есть несколько объектов, которые выглядят следующим образом:

 public class MyEvent 
{
    // ...
    public IEnumerable<MyEncounter> Encounters { get; set; } // Navigation property
    // ...
}

public class MyEncounter
{
    public Guid MyEventID { get; set; }
    public MyEvent MyEvent { get; set; }  // Navigation property

    public Guid? MySpecialProperty { get; set; }

    public int Status { get; set; }
    //...
}
  

Я хочу добавить метод расширения, чтобы выполнить некоторую фильтрацию коллекции Encounters в MyEvent и вернуть значение MySpecialProperty для одного из экземпляров MyEncounter (в частности, экземпляра, где status = 1).

Итак, я написал свой метод расширения:

 public static class MyExtensions
{
    public static Guid? GetMySpecialProperty(this IEnumerable<MyEncounter> encounters)
    {
        return encounters.Where(e => e.Status == 1)
                         .Select(e => e.MySpecialProperty)
                         .FirstOrDefault();
    }
}
  

Теперь я готов использовать свой метод расширения. Я значительно упростил это, чтобы удалить материал, который должен быть неактуальным:

 var qry = MyEventsContext.
    .Where(<some logic>)
    .Select(x => new MyViewModel
    {
        SpecialPropValue = x.Encounters.GetMySpecialProperty()   
    });
  

Это компилируется, как и ожидалось, и я готов выполнить запрос:

 IList<MyViewModel> items = await qry.ToListAsync();
  

Вот где все идет не так. Как только .ToListAsync() выполняется, я получаю это исключение:

 VerificationException: Method Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor CustomShaperCompilingExpressionVisitor.PopulateCollection: type argument 'System.Collections.Generic.IEnumerable`1[<fully qualified name of MyEncounter>]' violates the constraint of type parameter 'TCollection'.
  

Внутреннее исключение лишь немного более подробное:

 ArgumentException: GenericArguments[0], 'System.Collections.Generic.IEnumerable`1[<fully qualified name of MyEncounter>]', on 'Void PopulateCollection[TCollection,TElement,TRelatedEntity](Int32, Microsoft.EntityFrameworkCore.Query.QueryContext, System.Data.Common.DbDataReader, Microsoft.EntityFrameworkCore.Query.Internal.ResultCoordinator, System.Func`3[Microsoft.EntityFrameworkCore.Query.QueryContext,System.Data.Common.DbDataReader,System.Object[]], System.Func`3[Microsoft.EntityFrameworkCore.Query.QueryContext,System.Data.Common.DbDataReader,System.Object[]], System.Func`3[Microsoft.EntityFrameworkCore.Query.QueryContext,System.Data.Common.DbDataReader,System.Object[]], System.Func`5[Microsoft.EntityFrameworkCore.Query.QueryContext,System.Data.Common.DbDataReader,Microsoft.EntityFrameworkCore.Query.Internal.ResultContext,Microsoft.EntityFrameworkCore.Query.Internal.ResultCoordinator,TRelatedEntity])' violates the constraint of type 'TCollection'.
  

О чем мне говорит это исключение и есть ли способ его разрешить? Насколько я могу судить, я правильно создал свой метод расширения.

Интересно, что если я создам метод расширения, чтобы просто получить MyEncounters, соответствующие критериям, и выполнить Select / FirstOrDefault в запросе, все работает, как ожидалось:

 // Extension method
public static IEnumerable<MyEncounter> GetMySpecialEncounters(this IEnumerable<MyEncounter> encounters)
{
    return encounters.Where(e => e.Status == 1)
}

// Query
var qry = MyEventsContext.
    .Where(<some logic>)
    .Select(x => new MyViewModel
    {
        SpecialPropValue = x.Encounters.GetMySpecialEncounters()
                                       .Select(x => x.MySpecialProperty)
                                       .FirstOrDefault()   
    });
  



Я ошибся в приведенном выше. Использование этого метода расширения в запросе приводит к этому исключению:

 When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return a non-null value of the same type
  

То, что работает, выполняет всю логику внутри запроса, вообще без метода расширения:

 // Query
var qry = MyEventsContext.
    .Where(<some logic>)
    .Select(x => new MyViewModel
    {
        SpecialPropValue = x.Encounters.Where(e => e.Status == 1)
                                       .Select(x => x.MySpecialProperty)
                                       .FirstOrDefault()   
    });
  

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

1. Ваш код не компилируется.

2. @TanveerBadar на самом деле он не предназначен для компиляции. Я включил фрагменты соответствующего материала из множества разных файлов.

3. Я должен был быть более ясным. В вашем навигационном MyEvent указателе отсутствует имя, что затрудняет его понимание. Не имеет значения, компилируется он или нет, его трудно скомпилировать «мысленно».

4. Кроме того, посмотрите, устраняет ли использование ToList() вместо этого проблему. Я сам заметил некоторое странное поведение вокруг асинхронных методов.

5. @TanveerBadar ах, я вижу! Я исправил эту опечатку. Я также пытался использовать .ToList() при выполнении запроса, но это ничего не изменило.

Ответ №1:

Вызов ToList on x.Encounters работает для меня в ядре 3.1 EF (хотя AsEnumerable() и ToArray() не работает):

 .Select(x => new MyViewModel
{
    SpecialPropValue = x.Encounters.ToList().GetMySpecialProperty()
});
  

Причиной такого поведения является проблема с вводом — CustomShaperCompilingExpressionVisitor.VisitExtension метод пытается обработать заполняющуюся коллекцию и пытается построить дерево выражений для вызова PopulateCollection метода, который имеет общее ограничение для типа коллекции ICollection<TElement> , но определяет IEnumerable<TElement> и передает его для MakeGenericMethod вызова, который завершается неудачей, причиной IEnumerable которого не ICollection является (на самом деле наоборот).

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

1. Изменение коллекции свойств навигации в модели с IEnumerable на на ICollection решило проблему. Спасибо.