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

EF Core中Linq Select内复用映射逻辑且不加载全实体的实现方法

实现方案

核心思路是把单个属性的映射逻辑封装为Expression<Func<AddressObj, GenericComboBox>>表达式树,再通过表达式展开的方式注入到整体的Select投影逻辑中,EF Core可以正常解析生成SQL,不会触发客户端评估,也不会加载全量字段。

步骤1:封装复用的映射表达式

定义公共的静态类存储可复用的映射逻辑:

// 此处AddressObj替换为你的PrimaryOffice对应的实体类型
public static class CommonMappings
{
    public static readonly Expression<Func<AddressObj, GenericComboBox>> ToGenericComboBox = address =>
        address == null
            ? null
            : new GenericComboBox()
            {
                Value = address.Id,
                Text = address.LocationName
                + (address.LocationBusinessName == null ? "" : " (" + address.LocationBusinessName + ") ")
                + "("
                + (address.AddressLine1 != null ? address.AddressLine1 + ", " : null)
                + (address.AddressLine2 != null ? address.AddressLine2 + ", " : null)
                + (address.City != null ? address.City + ", " : null)
                + (address.State != null ? address.State.Name != null ? address.State.Name + ", " : null : null)
                + (address.ZipCode ?? null)
                + ")"
            };
}

步骤2:在Select中复用映射

有两种常用实现方式,按需选择即可:

方案A:使用LINQKit(最简实现)

  1. 安装对应EF Core版本的NuGet包 LinqKit.Microsoft.EntityFrameworkCore
  2. 查询时调用AsExpandable()展开表达式,直接调用封装的表达式:
using LinqKit;

IQueryable<ExampleViewModel> list = query.AsExpandable()
    .Select(x => new ExampleViewModel()
    {
        Id = x.Id,
        BindDate = x.BindDate,
        CancellationDate = x.CancellationDate,
        ChangeDate = x.ChangeDate,
        CreateDate = x.CreateDate,
        Description = x.Description,
        // 直接调用封装好的表达式即可
        PrimaryOffice = CommonMappings.ToGenericComboBox.Invoke(x.PrimaryOffice)
    });

AsExpandable()会自动将Invoke调用展开为完整的表达式树,EF Core可正常解析为SQL,仅查询需要的字段,无额外性能损耗。

方案B:无第三方依赖实现

如果不想引入额外NuGet包,可以自定义表达式访问者,将属性映射表达式替换到整体Select的表达式树中,实现代码如下:

public static class ExpressionExtensions
{
    public static Expression<Func<TSource, TTarget>> Inject<TSource, TTarget, TProp>(
        this Expression<Func<TSource, TTarget>> mainExpr,
        Expression<Func<TSource, TProp>> propExpr,
        string propertyName)
    {
        var parameter = mainExpr.Parameters.First();
        var memberInit = (MemberInitExpression)mainExpr.Body;
        var newBinding = Expression.Bind(
            typeof(TTarget).GetProperty(propertyName),
            Expression.Invoke(propExpr, parameter)
        );
        var newBindings = memberInit.Bindings.Where(b => b.Member.Name != propertyName).Append(newBinding);
        var newMemberInit = Expression.MemberInit(memberInit.NewExpression, newBindings);
        return Expression.Lambda<Func<TSource, TTarget>>(newMemberInit, parameter);
    }

    public static IQueryable<TTarget> SelectWithInjection<TSource, TTarget>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TTarget>> baseSelect,
        params (string PropName, object MappingExpr)[] injections)
    {
        var expr = baseSelect;
        foreach (var (propName, mappingExpr) in injections)
        {
            var genericMethod = typeof(ExpressionExtensions).GetMethod(nameof(Inject))
                ?.MakeGenericMethod(typeof(TSource), typeof(TTarget), mappingExpr.GetType().GetGenericArguments()[1]);
            if (genericMethod == null) continue;
            expr = (Expression<Func<TSource, TTarget>>)genericMethod.Invoke(null, new object[] { expr, mappingExpr, propName });
        }
        return source.Select(expr);
    }
}

使用方式:

// YourEntity替换为你的查询源实体类型
var baseSelect = (Expression<Func<YourEntity, ExampleViewModel>>)(x => new ExampleViewModel()
{
    Id = x.Id,
    BindDate = x.BindDate,
    CancellationDate = x.CancellationDate,
    ChangeDate = x.ChangeDate,
    CreateDate = x.CreateDate,
    Description = x.Description,
    // 此处默认值会被替换,随便填写即可
    PrimaryOffice = null
});

IQueryable<ExampleViewModel> list = query.SelectWithInjection(baseSelect, 
    (nameof(ExampleViewModel.PrimaryOffice), (Expression<Func<YourEntity, GenericComboBox>>)(x => CommonMappings.ToGenericComboBox.Invoke(x.PrimaryOffice)))
);

验证

你可以通过EF Core日志输出生成的SQL,确认只会查询映射逻辑中用到的字段,不会加载全量实体数据,符合OData的性能要求。

内容的提问来源于stack exchange,提问作者Jackie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 14:48:02