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
を呼び出すラッパーがあります。これはうまくいくようです。代わりにメソッドにフックしようとしましたが、 を呼び出さない を使用しているため、うまくいきませんでした。この方法は、最終的にはおそらくうまくいくでしょう。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 の人々に機能要求を送信し、そこで議論を開始する必要がありますか?
あなたの考えは大歓迎です!