我想找到一种在 EF Code First 中获取强类型主键的方法。我的目标是避免意外地将一种类型的 ID 分配给另一种类型的 ID。这是一个应用了这种野兽的实体的示例:
public partial class AccessMethod : BaseEntity
{
internal virtual Guid ID_
{
get { return ID.Value; }
set { ID = new ID<AccessMethod>(value); }
}
public ID<AccessMethod> ID { get; set; }
internal virtual Guid LocationID_
{
get { return LocationID.Value; }
set { LocationID = new ID<Location>(value); }
}
public ID<Location> LocationID { get; set; }
public virtual string Description { get; set; }
#region Navigation properties
public virtual Location Location { get; set; }
#endregion
}
这是代码ID<>
:
public class StrongID<T, TEntity> : IEquatable<StrongID<T, TEntity>>
{
readonly T _value;
public StrongID()
{
_value = default(T);
}
public StrongID(T value)
{
_value = value;
}
public T Value { get { return _value; } }
public override bool Equals(object obj)
{
if (obj is StrongID<T, TEntity>)
{
return Equals(obj as StrongID<T, TEntity>);
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ (_value == null ? 0 : _value.GetHashCode());
}
public override string ToString()
{
return "ID<" + typeof(T).Name + ">(" + _value + ")";
}
public bool Equals(StrongID<T, TEntity> other)
{
if (other == null) return false;
var thisIsNull = this._value == null;
var otherIsNull = other._value == null;
if (thisIsNull || otherIsNull) return thisIsNull == otherIsNull;
return this._value.Equals(other._value);
}
//commented for now
//public static implicit operator T(StrongID<T, TEntity> id)
//{
// return id.Value;
//}
}
public class ID<TEntity> : StrongID<Guid, TEntity>
where TEntity : BaseEntity
{
public ID() { }
public ID(Guid id) : base(id) { }
}
这个想法是让 EF 知道ID_
,但是这个属性对项目的其余部分是隐藏的,并且通过 暴露出来ID
,正如您所看到的,它包括实体的类型。
由于我为我的实体使用显式映射,因此我能够编写一个基类,它使用反射自动忽略StrongID<>
属性并将具有相同名称的属性加上下划线映射到删除下划线的列。
我知道 EF 根本不支持主键的自定义类型,只支持 .NET 的内置标量类型。所以我要修改所有查询的表达式树的疯狂路线,将所有访问改为ID
指向ID_
(并对其他ID<>
属性做同样的事情LocationID
)。
我有一个在其方法IQueryProvider
中调用 an 的包装器。这似乎工作正常。我打算尝试挂钩方法,但是使用从不调用的方法,所以这不起作用。无论如何,这种方式最终可能会更好。ExpressionVisitor
CreateQuery()
Execute()
ToList()
GetEnumerator()
Execute()
不幸的是,这就是我的计划开始分崩离析的地方。表达式树的使用非常复杂,并且有许多细微差别,如果不运行真实代码和检查事物,就不可能知道在哪里可以访问什么。
这是我到目前为止所拥有的:
public class ConvertStrongIDExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is ParameterExpression)
{
if (IsTypeStrongID(node.Type))
{
var ntex = node.Expression as ParameterExpression;
if (typeof(BaseEntity).IsAssignableFrom(ntex.Type))
{
//ntex.Type is an entity type, node refers to a strongid member on that type
var property = ntex.Type.GetProperty(node.Member.Name + "_", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return Expression.MakeMemberAccess(node.Expression, property);
}
}
}
return base.VisitMember(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (IsTypeStrongID(node.Type))
{
var value = node.Type.GetProperty("Value").GetMethod.Invoke(node.Value, null);
return Expression.Constant(value);
}
return base.VisitConstant(node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return base.VisitLambda<T>(node);
}
bool IsTypeStrongID(Type t)
{
var strongIDType = typeof(StrongID<,>);
while (t != null)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == strongIDType) return true;
t = t.BaseType;
}
return false;
}
}
我不确定我需要覆盖哪些其他方法,我不知道我ExpressionVisitor
是否正确使用开始。VisitMember()
两者VisitConstant()
似乎都可以正常工作。目前,我得到了这个异常(它恰好是在 中生成的VisitLambda()
):
Reference equality is not defined for the types 'System.Guid' and 'Core.ID`1[Core.Domain.Location]'.
它吐槽的表达是这样的:
o => o.LocationID == new ID<Location>(locationID)
我以为表达式new ID<Location>(locationID)
正在变成一个常数?好像不是?这就是为什么我不确定我是否使用 ExpressionVisitor。似乎表达式没有按任何特定顺序访问。或者,也许我做错了什么。
呃,我刚刚意识到这根本不适用于匿名类型,因为它们没有镜像属性。我必须动态地创建一个新类型,将其交换到表达式树中,并在任何原始类型的属性被引用的地方进行修复。可行,但这听起来像是一场噩梦。
然后是上述所有当前未知的性能影响......
所以我想我想知道这样的事情,有人对我如何让这个概念很好地运作有任何想法吗?以前有没有人尝试过这样的事情,无论是使用 EF 还是其他 ORM?我应该向 EF 人员提交功能请求并在那里开始讨论吗?
感谢您的想法!