解决EF6中Linq To Entities选择操作的类型转换失败问题
在Entity Framework 6中处理自定义Id类型的查询翻译问题
你可以通过自定义ExpressionVisitor结合查询扩展方法,自动处理Select投影中ToId()调用、Id显式转换或构造函数调用的翻译问题,无需手动拆分两次Select。以下是具体实现方案:
核心思路
通过ExpressionVisitor分析投影表达式,将EF无法翻译的Id转换操作拆分为两部分:
- 服务器端投影:仅提取EF能翻译的原始字段(如
x.order_id),避免冗余数据传输 - 客户端转换:在内存中将原始字段转换为自定义
Id类型,生成目标DTO
实现代码
1. 自定义ExpressionVisitor及查询扩展方法
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; public static class QueryableExtensions { public static IQueryable<TResult> SelectWithIdConversion<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector) { var serverVisitor = new ServerProjectionVisitor(); var serverSelector = serverVisitor.Visit(selector) as LambdaExpression; var clientVisitor = new ClientProjectionVisitor(serverVisitor.MemberMappings); var clientSelector = clientVisitor.Visit(selector) as Expression<Func<object, TResult>>; return source.Select(serverSelector) .AsEnumerable() .Select(clientSelector.Compile()) .AsQueryable(); } private class ServerProjectionVisitor : ExpressionVisitor { public Dictionary<MemberInfo, Expression> MemberMappings { get; } = new(); protected override Expression VisitMemberInit(MemberInitExpression node) { var propertyTypes = new List<Type>(); var bindings = new List<Expression>(); foreach (var binding in node.Bindings.Cast<MemberAssignment>()) { var valueExpr = Visit(binding.Expression); MemberMappings[binding.Member] = valueExpr; if (IsIdConversionExpression(valueExpr)) { var intExpr = ExtractIntExpression(valueExpr); bindings.Add(intExpr); propertyTypes.Add(typeof(int)); } else { bindings.Add(valueExpr); propertyTypes.Add(valueExpr.Type); } } var anonType = CreateAnonymousType(propertyTypes.ToArray()); var constructor = anonType.GetConstructor(propertyTypes.ToArray()); return Expression.New(constructor, bindings); } private bool IsIdConversionExpression(Expression expr) { return expr switch { MethodCallExpression mce => mce.Method.DeclaringType == typeof(IdHelper) && mce.Method.Name == nameof(IdHelper.ToId), UnaryExpression ue => ue.NodeType == ExpressionType.Convert && ue.Type == typeof(Id), NewExpression ne => ne.Type == typeof(Id) && ne.Arguments.Count == 1 && ne.Arguments[0].Type == typeof(int), _ => false }; } private Expression ExtractIntExpression(Expression expr) { return expr switch { MethodCallExpression mce => mce.Arguments[0], UnaryExpression ue => ue.Operand, NewExpression ne => ne.Arguments[0], _ => throw new InvalidOperationException("无效的Id转换表达式") }; } private Type CreateAnonymousType(Type[] propertyTypes) { var assemblyName = new AssemblyName("DynamicAnonTypes"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicAnonTypesModule"); var typeBuilder = moduleBuilder.DefineType($"Anon_{Guid.NewGuid():N}", TypeAttributes.Public | TypeAttributes.Class); var fields = new List<FieldBuilder>(); for (int i = 0; i < propertyTypes.Length; i++) { var field = typeBuilder.DefineField($"_prop{i}", propertyTypes[i], FieldAttributes.Private); fields.Add(field); var prop = typeBuilder.DefineProperty($"Prop{i}", PropertyAttributes.None, propertyTypes[i], null); var getter = typeBuilder.DefineMethod($"get_Prop{i}", MethodAttributes.Public | MethodAttributes.SpecialName, propertyTypes[i], Type.EmptyTypes); var getterIl = getter.GetILGenerator(); getterIl.Emit(OpCodes.Ldarg_0); getterIl.Emit(OpCodes.Ldfld, field); getterIl.Emit(OpCodes.Ret); var setter = typeBuilder.DefineMethod($"set_Prop{i}", MethodAttributes.Public | MethodAttributes.SpecialName, null, new[] { propertyTypes[i] }); var setterIl = setter.GetILGenerator(); setterIl.Emit(OpCodes.Ldarg_0); setterIl.Emit(OpCodes.Ldarg_1); setterIl.Emit(OpCodes.Stfld, field); setterIl.Emit(OpCodes.Ret); prop.SetGetMethod(getter); prop.SetSetMethod(setter); } var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, propertyTypes); var ctorIl = ctorBuilder.GetILGenerator(); for (int i = 0; i < propertyTypes.Length; i++) { ctorIl.Emit(OpCodes.Ldarg_0); ctorIl.Emit(OpCodes.Ldarg, i + 1); ctorIl.Emit(OpCodes.Stfld, fields[i]); } ctorIl.Emit(OpCodes.Ret); return typeBuilder.CreateType(); } } private class ClientProjectionVisitor : ExpressionVisitor { private readonly Dictionary<MemberInfo, Expression> _memberMappings; public ClientProjectionVisitor(Dictionary<MemberInfo, Expression> memberMappings) { _memberMappings = memberMappings; } protected override Expression VisitMemberInit(MemberInitExpression node) { var bindings = new List<MemberBinding>(); var paramExpr = Expression.Parameter(typeof(object)); foreach (var binding in node.Bindings.Cast<MemberAssignment>()) { var member = binding.Member; var propIndex = _memberMappings.Keys.ToList().IndexOf(member); var anonProp = Expression.Property(Expression.Convert(paramExpr, _memberMappings[member].Type.DeclaringType), $"Prop{propIndex}"); if (member.PropertyType == typeof(Id)) { var convertExpr = Expression.Call(typeof(IdHelper), nameof(IdHelper.ToId), Type.EmptyTypes, anonProp); bindings.Add(Expression.Bind(member, convertExpr)); } else { bindings.Add(Expression.Bind(member, anonProp)); } } return Expression.Lambda<Func<object, TResult>>(Expression.MemberInit(node.NewExpression, bindings), paramExpr); } } }
2. 使用方式
直接调用扩展方法替代原有的Select,语法保持一致:
var orders = db.Orders .SelectWithIdConversion(x => new Order() { OrderId = x.order_id.ToId(), OrderDesc = x.order_desc }) .ToList();
方案优势
- 减少冗余代码:无需手动编写两次
Select,保持查询语法简洁 - 降低内存占用:服务器端仅返回DTO所需的原始字段,避免传输不必要的数据
- 兼容多种转换方式:自动处理
ToId()调用、(Id)intValue显式转换、new Id(intValue)构造函数三种场景
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

