简体   繁体   中英

How to map NHibernate bag with a case-insensitive key

When I attempt to load an entity that has a child entity mapped as a bag, and the key between the child entity and the parent is a string, the resulting bag will not be populated if the key's case does not match the parent's ID property.

If Office.ID == " MyOffice " and PromotionalContent.ContextKey == " myoffice " , the collection does NOT load the promotions, even though NHProfiler shows that it was returned by the database .

If Office.ID == " MyOffice " and *PromotionalContent.ContextKey == " MyOffice ", the collection DOES load .

    <class name="Office" table="Office" lazy="true">
        <id name="ID" column="OfficeNum">
            <generator class="assigned" />
        </id>
      <property name="Name" column="OfficeName" />
      <property name="PhoneNumber" column="PhoneNum" />
      <bag name="Promotions" lazy="true" where="ContextType='Office'"  >
        <key column="ContextKey"/>
        <one-to-many  class="PromotionalContent"/>
      </bag>...

This is NHibernate version 4.0.3.

How can I map it, or fix it so that it always loads regardless of case?

After a few days trying to solve the problem using event listeners, debugging , turns out there is a bug in NHibernate.

I was able to solve the problem by changing the actual NHibernate source, rebuilding and relinking. The technique I used leaves the actually entity keys untouched, which therefore preserves the case as it was when it came from or went into the database.

When NHibernate loads an entity, a "key" is created that is basically just a marker that is stored and tested in an IDictionary<> that represents the session or a cache of entities and their related, dependent collections. This "key" was not smart enough to do a case-insensitive comparison. In order to do that, I had to modify the hashcode and tweak the Equals() to do case-insensitive comparison so that it always gets a hit if the case within the key is different from the test value.

If you are comfortable building NHibernate and owning the tech debt until it is fixed, here is my patch:

Modify EntityKey.cs, CollectionKey.cs, and CacheKey.cs

  1. GetHashCode() - needs to return a consistent hash code regardless of the case of the key. Otherwise, Equals() will NEVER get called when the case is different.
  2. Equals() - needs to return true regardless of the case. Otherwise, your entity and/or it's child collections will get missed.

I cheated by simply using upper case for comparison, rather than doing the case insensitive compare.

Sample Code:

EntityKey.cs

    public override bool Equals(object other)
    {
        var otherKey = other as EntityKey;
        if(otherKey==null) return false;

        if (Identifier is string && otherKey.Identifier is string)
        {
            object thiskeySanitized = ((string)Identifier).ToUpper();
            object thatkeySanitized = ((string)otherKey.Identifier).ToUpper();
            return otherKey.rootEntityName.Equals(rootEntityName) && identifierType.IsEqual(thatkeySanitized, thiskeySanitized, entityMode, factory);
        }
        return
            otherKey.rootEntityName.Equals(rootEntityName)
            && identifierType.IsEqual(otherKey.Identifier, Identifier, entityMode, factory);
    }

    private int GenerateHashCode()
    {
        int result = 17;
        object sanitizedIdentifier = (identifier is string) ? ((string) identifier).ToUpper() : identifier;
        unchecked
        {
            result = 37 * result + rootEntityName.GetHashCode();
            result = 37 * result + identifierType.GetHashCode(sanitizedIdentifier, entityMode, factory);
        }
        return result;
    }

CollectionKey.cs

    public override bool Equals(object obj)
    {
        CollectionKey that = (CollectionKey)obj;
        if (this.key is string && that.key is string)
        {
            object thiskeySanitized = ((string)this.key).ToUpper();
            object thatkeySanitized = ((string)that.key).ToUpper();
            return that.role.Equals(role) && keyType.IsEqual(thatkeySanitized, thiskeySanitized, entityMode, factory);
        }
        return that.role.Equals(role) && keyType.IsEqual(that.key, key, entityMode, factory);
    }

    private int GenerateHashCode()
    {
        int result = 17;
        unchecked
        {
            result = 37 * result + role.GetHashCode();
            object sanitizedIdentifier = (key is string) ? ((string)key).ToUpper() : key;
            result = 37 * result + keyType.GetHashCode(sanitizedIdentifier, entityMode, factory);
        }
        return result;
    }

CacheKey.cs

    public CacheKey(object id, IType type, string entityOrRoleName, EntityMode entityMode, ISessionFactoryImplementor factory)
    {
        key = id;
        this.type = type;
        this.entityOrRoleName = entityOrRoleName;
        this.entityMode = entityMode;
        object sanitizedIdentifier = (key is string) ? ((string)key).ToUpper() : key;
        hashCode = type.GetHashCode(sanitizedIdentifier, entityMode, factory);
    }

    public override bool Equals(object obj)
    {
        CacheKey that = obj as CacheKey;
        if (that == null) return false;
        if (key is string && that.key is string)
        {
            object thiskeySanitized = ((string)key).ToUpper();
            object thatkeySanitized = ((string)that.key).ToUpper();
            return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(thiskeySanitized, thatkeySanitized, entityMode);
        }
        return entityOrRoleName.Equals(that.entityOrRoleName) && type.IsEqual(key, that.key, entityMode);
    }

The issue was reported to the NHibernate team back in 2015 - https://nhibernate.jira.com/browse/NH-3833 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM