You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:43:25