AutoMapper.ProjectTo 正在本地评估带有条件的查询
AutoMapper.ProjectTo with criteria query is being evaluated locally
我正在使用 Automapper 可查询扩展:http://docs.automapper.org/en/stable/Queryable-Extensions.html#
我想将我的完整实体映射到只有几个字段的简化版本。在这种情况下,我的前端 UI 将不知道基本实体,只会发送简化实体的过滤条件。因此,我正在使用 query.ProjectTo<MySimpleEntity>
,然后将 Where
与过滤条件一起应用。
在这种特定情况下,两个实体都实现了我的自定义接口 ITimeLimitedEntity
,它确保了 ValidFrom
和 ValidTo
字段。
当我不使用 Automapper 并在整个实体上应用条件时,过滤会按预期工作并生成正确的 SQL 查询。
但是一旦我添加 ProjectTo<MySimpleEntity>
,我就会收到警告(我实际上将其作为异常抛出以避免静默客户端评估):
The LINQ expression 'where ((Convert(new MySimpleEntity() {Id = [dtoMyOriginalEntity].Id, Name = [dtoMyOriginalEntity].Name, ValidFrom = [dtoMyOriginalEntity].ValidFrom}, ITimeLimitedEntity).ValidFrom == null))'
could not be translated and will be evaluated locally.'
所以,标准被应用于 Automapper 的内部 dtoMyOriginalEntity
,当然,它没有实现我的 ITimeLimitedEntity
,因此 LINQ 添加了 Convert
,这显然不能被转换为有效的 SQL 查询,从而导致尝试在内存中对其进行评估。
这很奇怪。当我调试代码时:
projQuery = query.ProjectTo<MySimpleEntity>(_mapper.ConfigurationProvider);
projQuery 是 IQueryable,它实现了 ITimeLimitedEntity
- 那为什么 Linq 需要转换?
我如何告诉 Automapper 或 Linq 它应该将我的 where
标准应用于已经是 ITimeLimitedEntity 的最终投影 MySimpleEntity 而不是某些 Automapper 的内部 DTO 实体?
更新:
我创建了一个简单的 dotnetfiddle 示例 https://dotnetfiddle.net/X2RDGn
当然,它工作得很好,因为它使用的是内存数据库。但是我已经评论了现实世界中失败的问题区域。
更新 2:
我使用真实数据库创建了一个简化的控制台应用程序 gist.github.com/progmars/eeec32a533dbd2e1f85e551db1bc53f8,当通过通用方法访问时,Linq 表达式仍在本地执行,尽管调试器显示两个表达式主体完全匹配。
更新 3:
正如一些人所指出的,原因是 Linq-to-entities 本身而不是 Automapper。因此,为新的 SO 问题创建了一个新的、不太复杂的可重现案例,并在此处解决:
问题与AutoMapper无关,而是基于接口成员的谓词表达式。 Convert
是 C# cast 的等效表达式,如您所见,它将实体类型强制转换为 接口 包含 属性 (dtoMyOriginalEntity
是只是 lambda 表达式的名称 参数).
这是接口约束泛型方法的已知行为。解决方案是添加 class
约束。将其应用于您的示例:
public class CriteriaSpecificationForSomeable<T> : ISpecification<T> where T: class, ISomeable
// ----------------------------------------------------------------------------^^^
{
private Expression<Func<T, bool>> _someCriteria;
public CriteriaSpecificationForSomeable()
{
_someCriteria = e => (e.Some == "Hello");
}
public virtual Expression<Func<T, bool>> Criteria { get { return _someCriteria; } }
}
演员表 (Convert
) 消失了。
顺便说一句,这是在最新的 EF Core 2.2.6 中修复的,原始代码在那里工作(可能您使用的是旧的 EF Core 版本)。但是 EF6 有同样的问题,所以通常在针对接口约束泛型类型创建表达式时添加 class
约束更安全。
我正在使用 Automapper 可查询扩展:http://docs.automapper.org/en/stable/Queryable-Extensions.html#
我想将我的完整实体映射到只有几个字段的简化版本。在这种情况下,我的前端 UI 将不知道基本实体,只会发送简化实体的过滤条件。因此,我正在使用 query.ProjectTo<MySimpleEntity>
,然后将 Where
与过滤条件一起应用。
在这种特定情况下,两个实体都实现了我的自定义接口 ITimeLimitedEntity
,它确保了 ValidFrom
和 ValidTo
字段。
当我不使用 Automapper 并在整个实体上应用条件时,过滤会按预期工作并生成正确的 SQL 查询。
但是一旦我添加 ProjectTo<MySimpleEntity>
,我就会收到警告(我实际上将其作为异常抛出以避免静默客户端评估):
The LINQ expression 'where ((Convert(new MySimpleEntity() {Id = [dtoMyOriginalEntity].Id, Name = [dtoMyOriginalEntity].Name, ValidFrom = [dtoMyOriginalEntity].ValidFrom}, ITimeLimitedEntity).ValidFrom == null))'
could not be translated and will be evaluated locally.'
所以,标准被应用于 Automapper 的内部 dtoMyOriginalEntity
,当然,它没有实现我的 ITimeLimitedEntity
,因此 LINQ 添加了 Convert
,这显然不能被转换为有效的 SQL 查询,从而导致尝试在内存中对其进行评估。
这很奇怪。当我调试代码时:
projQuery = query.ProjectTo<MySimpleEntity>(_mapper.ConfigurationProvider);
projQuery 是 IQueryable,它实现了 ITimeLimitedEntity
- 那为什么 Linq 需要转换?
我如何告诉 Automapper 或 Linq 它应该将我的 where
标准应用于已经是 ITimeLimitedEntity 的最终投影 MySimpleEntity 而不是某些 Automapper 的内部 DTO 实体?
更新:
我创建了一个简单的 dotnetfiddle 示例 https://dotnetfiddle.net/X2RDGn 当然,它工作得很好,因为它使用的是内存数据库。但是我已经评论了现实世界中失败的问题区域。
更新 2: 我使用真实数据库创建了一个简化的控制台应用程序 gist.github.com/progmars/eeec32a533dbd2e1f85e551db1bc53f8,当通过通用方法访问时,Linq 表达式仍在本地执行,尽管调试器显示两个表达式主体完全匹配。
更新 3:
正如一些人所指出的,原因是 Linq-to-entities 本身而不是 Automapper。因此,为新的 SO 问题创建了一个新的、不太复杂的可重现案例,并在此处解决:
问题与AutoMapper无关,而是基于接口成员的谓词表达式。 Convert
是 C# cast 的等效表达式,如您所见,它将实体类型强制转换为 接口 包含 属性 (dtoMyOriginalEntity
是只是 lambda 表达式的名称 参数).
这是接口约束泛型方法的已知行为。解决方案是添加 class
约束。将其应用于您的示例:
public class CriteriaSpecificationForSomeable<T> : ISpecification<T> where T: class, ISomeable
// ----------------------------------------------------------------------------^^^
{
private Expression<Func<T, bool>> _someCriteria;
public CriteriaSpecificationForSomeable()
{
_someCriteria = e => (e.Some == "Hello");
}
public virtual Expression<Func<T, bool>> Criteria { get { return _someCriteria; } }
}
演员表 (Convert
) 消失了。
顺便说一句,这是在最新的 EF Core 2.2.6 中修复的,原始代码在那里工作(可能您使用的是旧的 EF Core 版本)。但是 EF6 有同样的问题,所以通常在针对接口约束泛型类型创建表达式时添加 class
约束更安全。