0

我想找到一种在 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 的包装器。这似乎工作正常。我打算尝试挂钩方法,但是使用从不调用的方法,所以这不起作用。无论如何,这种方式最终可能会更好。ExpressionVisitorCreateQuery()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 人员提交功能请求并在那里开始讨论吗?

感谢您的想法!

4

0 回答 0