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

EF Core中带值转换的Union查询如何实现服务端求值?

解决方案:EF Core中实现服务端执行的Union查询

可以实现服务端执行的Union查询,核心是调整查询结构,将Union操作放在最终投影到GridModel之前,避免EF Core无法转换客户端投影后的集合操作。

问题根源

原代码先将实体投影为GridModel再执行Union,EF Core无法将自定义类型的集合操作转换为SQL:一方面GridModel不是EF映射的实体类型,另一方面复杂类型(List<StringWithStyle>、StringWithStyle)的比较逻辑无法直接转换为数据库层面的操作。

调整步骤与代码示例

1. 确保复杂类型的值比较正确性(可选但推荐)

重写StringWithStyle的Equals和GetHashCode方法,保证客户端值比较的准确性(服务端Union基于数据库存储的JSON字符串比较,此步骤主要优化客户端逻辑):

public class StringWithStyle
{
    public string Text { get; set; }
    public bool IsBold { get; set; }
    public bool IsItalic { get; set; }
    public bool IsUpperCase { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is not StringWithStyle other)
            return false;
        return Text == other.Text 
               && IsBold == other.IsBold 
               && IsItalic == other.IsItalic 
               && IsUpperCase == other.IsUpperCase;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Text, IsBold, IsItalic, IsUpperCase);
    }
}

2. 调整查询结构

先投影为结构一致的匿名类型,执行Union后再转换为GridModel:

// 1. 对两个实体分别投影为结构完全一致的匿名类型
var entity1Query = dbContext.Set<Entity1>()
    .Select(e1 => new 
    {
        Id = e1.Id,
        IsFirst = true,
        Names = e1.Names,
        Name = (StringWithStyle)null
    });

var entity2Query = dbContext.Set<Entity2>()
    .Select(e2 => new 
    {
        Id = e2.Id,
        IsFirst = false,
        Names = (List<StringWithStyle>)null,
        Name = e2.Name
    });

// 2. 执行Union(自动去重)或UnionAll(无去重,性能更高)
var unionQuery = entity1Query.Union(entity2Query);

// 3. 最终投影为GridModel并执行查询
var grid = unionQuery.Select(x => new GridModel
    {
        Id = x.Id,
        IsFirst = x.IsFirst,
        Names = x.Names,
        Name = x.Name
    })
    .ToList();

3. 额外优化:稳定JSON序列化(可选)

为避免因JSON序列化格式差异(如属性顺序)导致服务端Union误判行唯一性,可在值转换时指定固定的JsonSerializerOptions:

var jsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = false,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

// 在Entity1的Names属性配置中使用
modelBuilder.Entity<Entity1>()
    .Property(e => e.Names)
    .HasConversion(
        v => JsonSerializer.Serialize(v, jsonOptions),
        v => JsonSerializer.Deserialize<List<StringWithStyle>>(v, jsonOptions),
        new ValueComparer<List<StringWithStyle>>(
            (arr1, arr2) => arr1.SequenceEqual(arr2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode()))
        )
    );

// Entity2的Name属性配置同理
modelBuilder.Entity<Entity2>()
    .Property(e => e.Name)
    .HasConversion(
        v => JsonSerializer.Serialize(v, jsonOptions),
        v => JsonSerializer.Deserialize<StringWithStyle>(v, jsonOptions),
        new ValueComparer<StringWithStyle>(
            (val1, val2) => val1.Equals(val2),
            c => c.GetHashCode()
        )
    );

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 18:40:38