为什么 EF 不能翻译这个从虚拟属性派生的表达式?

问题描述 投票:0回答:1

我想定义一个

Expression<Func<TSource,TKey>> KeyExpr
,例如您将传递给
IQueryable.OrderBy
,然后将其与
TKey
值组合以导出一个
Expression<Func<TSource,bool>> WithKeyExpr
,例如您将传递给
IQueryable.Single
。我在 another Q&A 中学习了如何做到这一点,并确认接受的答案有效。

我现在遇到的问题是当

WithKeyExpr
是虚的时候EF无法翻译
KeyExpr
。作为基线,我编写了一个非虚拟定义这些表达式的控制器:

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private ErpContext Db { get; }

    private Expression<Func<Country, int>> KeyExpr { get; } = e => e.CountryId;

    private Expression<Func<Country, bool>> WithKeyExpr(int key) =>
        Expression.Lambda<Func<Country, bool>>(Expression.Equal(KeyExpr.Body, Expression.Constant(key)), KeyExpr.Parameters);

    public TestController(ErpContext db)
    {
        Db = db;
    }

    [HttpGet]
    [Route("[action]")]
    [ProducesResponseType(typeof(IEnumerable<Country>), StatusCodes.Status200OK)]
    public async Task<IEnumerable<Country>> GetAsync()
    {
        return await Db.Countries.OrderBy(KeyExpr).ToListAsync();
    }

    [HttpGet]
    [Route("{id}/[action]")]
    [ProducesResponseType(typeof(Country), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetAsync(int id)
    {
        var country = await Db.Countries.SingleOrDefaultAsync(WithKeyExpr(id));
        return country == null ? NotFound() : Ok(country);
    }
}

这两个控制器动作都完美无缺。

为了重现我遇到的错误,我做了

KeyExpr
抽象。其他所有内容,包括
WithKeyExpr
,都以与之前完全相同的方式定义:

[ApiController]
[Route("[controller]")]
public abstract class TestControllerBase : ControllerBase
{
    private ErpContext Db { get; }

    protected abstract Expression<Func<Country, int>> KeyExpr { get; }
    private Expression<Func<Country, bool>> WithKeyExpr(int key) =>
        Expression.Lambda<Func<Country, bool>>(Expression.Equal(KeyExpr.Body, Expression.Constant(key)), KeyExpr.Parameters);

    public TestControllerBase(ErpContext db)
    {
        Db = db;
    }

    [HttpGet]
    [Route("[action]")]
    [ProducesResponseType(typeof(IEnumerable<Country>), StatusCodes.Status200OK)]
    public async Task<IEnumerable<Country>> GetAsync()
    {
        return await Db.Countries.OrderBy(KeyExpr).ToListAsync();
    }

    [HttpGet]
    [ProducesResponseType(typeof(Country), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [Route("{id}/[action]")]
    public async Task<IActionResult> GetAsync(int id)
    {
        var country = await Db.Countries.SingleOrDefaultAsync(WithKeyExpr(id));
        return country == null ? NotFound() : Ok(country);
    }
}

然后我推导出一个具体的控制器:

public class TestController : TestControllerBase
{
    public TestController(ErpContext db) : base(db) { }

    protected override Expression<Func<Country, int>> KeyExpr => o => o.CountryId;
}

Get()
仍然有效,表明 EF 可以翻译虚拟
KeyExpr
。但是
Get(int)
确实 not 工作; EF 无法翻译从虚拟
WithKeyExpr
派生的
KeyExpr
。错误是:

无法翻译 LINQ 表达式“o”。

为什么EF不能再翻译

WithKeyExpr

c# asp.net-core entity-framework-core expression-trees ef-core-7.0
1个回答
0
投票

问题在这里:

protected override Expression<Func<Country, int>> KeyExpr => o => o.CountryId;

这将为每次调用

KeyExpr
属性返回一个新的表达式实例,因此
o
中的
KeyExpr.Parameters
将与
o
中使用的
KeyExpr.Body
不同。修复它的一种方法是将具体类更改为:

public class TestController : TestControllerBase
{
    // ...

    protected override Expression<Func<Country, int>> KeyExpr { get; } = o => o.CountryId;
}
© www.soinside.com 2019 - 2024. All rights reserved.