65.9K
CodeProject 正在变化。 阅读更多。
Home

Myotragus 元组 - 自动化 NHibernate 复合键

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (6投票s)

2011 年 5 月 15 日

CPOL

4分钟阅读

viewsIcon

34364

downloadIcon

349

自动创建相等成员 (Equals 和 GetHashCode)。

引言

主键通常是自动递增的整数或GUID。不幸的是,许多领域需要更多。主键不是定义为单列,而只是唯一。迟早你会发现自己需要复合主键。使用复合主键时,能够将相等函数应用于键值非常有用。实际上,NHibernate 要求复合主键重写相等成员 (EqualsGetHashCode)。本文旨在使实现相等函数的过程变得轻松。

目录

  1. 模型
  2. 自动相等函数
  3. Using the Code
  4. 统计
  5. 结论
  6. 有用链接

模型

让我们来看一下下面的领域。

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
    assembly="Myotragus.Data.Tupples.Tests" 
    namespace="Myotragus.Data.Tupples.Tests.Domain"> 
    
    <class name="Product"> 
        <id name="Id" column="ProductId">
            <generator class="identity"/>
        </id>
        <property name="Name"/> 
    </class> 
    
    <class name="Category"> 
        <id name="Id" column="CategoryId"> 
            <generator class="identity"/> 
        </id> 
        <property name="Name"/> 
    </class>

    <class name="CategoryProducts"> 
        <composite-id name="Key"> 
            <key-property name="ProductId"/> 
            <key-property name="CategoryId"/> 
        </composite-id>
        <property name="CustomDescription"/>
    </class>
</hibernate-mapping>
代码片段 1:示例域的NHibernate映射

在之前的代码中,定义了一个包含三个实体的域。ProductCategory 不需要解释。另一方面,CategoryProducts 需要解释。如果您有NHibernate经验,您可能会在ProductCategory 中都使用集合来表示多对多关系。我更喜欢让我的POCO保持关系清晰,但这只是我个人的偏好。为了这个例子,我们将按照我在现实生活中使用这种映射的方式来使用它。现在让我们看一下POCO。

public class Category 
{ 
    public virtual int Id { get; set; } 
    public virtual string Name { get; set; } 
}

public class Product 
{ 
    public virtual int Id { get; set; } 
    public virtual string Name { get; set; } 
}
代码片段 2:简单的POCO:Product 和 Category
public class CategoryProducts 
{ 
    public CategoryProducts() 
    { 
        Key = new CategoryProductsKey() ; 
    } 

    public virtual CategoryProductsKey Key { get; set; } 
    public virtual int ProductId 
    { 
        get { return Key.ProductId ;} 
        set { Key.ProductId = value ; } 
    } 
    public virtual int CategoryId 
    { 
        get { return Key.CategoryId;} 
        set { Key.CategoryId = value ;} 
    } 
    public virtual string CustomDescription { get;set;} 
}
代码片段 3:不太简单的POCO

CategoryProducts 使用具有两个字段的复合键,一个引用Product,另一个引用Category。NHibernate 强制重写复合键类型的相等成员。现在让我们来看一下键的实现。

public class CategoryProductsKey
{    
    public int ProductId { get; set; }
    public int CategoryId { get; set; }

    public override in GetHashCode()
    {
        return ProductId ^ CategoryId ;
    }

    public override Equals(object x)
    {
        return Equals(x as CategoryProductsKey) ;
    }
    public bool Equals(CategoryProductsKey x)
    {
        return x != null && x.ProductId == ProductId &&
            x.CategoryId == CategoryId ;
    }
}
代码片段 4:复合键实现

如您所见,相等成员的实现非常简单。实际上,在大多数情况下它非常直接。

自动相等函数

