我想定义一个
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
?
问题在这里:
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;
}