Blazor Server中EF Core实现IQueryable<T>跨模型映射适配DxGrid
解决方案:DTO与数据库实体的IQueryable查询转译
核心思路
要让GridDevExtremeDataSource针对DTO执行的筛选、排序、分页等LINQ操作最终作用在数据库实体的IQueryable上,核心是通过表达式树转换拦截并重写查询逻辑:将针对DTO的表达式树(比如Where(dto => dto.UnitPrice > 100))转换为针对数据库实体的表达式树(比如Where(product => product.Price > 100)),执行数据库查询后再投影为DTO。
步骤1:定义字段映射与表达式树转换器
创建继承ExpressionVisitor的转换器类,负责将DTO的成员访问替换为对应数据库实体的成员:
public class ProductDtoToEntityVisitor : ExpressionVisitor { // 硬编码DTO与实体的字段映射,也可通过特性标记实现动态配置 private static readonly Dictionary<string, string> _propertyMappings = new() { { nameof(ProductDTO.Id), nameof(Product.Id) }, { nameof(ProductDTO.ProductName), nameof(Product.Name) }, { nameof(ProductDTO.UnitPrice), nameof(Product.Price) }, { nameof(ProductDTO.InStock), nameof(Product.StockQuantity) } }; protected override Expression VisitMember(MemberExpression node) { // 检测当前访问的是否是DTO属性,若是则替换为实体属性 if (node.Member.DeclaringType == typeof(ProductDTO) && _propertyMappings.TryGetValue(node.Member.Name, out var entityPropertyName)) { var parameterExpr = Visit(node.Expression); var entityProperty = typeof(Product).GetProperty(entityPropertyName); return Expression.Property(parameterExpr, entityProperty); } return base.VisitMember(node); } }
步骤2:创建自定义IQueryable包装类
这个类对外暴露IQueryable<ProductDTO>,内部将查询转译到数据库实体的IQueryable<Product>上执行:
public class CustomProductDataSource : IQueryable<ProductDTO> { private readonly IQueryable<Product> _entityQuery; private readonly ProductDtoToEntityVisitor _expressionVisitor; public CustomProductDataSource(IQueryable<Product> entityQuery) { _entityQuery = entityQuery; _expressionVisitor = new ProductDtoToEntityVisitor(); Provider = new CustomQueryProvider(_entityQuery.Provider, _expressionVisitor); Expression = Expression.Constant(this); } public IEnumerator<ProductDTO> GetEnumerator() => ExecuteQuery().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public Type ElementType => typeof(ProductDTO); public Expression Expression { get; } public IQueryProvider Provider { get; } private IQueryable<ProductDTO> ExecuteQuery() { // 转译查询表达式后,投影为DTO return _entityQuery.Select(p => new ProductDTO { Id = p.Id, ProductName = p.Name, UnitPrice = p.Price, InStock = p.StockQuantity > 0 }); } // 自定义QueryProvider处理复杂查询操作(OrderBy/Skip/Take等) private class CustomQueryProvider : IQueryProvider { private readonly IQueryProvider _innerProvider; private readonly ProductDtoToEntityVisitor _visitor; public CustomQueryProvider(IQueryProvider innerProvider, ProductDtoToEntityVisitor visitor) { _innerProvider = innerProvider; _visitor = visitor; } public IQueryable CreateQuery(Expression expression) { var translatedExpr = _visitor.Visit(expression); var entityQuery = _innerProvider.CreateQuery(translatedExpr) as IQueryable<Product>; return entityQuery.Select(p => new ProductDTO { Id = p.Id, ProductName = p.Name, UnitPrice = p.Price, InStock = p.StockQuantity > 0 }); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { if (typeof(TElement) == typeof(ProductDTO)) { var translatedExpr = _visitor.Visit(expression); var entityQuery = _innerProvider.CreateQuery<Product>(translatedExpr); return entityQuery.Select(p => new ProductDTO { Id = p.Id, ProductName = p.Name, UnitPrice = p.Price, InStock = p.StockQuantity > 0 }) as IQueryable<TElement>; } return _innerProvider.CreateQuery<TElement>(expression); } public object Execute(Expression expression) { var translatedExpr = _visitor.Visit(expression); return _innerProvider.Execute(translatedExpr); } public TResult Execute<TResult>(Expression expression) { var translatedExpr = _visitor.Visit(expression); return _innerProvider.Execute<TResult>(translatedExpr); } } }
步骤3:在Blazor组件中集成使用
将自定义数据源传入GridDevExtremeDataSource,即可实现DTO层面的查询自动转译到数据库:
object _dataBinding { get; set; } protected override async Task OnInitializedAsync() { await using var databaseContext = _dbContextFactory.CreateDbContext(); // 用自定义数据源包装数据库实体的IQueryable var customDataSource = new CustomProductDataSource(databaseContext.Products.AsQueryable()); _dataBinding = new GridDevExtremeDataSource<ProductDTO>(customDataSource); }
关键说明
- 上述示例支持基本的
Where、OrderBy、Skip/Take操作,若需处理分组、聚合等复杂查询,可扩展ExpressionVisitor的VisitMethodCall方法来解析对应的LINQ方法。 - 字段映射可通过特性标记(如
[MapTo(nameof(Product.Name))])替代硬编码字典,实现更灵活的配置。
内容的提问来源于stack exchange,提问作者JamesWilliams
相关产品推荐
相关产品推荐

