LINQ to Entities仅支持转换EDM原语或枚举类型(唯一的新帖子)

时间:2017-11-22 00:34:01

标签: entity-framework entity-framework-6 expression-trees

前言

我计算了大约20个与此特定错误有关的问题,但我没有发现它们中的任何一个适用。我正在做一些不同的事情,因为我以编程方式而不是文字lambda语法创建表达式。我怀疑这可能会发现其他并发症。

下面的代码使用了我的一些助手以及我自己的UnitOfWork类。为简洁起见,我不会在此处拖动所有内容,但会根据请求提供任何其他代码。

问题

我正在尝试对IQueryable执行SelectMany次操作。它在我按字面意思写出表达式时起作用。但是,因为我需要能够动态地构建所有这些,所以我没有文字表达式的选项。

请参阅以下代码以获取评论。

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

1 个答案:

答案 0 :(得分:2)

EF不支持此类强制转换,因此不应使用Expression.Convert

实际问题是Expression.Lambda(call_select, p)会将Expression<Func<T, IQueryable<R>>>返回到Expression<Func<T, IEnumerable<R>>>。但Expression因为任何C#都不支持方差,因此预期会失败。

解决方案是使用Expression.Lambda方法重载,允许您指定所需的结果类型。在您的示例中,它可能是这样的:

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

一旦你这样做,问题就会解决。

作为旁注 - 当您动态构建表达式时,不需要在集合导航属性上应用AsQueryable - 它用于欺骗C#编译器以允许使用Expression<Func<...>>变量在你期望Func<...>的方法中调用文字表达式的内部。当您手动发出Expression.Call时,您只需调用相应的Enumerable方法:

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

我在这里猜测你ExpressionHelpers.GetMethodInfo的预期参数。我个人&#34;打电话给#34;像这样的通用方法:

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

当然AsQueryable在EF6中并不会造成伤害,但是它是多余的,并且在EF Core中不受支持。