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
相关产品推荐
相关产品推荐

