Entity framework LINQ to实体仅支持强制转换EDM基元或枚举类型(唯一的新post)

Entity framework LINQ to实体仅支持强制转换EDM基元或枚举类型(唯一的新post),entity-framework,entity-framework-6,expression-trees,Entity Framework,Entity Framework 6,Expression Trees,前言 我数了大概20个关于这个特定错误的问题,但我没有发现其中任何一个适用。我正在做一些不同的事情,因为我是通过编程而不是通过文字lambda语法创建表达式的。我怀疑这可能揭示了其他的复杂情况 下面的代码使用了我的一些助手以及我自己的UnitOfWork类。为了简洁起见,我不会将所有这些都拖到这里,但会根据请求提供任何附加代码 问题 我正在尝试对SelectMany执行IQueryable操作。当我逐字逐句写出这些表达式时,它就起作用了。然而,因为我需要能够动态地构建所有这些,所以我没有文本表达

前言

我数了大概20个关于这个特定错误的问题,但我没有发现其中任何一个适用。我正在做一些不同的事情,因为我是通过编程而不是通过文字lambda语法创建表达式的。我怀疑这可能揭示了其他的复杂情况

下面的代码使用了我的一些助手以及我自己的UnitOfWork类。为了简洁起见,我不会将所有这些都拖到这里,但会根据请求提供任何附加代码

问题

我正在尝试对
SelectMany
执行
IQueryable
操作。当我逐字逐句写出这些表达式时,它就起作用了。然而,因为我需要能够动态地构建所有这些,所以我没有文本表达式的选项

有关注释,请参见下面的代码

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());
}
publicstaticvoid SelectManyTest()
{
int id=14690;
var p=表达式参数(typeof(systemauthentication));
var projector=ExpressionHelpers.GetLambda(x=>newsystemassociatemodel
{
role=x.role.Name,
id=x.Associate.id,
订单=x.订单
});
var memberPath=ExpressionHelpers.GetLambda(
x=>x.System.Associates.Body.AsMemberExpression().ReplaceInstance(p);
//这两个调用相当于:x.System.Associates.AsQueryable().Select(投影仪)
var call_asQueryable=Expression.call(ExpressionHelpers.GetMethodInfo(()=>Queryable.asQueryable(null)),memberPath);
Expression call\u select=Expression.call(ExpressionHelpers.GetMethodInfo(
()=>Queryable.Select(默认值(IQueryable)、默认值(表达式)),
呼叫_asQueryable,投影仪);
//我使用它试图将我的'Select'转换为'SelectMany'的'IEnumerable'
//最有可能发生问题的地方。只有特定类型的
//表达式中支持强制转换。
//call_select=Expression.Convert(call_select,typeof(IEnumerable));
//我必须使用未注释行,因为这是唯一适合于“.SelectMany”的行。
//这就是上面的强制转换将'Select'的'IQueryable'转换为
//`IEnumerable`for`SelectMany`。如果我不使用显式强制转换,它将尝试
//隐式强制转换仍然失败。
//变量选择器=(表达式)表达式.Lambda(调用select,masterp);
变量选择器=(表达式)表达式.Lambda(调用选择,p);
//只要我逐字写下`.SelectMany`表达式,这就行了。我觉得编译器在某种程度上
//能够处理从“ICollection”到“IEnumerable”的隐式强制转换。
var associates1=UnitOfWork.UseWith(work=>work.GetRepo().AsQueryable())
.其中(x=>x.Id==Id)
.SelectMany(x=>x.System.Associates.AsQueryable().Select(投影仪))
.其中(x=>x.order==1)
.ToArray());
//这就引发了对错误的质疑。
var associates2=UnitOfWork.UseWith(work=>work.GetRepo().AsQueryable())
.Where(pred)
.SelectMany(选择器)
.其中(x=>x.order==1)
.ToArray());
}

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

实际的问题是
Expression.Lambda(call_select,p)
返回您试图转换为
Expression
Expression。但是
Expression
因为任何C#类都不支持方差,所以cast预期会失败

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

var selector = Expression.Lambda<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>(
    call_select, p);
var call_select = Expression.Call(
    typeof(Enumerable), "Select", new[] { typeof(SystemAssociate), typeof(SystemAssociateModel) },
    memberPath, projector);
在这里,我猜测您的
ExpressionHelpers.GetMethodInfo
的预期参数。我个人“调用”一般方法如下:

var selector = Expression.Lambda<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>(
    call_select, p);
var call_select = Expression.Call(
    typeof(Enumerable), "Select", new[] { typeof(SystemAssociate), typeof(SystemAssociateModel) },
    memberPath, projector);

当然,
AsQueryable
在EF6中没有坏处,但它是多余的,在EF Core中不受支持。

与往常一样,在表达式中,Ivan Stoev代表胜利。使用lambda重载就成功了。至于不调用AsQueryable,我最初只调用了Select,但它告诉我不能在ICollection导航属性上调用Select,因此我用这种方式将其转换为IQueryable。