EF7中使用Expression<Func<T, bool>>查询含ValueObject属性的实体时LINQ表达式无法翻译的问题求助
问题描述
我最近在使用EF7(EF Core)结合ValueObject模式开发时遇到了一个LINQ表达式翻译的问题,想请教大家怎么解决。
情况是这样的:我定义了一个FacturacionValueObject类,它的属性都是继承自抽象ValueObject类的自定义类型,比如Numero:
public class FacturacionValueObject { public MovStock MovStockId { get; private set; } public CuentaCorriente CuentaCorrienteId { get; private set; } public Numero NumeroNpca { get; private set; } public Tipo TipoNpca { get; private set; } public Fecha FechaEmision { get; private set; } } public sealed class Numero : ValueObject { public decimal NumeroId { get; private set; } public Numero(decimal numeroId) { NumeroId = numeroId; } protected override IEnumerable<object> GetAtomicValues() { yield return NumeroId; } }
我用了仓储和工作单元模式,泛型仓储里有这个查询方法:
public async Task<IReadOnlyList<T>> GetAsync(Expression<Func<T, bool>> predicate) { try { return await _context.Set<T>().Where(predicate).AsNoTracking().ToListAsync(); } catch (Exception ex) { throw new Exception(ex.Message); } }
当我这样调用查询时:
Expression<Func<FacturacionValueObject, bool>> expression = f => f.NumeroNpca.NumeroId == 441268; var datos = await _unitOfWork.Repository<FacturacionValueObject>().GetAsync(predicate: expression);
API执行时抛出了异常:
The LINQ expression 'DbSet()
.Where(f => f.NumeroNpca.NumeroId == 441268)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
我查了很多资料但都没解决,希望有人能帮我指点一下,谢谢!
解决方案
你遇到的问题核心是EF Core默认无法自动识别自定义ValueObject类型的内部属性映射,需要我们显式配置这些类型在EF中的存储方式,下面给你两种可行的解决思路:
1. 将ValueObject配置为EF的Owned Entity(拥有实体)
EF Core的Owned Entity专门用来处理这种值对象的场景,它会把ValueObject的属性映射到主实体表的嵌套列中(比如NumeroNpca_NumeroId)。你需要在DbContext的OnModelCreating方法里添加配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 为FacturacionValueObject的每个ValueObject属性配置Owned Entity modelBuilder.Entity<FacturacionValueObject>() .OwnsOne(f => f.NumeroNpca); modelBuilder.Entity<FacturacionValueObject>() .OwnsOne(f => f.MovStockId); modelBuilder.Entity<FacturacionValueObject>() .OwnsOne(f => f.CuentaCorrienteId); // 其他ValueObject属性同理配置 }
配置完成后,EF就能正确解析f.NumeroNpca.NumeroId这样的表达式,将其转换为对NumeroNpca_NumeroId列的查询。
2. 使用值转换器(Value Converter)将ValueObject映射为单个列
如果你的ValueObject只包含一个核心属性(比如Numero只有NumeroId),可以直接把它映射为数据库表的单个列,不需要生成嵌套列。同样在OnModelCreating里配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<FacturacionValueObject>() .Property(f => f.NumeroNpca) .HasConversion( // 写入数据库时:将Numero转换为decimal类型的NumeroId numero => numero.NumeroId, // 读取数据库时:用decimal值实例化Numero对象 numeroId => new Numero(numeroId) ); // 其他单属性ValueObject同理配置值转换器 }
这种方式下,EF会把NumeroNpca直接映射为表中的一个decimal列,查询时的表达式自然能被正确翻译。
额外提示
- 不要轻易使用客户端评估(比如加
AsEnumerable()),因为这会把整个表的数据加载到内存中再过滤,数据量大时性能会很差。 - 配置完成后,可以先写一个简单的查询测试,确认映射生效后再用原来的表达式。
备注:内容来源于stack exchange,提问作者Alex Magatama