现在让我们正式地 (C#) 定义一个用于复合键的简单相等实现。

public bool AreEqual(TKey x, TKey)
{
    var result = true ;

     foreach(var property in typeof(TKey).GetProperties(All))
        result &= object.Equals(property.GetValue(x), property.GetValue(y));

    return result ;
}
代码片段 5:简单的Equals实现

可以进行一些优化,但现在它们并不重要。

public in GetHashCode(TKey x)
{
    var getHashCodeMethod = typeof(object).GetMethod("GetHashCode") ;
    var result = 0;

    foreach(var property in typeof(TKey).GetProperties(All))
        return ^= getHashCodeMethod(property.GetValue(x));

    return result ;
}
代码片段 6:简单的GetHashCode实现

使用代码

明白了吗?我们现在要做的是将此定义封装在一个类中。这个类将生成相等函数,客户端稍后可以使用它来比较复合键。使用它看起来就像

var o1 = new TKey { P1 = v11, P2 = v21, P3 = v31 } ;
var o2 = new TKey { P1 = v21, P2 = v22, P3 = v33 } ;

Func<TKey, TKey, bool> AreEquals = 
   EqualityFunctionsGenerator<TKey>.CreateEqualityComparer();

var r = AreEquals(o1, o2) ; // would work as expected

Func<TKey, int> GetHashCode = EqualityFunctionsGenerator<TKey>.CreateGetHashCode();

var c1 = GetHashCode(o1) ;
var c2 = GetHashCode(o2) ;
代码片段 7:使用自动生成的函数

您可以想到成千上万种用途,但实际上,您可以赋予这些函数的最佳用途是重写对象的定义并让客户端完成其余工作。

public class Tupple<TObject> : IEquatable<TObject> 
    where TObject : class 
{ 
    private static readonly Func<TObject, int> GetHashCodeMethod = 
            EqualityFunctionsGenerator<TObject>.CreateGetHashCode(); 

    private static readonly Func<TObject, TObject, bool> EqualsMethod = 
            EqualityFunctionsGenerator<TObject>.CreateEqualityComparer(); 

    public override bool Equals(object obj) 
    { 
        return Equals(obj as TObject); 
    } 

    public override int GetHashCode() 
    { 
        var @this = ((object)this) as TObject; 
        if (@this == null) return 0 ; 
        return GetHashCodeMethod(@this); 
    } 

    public bool Equals(TObject other) 
    { 
        var @this = ((object)this) as TObject ; 
        if (other == null || @this == null) return false ; 
        return EqualsMethod(@this, other); 
    } 
}
代码片段 8:Tupple 的实现

扩展一个元组将使一切正常工作。

public class CategoryProductsKey : Tupple<CategoryProductsKey>
{
    public virtual int ProductId { get; set; }
    public virtual int CategoryId { get; set; }
} 
代码片段 9:哇!太容易了

只是缺少函数生成器的整个实现,这里就是

public class EqualityFunctionsGenerator<TObject>
{
    public static readonly Type TypeOfTObject = typeof(TObject);
    public static readonly Type TypeOfBool = typeof(bool);
    public static readonly MethodInfo MethodEquals = 
        typeof(object).GetMethod("Equals", 
        BindingFlags.Static | BindingFlags.Public);
    public static readonly MethodInfo MethodGetHashCode = 
        typeof(object).GetMethod("GetHashCode",
        BindingFlags.Instance | BindingFlags.Public);

    public static Func<TObject, TObject, bool> CreateEqualityComparer()
    {
        var x = Expression.Parameter(TypeOfTObject, "x");
        var y = Expression.Parameter(TypeOfTObject, "y");
        
        var result = (Expression)Expression.Constant(true, TypeOfBool);

        foreach (var property in GetProperties())
        {
            var comparison = CreatePropertyComparison(property, x, y);
            result = Expression.AndAlso(result, comparison);
        }

        return Expression.Lambda<Func<TObject, TObject, bool>>(result, x, y).Compile();
    }

    private static Expression CreatePropertyComparison(PropertyInfo property, 
                   Expression x, Expression y)
    {
        var type = property.PropertyType;

        var propertyOfX = GetPropertyValue(x, property);
        var propertyOfY = GetPropertyValue(y, property);

        return (type.IsValueType)? CreateValueTypeComparison(propertyOfX, propertyOfY) 
            :CreateReferenceTypeComparison(propertyOfX, propertyOfY);
    }

    private static Expression GetPropertyValue(Expression obj, PropertyInfo property)
    {
        return Expression.Property(obj, property);
    }

    private static Expression CreateReferenceTypeComparison(Expression x, Expression y)
    {
        return Expression.Call(MethodEquals, x, y);
    }

    private static Expression CreateValueTypeComparison(Expression x, Expression y)
    {
        return Expression.Equal(x, y);
    }

    public static IEnumerable<PropertyInfo> GetProperties()
    {
        return TypeOfTObject.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    }

    public static Func<TObject, int> CreateGetHashCode()
    {
        var obj = Expression.Parameter(TypeOfTObject, "obj");
        var result = (Expression)Expression.Constant(0);

        foreach (var property in GetProperties())
        {
            var hash = CreatePropertyGetHashCode(obj, property);
            result = Expression.ExclusiveOr(result, hash);
        }

        return Expression.Lambda<Func<TObject, int>(result, obj).Compile();
    }

    private static Expression CreatePropertyGetHashCode(Expression obj, PropertyInfo property)
    {
        var type = property.PropertyType;

        var propertyOfObj = GetPropertyValue(obj, property);

        return type.IsValueType ? CreateValueTypeGetHashCode(propertyOfObj) 
            : CreateReferenceTypeGetHashCode(propertyOfObj);
    }

    private static Expression CreateReferenceTypeGetHashCode(Expression value)
    {
        return Expression.Condition(
            Expression.Equal(Expression.Constant(null), value),
            Expression.Constant(0),
            Expression.Call(value, MethodGetHashCode));
    }

    private static Expression CreateValueTypeGetHashCode(Expression value)
    {
        return Expression.Call(value, MethodGetHashCode);
    }

    private static Expression CheckForNull(Expression value)
    {
        return Expression.Condition(
            Expression.Equal(Expression.Constant(null), value),
            Expression.Constant(0),
            value);
    }
}
代码片段 10:相等函数生成器

统计数据

如果它很慢,它有什么用?我进行了一些性能测试,结果不如预期好,但足够好。测试是在一台1 CPU/1GB VirtualBox虚拟机上运行的,该虚拟机运行在Phenom II x6 1055T 2.46Ghz处理器上。创建了两种元组类型来进行测试,一种是自动实现的,另一种是手动实现的。性能结果将与intPoint 和手动实现的元组的等效测试一起显示。

public class AutomaticCompositeKey : Tupple<AutomaticCompositeKey> 
{ 
    public string KeyField1 { get; set; } 
    public int KeyField2 { get; set; } 
    public int KeyField3 { get; set; } 
}

public class ImplementedCompositeKey
{
    public string KeyField1 { get; set; } 
    public int KeyField2 { get; set; } 
    public int KeyField3 { get; set; } 

    public override int GetHashCode {...}
    public override bool Equals(object x) {...}
}
代码片段 11:用于测试的元组

Equals 测试结果

测试 1000万次测试 1亿次测试 10亿次测试
int 相等性测试 0 0.04 0.566
使用==运算符的Point 相等性 0.01 0.1 1.398
使用EqualsPoint 相等性 0.08 0.671 8.12
手动实现的元组 0.06 0.491 5.31
自动生成的元组 0.19 1.122 13.2

GetHashCode 测试结果

测试 1000万次测试 1亿次测试 10亿次测试
Point 0.01 0.05 0.496
手动实现的元组 0.09 0.831 9.136
自动生成的元组 0.23 1.402 15.03

对自动生成的元组进行了一些额外的测试以确定每秒执行次数。还有一个包含线性回归的Excel文件可用于确定以下值。

函数 每秒百万次
Equals 85.4
GetHashCode 67.53

结论

每当我发现重复性任务时,我都会尝试将其自动化。反射、Emit和现在的Linq.Expressions在这方面非常有用。这个小包是我最近完成的一个库的一部分。希望你会发现它有用。

有用链接

© . All rights reserved.