Chaturvedi Dewashish
Chaturvedi Dewashish

Reputation: 1469

NHibernate Collection of collections fetching, Duplicate data

I have a complex situation, where I need to map three different classes with NHibernate. Class1(Branch.cs) has a collection of Class2(Employee.cs) objects. At the same time Class2 also has a collection of Class3(Contacts.cs) objects.
As data is very huge I used fetch keyword to retrieve data in single query.
I used the query as

Query1 - "from Branch b inner join fetch b.Employee e inner join fetch e.Contacts" - Single query but duplicate results.
Query2 - "from Branch b inner join fetch b.Employee" - Multiple query, required results.

I have used bags in mapping files. The query result seems to having duplicate results. How to remove duplicate results as well as retrieving data in single query. I am including mapping files as well as Classes.

Contacts.hbm.xml

    <?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateSample" namespace="NHibernateSample">
  <class name="Contacts" table="Contacts">
      <id name="EmployeeID"/>
      <property name="EmployeeID"/>
      <property name="Mobile"/>
      <property name="Alternate"/>
  </class>
</hibernate-mapping>

Branch.hbm.xml

    <?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateSample" namespace="NHibernateSample">
  <class name="Branch" table="Branch">
      <id name="BranchCode"/>
      <property name="BranchCode"/>
      <property name="BranchName"/>
      <bag name="EmployeeList" cascade="all-delete-orphan" inverse="false" batch-size="10000">
          <key column="BranchCode"/>
          <one-to-many class="Employee" />
      </bag>
  </class>

</hibernate-mapping>

Employee.hbm.xml

    <?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateSample" namespace="NHibernateSample">
  <class name="Employee" table="Employee">
      <id name="EmployeeId"/>
      <property name="EmployeeId"/>
      <property name="FirstName"/>
      <property name="LastName"/>
      <property name="BranchCode"/>
      <bag name="Contact" cascade="all-delete-orphan" inverse="false" batch-size="10000">
          <key column="EmployeeID"/>
          <one-to-many class="Contacts" />
      </bag>
  </class>
</hibernate-mapping>

Contacts.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace NHibernateSample
{
    public class Contacts
    {
        String employeeID;
        String mobile;
        String alternate;

        public Contacts()
        { }


        public virtual String EmployeeID
        {
            get { return employeeID; }
            set { employeeID = value; }
        }

        public virtual String Mobile
        {
            get { return mobile; }
            set { mobile = value; }
        }        
        public virtual String Alternate
        {
            get { return alternate; }
            set { alternate = value; }
        }
    }
}

Employee.cs

    using System;
using System.Collections.Generic;
using System.Text;

namespace NHibernateSample
{
    public class Employee
    {
        String employeeId;
        String firstName;
        String lastName;
        String branchCode;
        List<Contacts> contact = new List<Contacts>();

        public virtual List<Contacts> Contact
        {
            get { return contact; }
            set { contact = value; }
        }
        public virtual String EmployeeId
        {
            get { return employeeId; }
            set { employeeId = value; }
        }

        public virtual String FirstName
        {
            get { return firstName; }
            set { firstName = value; }
        }

        public virtual String LastName
        {
            get { return lastName; }
            set { lastName = value; }
        }

        public virtual String BranchCode
        {
            get { return branchCode; }
            set { branchCode = value; }
        }

        public Employee()
        { }
    }
}

Branch.cs

    using System.Collections.Generic; 
using System.Text; 
using System; 


namespace NHibernateSample 
{
    [Serializable]
    public class Branch
    {
        private String branchCode;
        private String branchName;
        private IList<Employee> employeeList = new List<Employee>();

        public virtual IList<Employee> EmployeeList
        {
            get { return employeeList; }
            set { employeeList = value; }
        }
        public virtual String BranchCode
        {
            get { return branchCode; }
            set { branchCode = value; }
        }

        public virtual String BranchName
        {
            get { return branchName; }
            set { branchName = value; }
        }

        public Branch() { }
    }
}

Upvotes: 2

Views: 1739

Answers (1)

Manar Husrieh
Manar Husrieh

Reputation: 491

This is an issue with NHibernate which they won't fix.

You can get the result correct on the first level by applying the DistinctEntityTransformer, this is a built in transformer in the NHibernate and u should apply it to the queryover object using

QueryOver.TranformUsing(Tranformers.DistinctEntityTransformer).

On multilevel such as your problem you will need to write your own transformer and use it instead of the distinct entity transformer. you can use similar logic to the distinct entity transformer provided by NHibernate to fix the replications at the details.

Edit: Here is an implementation of this:

public class MultiLevelDistinctEntityTransformer : IResultTransformer
{
    private readonly Dictionary<Type, List<String>> _fetchedCollectionProperties; // used to know which properties are fetched so you don't fetch more details than required

    public MultiLevelDistinctEntityTransformer(Dictionary<Type, List<String>> fetchedCollectionProperties)
    {
        _fetchedCollectionProperties = fetchedCollectionProperties;
    }

    public object TransformTuple(object[] tuple, string[] aliases)
    {
        return tuple.Last();
    }

    public IList TransformList(IList list)
    {
        if (list.Count == 0)
            return list;
        var result = (IList) Activator.CreateInstance(list.GetType());
        var distinctSet = new HashSet<Entity>();
        foreach (object item in list)
        {
            var entity = item as Entity; // Entity is the base class of my nhibernate classes
            if (entity == null)
                continue;
            if (distinctSet.Add(entity))
            {
                result.Add(item);
                HandleItemDetails(item);
            }
        }
        return result;
    }

    private void HandleItemDetails(object item)
    {
        IEnumerable<PropertyInfo> collectionProperties =
            item.GetType().GetProperties().Where(
                prop =>
                prop.IsCollectionProperty()/*extension method which checks if the object inherits from ICollection*/ &&
                _fetchedCollectionProperties.ContainsKey(item.GetType()) &&// get only the fetched details

                _fetchedCollectionProperties[item.GetType()].Contains(prop.Name));
        foreach (PropertyInfo collectionProperty in collectionProperties)
        {
            dynamic detailList = collectionProperty.GetValue(item, null);
            if (detailList != null)
            {
                dynamic uniqueValues =
                    Activator.CreateInstance(
                        typeof (List<>).MakeGenericType(collectionProperty.PropertyType.GetGenericArguments()[0]));
                var distinct = new HashSet<Entity>();
                foreach (var subItem in detailList)
                {
                    var entity = subItem as Entity;
                    if (distinct.Add(entity))
                    {
                        uniqueValues.Add(subItem);
                        HandleItemDetails(subItem);
                    }
                }
                collectionProperty.SetValue(item, uniqueValues, null);
            }
        }
    }
}

Upvotes: 5

Related Questions