EF Core使用显式转换时LINQ表达式无法翻译问题求助
我有一个名为Product的实体类,包含向ProductListDTO的显式转换:
public class Product { public Guid ID { get; set; } public string Name { get; set; } public string Description { get; set; } public string? Notes { get; set; } public static explicit operator ProductListDTO (Product p) { return new ProductListDTO { ID = p.ID, Name = p.Name, Description = p.Description }; } }
以下是4种数据访问方式,其中第4种执行失败:
- (1) 可行
var allProducts = db.Products.Select(x => new ProductListDTO { Name = x.Name, Description = x.Description }).ToList();
- (2) 可行
var allProducts_Casting = db.Products.Select(x => (ProductListDTO)x).ToList();
- (3) 可行
var filteredProducts = db.Products.Select(x => new ProductListDTO { Name = x.Name, Description = x.Description }) .Where(x => x.Name == "Product 1") .ToList();
- (4) 执行失败
var filteredProducts_Casting = db.Products.Select(x => (ProductListDTO) x) .Where(x => x.Name == "Product 1") .ToList();
抛出的异常信息:
System.InvalidOperationException : The LINQ expression 'DbSet()
.Where(p => !(p.IsDeleted))
.Where(p => ((ProductListDTO)p).Name == "Product 1")' 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'.
我的目标是实现可复用、简洁的实体转DTO逻辑,偏好以下写法:
public IQueryable<ProductListDTO> OdataList() { return db.Products.Select(x => (ProductListDTO)x); } public IQueryable<PurchaseOrderItemListDTO> OdataList() { return db.PurchaseOrderItems.Select(p => new PurchaseOrderItemListDTO { ID = p.ID, UnitPrice = p.UnitPrice, Quantity = p.Quantity, Product = (ProductListDTO) p.Product, }); }
但这种写法会遇到LINQ表达式无法翻译的问题。同时我希望避免使用复杂的表达式树方案:
public static readonly Expression<Func<Product, ProductListDTO>> ProductListQuery = p => new ProductListDTO { ID = p.ID, Name = p.Name, Description = p.Description }; public IQueryable<ProductListDTO> OdataList() { return db.Products.Select(ProductListQuery); } public IQueryable<PurchaseOrderItemListDTO> OdataList() { var productListing = ProductListQuery.Compile(); return db.PurchaseOrderItems.Select(p => new PurchaseOrderItemListDTO { ID = p.ID, UnitPrice = p.UnitPrice, Quantity = p.Quantity, Product = productListing(p.Product), }); }
需要解决这个问题,实现简洁可复用的实体转DTO逻辑。
方法1:使用AutoMapper的ProjectTo(推荐)
AutoMapper专门支持将EF Core的IQueryable投影到DTO,能自动处理表达式翻译,同时保持代码简洁。
- 定义映射配置:
public class MappingProfile : Profile { public MappingProfile() { CreateMap<Product, ProductListDTO>(); CreateMap<PurchaseOrderItem, PurchaseOrderItemListDTO>() .ForMember(dest => dest.Product, opt => opt.MapFrom(src => src.Product)); } }
- 在启动类中注册AutoMapper:
// .NET 6+ builder.Services.AddAutoMapper(typeof(MappingProfile));
- 使用
ProjectTo实现查询:
private readonly IMapper _mapper; private readonly YourDbContext _db; // 通过构造函数注入依赖 public YourRepository(IMapper mapper, YourDbContext db) { _mapper = mapper; _db = db; } public IQueryable<ProductListDTO> OdataList() { return _db.Products.ProjectTo<ProductListDTO>(_mapper.ConfigurationProvider); } public IQueryable<PurchaseOrderItemListDTO> OdataList() { return _db.PurchaseOrderItems.ProjectTo<PurchaseOrderItemListDTO>(_mapper.ConfigurationProvider); }
这种方式完全复用映射逻辑,EF Core能正确翻译后续的Where等操作,代码简洁易维护。
方法2:封装表达式树为扩展方法(无需第三方库)
如果不想引入AutoMapper,可以把表达式树封装成扩展方法,隐藏复杂实现,同时保持调用简洁:
- 定义扩展类:
public static class ProductMappingExtensions { private static readonly Expression<Func<Product, ProductListDTO>> _toProductListDTO = p => new ProductListDTO { ID = p.ID, Name = p.Name, Description = p.Description }; public static IQueryable<ProductListDTO> AsProductListDTO(this IQueryable<Product> query) { return query.Select(_toProductListDTO); } }
- 基础查询调用:
public IQueryable<ProductListDTO> OdataList() { return _db.Products.AsProductListDTO(); }
如果要在关联实体查询中支持EF Core翻译(比如PurchaseOrderItem的后续过滤),可以搭配LINQKit的AsExpandable扩展:
先安装LINQKit NuGet包,然后扩展:
public static class PurchaseOrderItemMappingExtensions { public static IQueryable<PurchaseOrderItemListDTO> AsPurchaseOrderItemListDTO(this IQueryable<PurchaseOrderItem> query) { return query.AsExpandable().Select(p => new PurchaseOrderItemListDTO { ID = p.ID, UnitPrice = p.UnitPrice, Quantity = p.Quantity, Product = ProductMappingExtensions._toProductListDTO.Invoke(p.Product) }); } }
调用时直接用_db.PurchaseOrderItems.AsPurchaseOrderItemListDTO()即可,EF Core能正确翻译整个表达式。
方法3:实体类中统一维护转换逻辑与表达式树
把转换逻辑和对应的表达式树放在实体类中,统一维护,调用也比较简洁:
public class Product { public Guid ID { get; set; } public string Name { get; set; } public string Description { get; set; } public string? Notes { get; set; } public static ProductListDTO ToProductListDTO(Product p) { return new ProductListDTO { ID = p.ID, Name = p.Name, Description = p.Description }; } public static Expression<Func<Product, ProductListDTO>> ToProductListDTOExpression => p => new ProductListDTO { ID = p.ID, Name = p.Name, Description = p.Description }; }
查询调用:
public IQueryable<ProductListDTO> OdataList() { return _db.Products.Select(Product.ToProductListDTOExpression); } public IQueryable<PurchaseOrderItemListDTO> OdataList() { return _db.PurchaseOrderItems.AsExpandable().Select(p => new PurchaseOrderItemListDTO { ID = p.ID, UnitPrice = p.UnitPrice, Quantity = p.Quantity, Product = Product.ToProductListDTOExpression.Invoke(p.Product) }); }
内容的提问来源于stack exchange,提问作者Aza Asim

