如何将[NotMapped]属性作为代理用于EF Core LINQ查询的服务端解析?
问题核心
你的代码中RelatedId是[NotMapped]属性,EF Core在解析LINQ表达式生成SQL时,只会识别映射到数据库列的属性,因此直接使用x => x.RelatedId == RelatedId会抛出异常——EF无法将该未映射属性转换为对应的数据库字段。
解决方案:表达式树替换
通过自定义表达式访问者,将针对IFileRelated.RelatedId的表达式访问,替换为实体类中实际映射的属性(如ClientFile.ClientId、CompanyFile.CompanyId),让EF Core能正确生成SQL。
步骤1:定义属性映射(两种可选方式)
方式1:手动维护映射字典
创建字典记录每个IFileRelated实现类对应的实际属性名称:
private static readonly Dictionary<Type, string> RelatedIdPropertyMap = new() { { typeof(ClientFile), nameof(ClientFile.ClientId) }, { typeof(CompanyFile), nameof(CompanyFile.CompanyId) } };
方式2:用特性自动识别(更灵活)
先定义特性用于标记实际映射属性:
[AttributeUsage(AttributeTargets.Property)] public class RelatedIdSourceAttribute : Attribute { public string PropertyName { get; } public RelatedIdSourceAttribute(string propertyName) => PropertyName = propertyName; }
然后在实体类的RelatedId属性上添加特性:
public class ClientFile : IFileRelated { public int ClientFileId { get; set; } public int ClientId { get; set; } public int FileId { get; set; } [NotMapped] [RelatedIdSource(nameof(ClientId))] public int RelatedId { get => ClientId; set => ClientId = value; } } public class CompanyFile : IFileRelated { public int CompanyFileId { get; set; } public int CompanyId { get; set; } public int FileId { get; set; } [NotMapped] [RelatedIdSource(nameof(CompanyId))] public int RelatedId { get => CompanyId; set => CompanyId = value; } }
步骤2:实现表达式访问者替换属性
创建表达式访问者类,用于替换表达式中的RelatedId访问节点:
private class ReplaceMemberExpressionVisitor : ExpressionVisitor { private readonly MemberInfo _oldMember; private readonly Expression _newExpression; public ReplaceMemberExpressionVisitor(MemberInfo oldMember, Expression newExpression) { _oldMember = oldMember; _newExpression = newExpression; } protected override Expression VisitMember(MemberExpression node) { if (node.Member == _oldMember) return _newExpression; return base.VisitMember(node); } }
步骤3:编写表达式转换方法
根据选择的映射方式,实现表达式转换逻辑:
对应方式1(字典映射):
private static Expression<Func<T, bool>> ReplaceRelatedIdWithActualProperty<T>(Expression<Func<T, bool>> predicate) where T : class, IFileRelated { if (!RelatedIdPropertyMap.TryGetValue(typeof(T), out var actualPropertyName)) throw new InvalidOperationException($"未定义{typeof(T).Name}的RelatedId映射"); var parameter = predicate.Parameters[0]; var actualProperty = Expression.Property(parameter, actualPropertyName); var visitor = new ReplaceMemberExpressionVisitor( typeof(IFileRelated).GetProperty(nameof(IFileRelated.RelatedId))!, actualProperty); var modifiedBody = visitor.Visit(predicate.Body); return Expression.Lambda<Func<T, bool>>(modifiedBody, parameter); }
对应方式2(特性映射):
private static string GetActualRelatedIdPropertyName<T>() where T : class, IFileRelated { var relatedIdProp = typeof(T).GetProperty(nameof(IFileRelated.RelatedId)); var attribute = relatedIdProp?.GetCustomAttribute<RelatedIdSourceAttribute>(); if (attribute == null) throw new InvalidOperationException($"{typeof(T).Name}.RelatedId未标记{nameof(RelatedIdSourceAttribute)}"); return attribute.PropertyName; } private static Expression<Func<T, bool>> ReplaceRelatedIdWithActualProperty<T>(Expression<Func<T, bool>> predicate) where T : class, IFileRelated { var actualPropertyName = GetActualRelatedIdPropertyName<T>(); var parameter = predicate.Parameters[0]; var actualProperty = Expression.Property(parameter, actualPropertyName); var visitor = new ReplaceMemberExpressionVisitor( typeof(IFileRelated).GetProperty(nameof(IFileRelated.RelatedId))!, actualProperty); var modifiedBody = visitor.Visit(predicate.Body); return Expression.Lambda<Func<T, bool>>(modifiedBody, parameter); }
步骤4:修改泛型查询方法
使用转换后的表达式执行查询:
public List<T> GetFilesForRelated<T>(int relatedId) where T : class, IFileRelated { var predicate = ReplaceRelatedIdWithActualProperty<T>(x => x.RelatedId == relatedId); return _context.Set<T>().Where(predicate).ToList(); }
原理说明
表达式访问者会遍历LINQ表达式树,将所有访问IFileRelated.RelatedId的节点替换为对应实体的实际映射属性节点,这样EF Core就能将转换后的表达式正确解析为SQL语句,实现通用的泛型查询逻辑。
内容的提问来源于stack exchange,提问作者statler
相关产品推荐
相关产品推荐

