LINQ to Entities ne prend en charge que le transtypage de types de primitive ou d'énumération EDM (nouvelle publication unique)

entity-framework entity-framework-6 expression-trees

Question

Préface

J'ai compté probablement 20 questions concernant cette erreur particulière, mais je n'ai trouvé aucune d'entre elles applicable. Je fais quelque chose de différent en ce sens que je crée mes expressions par programmation plutôt que par syntaxe lambda littérale. Je soupçonne que cela pourrait révéler d'autres complications.

Le code ci-dessous utilise certaines de mes aides ainsi que ma propre classe UnitOfWork. Par souci de brièveté, je ne ferai pas tout glisser ici, mais je fournirai tout code supplémentaire sur demande.

La question

J'essaie d'effectuer des opérations IQueryable sur un SelectMany . Cela fonctionne lorsque j'écris littéralement les expressions. Cependant, comme je dois pouvoir construire tout cela dynamiquement, je n'ai pas l'option des expressions littérales.

Voir le code ci-dessous pour les commentaires.

Unable to cast the type 'IQueryable`1[[SystemAssociateModel]]' to type 'IEnumerable`1[[SystemAssociateModel]]'.
LINQ to Entities only supports casting EDM primitive or enumeration types.

☺

public static void SelectManyTest()
{
    int id = 14690;

    var p = Expression.Parameter(typeof(SystemEntitlement));

    var projector = ExpressionHelpers.GetLambda<SystemAssociate, SystemAssociateModel>(x => new SystemAssociateModel
    {
        role = x.Role.Name,
        id = x.Associate.Id,
        order = x.Order
    });

    var memberPath = ExpressionHelpers.GetLambda<SystemEntitlement, ICollection<SystemAssociate>>(
        x => x.System.Associates).Body.AsMemberExpression().ReplaceInstance(p);

    //These two calls equate to this:  x.System.Associates.AsQueryable().Select(projector)
    var call_asQueryable = Expression.Call(ExpressionHelpers.GetMethodInfo(() => Queryable.AsQueryable<SystemAssociate>(null)), memberPath);
    Expression call_select = Expression.Call(ExpressionHelpers.GetMethodInfo(
        () => Queryable.Select(default(IQueryable<SystemAssociate>), default(Expression<Func<SystemAssociate, SystemAssociateModel>>))),
        call_asQueryable, projector);

    //I use this in an attempt to cast my `Select` into `IEnumerable` for `SelectMany`.  This
    //is most likely where the issue is occurring.  It makes sense that only specific types of
    //casts would be supported within an expression.
    //call_select = Expression.Convert(call_select, typeof(IEnumerable<SystemAssociateModel>));

    //I have to use the uncommented line since that's the only one suitable for `.SelectMany`.
    //This is the reason for the cast above, to convert `Select`'s `IQueryable` into an
    //`IEnumerable` for `SelectMany`.  If I don't use the explicit cast, it will attempt an
    //implicit cast and still fail.
    //var selector = (Expression<Func<SystemEntitlement, IQueryable<SystemAssociateModel>>>)Expression.Lambda(call_select, masterp);
    var selector = (Expression<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>)Expression.Lambda(call_select, p);

    //This works so long as I write the `.SelectMany` expression literally.  I seems that the compiler is somehow
    //able to handle the implicit cast from `ICollection` to `IEnumerable`.
    var associates1 = UnitOfWork.UseWith(work => work.GetRepo<SystemEntitlement>().AsQueryable()
    .Where(x => x.Id == id)
    .SelectMany(x => x.System.Associates.AsQueryable().Select(projector))
    .Where(x => x.order == 1)
    .ToArray());

    //This throws the error in question.
    var associates2 = UnitOfWork.UseWith(work => work.GetRepo<SystemEntitlement>().AsQueryable()
    .Where(pred)
    .SelectMany(selector)
    .Where(x => x.order == 1)
    .ToArray());
}

Réponse acceptée

EF ne supporte pas ce type d'incantation, aucune Expression.Convert ne doit donc être utilisée.

Le problème actuel est que Expression.Lambda(call_select, p) renvoie Expression<Func<T, IQueryable<R>>> que vous essayez de convertir en Expression<Func<T, IEnumerable<R>>> . Mais Expression tant que classe C # ne prend pas en charge la variance, de sorte que la conversion a échoué comme prévu.

La solution consiste à utiliser les surcharges de méthode Expression.Lambda qui vous permettent de spécifier le type de résultat souhaité. Dans votre exemple, cela pourrait être comme ça:

var selector = Expression.Lambda<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>(
    call_select, p);

Une fois que vous faites cela, le problème sera résolu.

Remarque AsQueryable : lorsque vous AsQueryable expressions de manière dynamique, il n'est pas nécessaire d'appliquer AsQueryable aux propriétés de navigation de la collection. Il permet au compilateur C # de tromper pour permettre l'utilisation de la variable Expression<Func<...>> dans ce que vous appelez des expressions littérales méthodes qui attendent Func<...> . Lorsque vous Enumerable manuellement Expression.Call , vous pouvez simplement appeler la méthode Enumerable correspondante:

var call_select = Expression.Call(ExpressionHelpers.GetMethodInfo(
    () => Enumerable.Select(default(IEnumerable<SystemAssociate>), default(Func<SystemAssociate, SystemAssociateModel>))),
    memberPath, projector);

Ici, je devine les arguments attendus de votre ExpressionHelpers.GetMethodInfo . Personnellement, j'appelle des méthodes génériques comme ceci:

var call_select = Expression.Call(
    typeof(Enumerable), "Select", new[] { typeof(SystemAssociate), typeof(SystemAssociateModel) },
    memberPath, projector);

Bien entendu, AsQueryable ne fait pas mal dans EF6, mais est redondant et n'est pas pris en charge dans EF Core.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow