0

I have following tables

  1. Car(CarId, Name,...)
  2. CarPartLink(CarId, PartId)
  3. Part(PartId, Name)
  4. SubPartLink(Parent_PartId, Child_PartId) where both parent and child comes from Part table

I want Car object to have list of Parts including the SubParts, here Car does not have direct relationship with Part its Subparts and neither Part has a direct relation with Subparts.

i.e.

class Car
{
  public virtual string Id { get; set; }
  public virtual string Name { get; set; }
  public virtual ICollection<Parts> AllParts { get; set; } //This should include all the parts and its subparts assuming subparts are only one level deep
}

How to make the map for the same in Fluent NHibernate?

Edit 1:

If it is not possible in Fluent NHibernate but possible in NHibernate mapping then also fine with me.

I am using Fluent NHibernate version: 1.4.0.1 and NHibernate version: 3.3.3

Edit 2:

I am also fine if I get only the subparts, or the id's of subparts in the map.

Edit 3:

Each vehicle( here in the example mentioned as Car) has more than 1 million parts and subparts combined, out of which user would be actually be using few 100 parts depending on the conditions. e.g. Get all parts that are weighing 100 kg or get all parts that are of type "Screw" etc. I will be needing these data in read only mode.

keyr
  • 990
  • 13
  • 27

1 Answers1

1

There is already a fine tutorial on relationship mappings in the FluentNH wiki. I suggest you read the guide, or even better, follow it step-by-step. Assuming the following entities:

public class Car
{
    public virtual int CarId { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Part> AllParts {get; set;}

    public Car()
    {
        AllParts = new List<Part>();
    }
}

public class Part
{
    public virtual int PartId { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Car> AllCars {get; set;}

    //never tried mapping a many-to-many on the same entity, but this should work...
    public virtual IList<Part> ParentParts {get; set;}
    public virtual IList<Part> SubParts {get; set;}

    public Part()
    {
        AllCars = new List<Car>();
        ParentParts = new List<Part>();
        SubParts = new List<Part>();
    }   
}

Your mapping will probably be something like this:

public class CarMap : ClassMap<Car>
{
    public CarMap()
    {
        Id(x => x.CarId);
        Map(x => x.Name);
        HasManyToMany(x => x.AllParts)
            //depending on your logic, you would either set .Inverse here or in the PartMap
            .Table("CarPartLink")
            .ParentKeyColumn("CarId")
            .ChildKeyColumn("PartId")
            .Cascade.All();
    }
}   

public class PartMap : ClassMap<Part>
{
    public PartMap()
    {
        Id(x => x.PartId);
        Map(x => x.Name);
        HasManyToMany(x => x.AllCars)
            .Table("CarPartLink")
            .ParentKeyColumn("PartId")
            .ChildKeyColumn("CarId")
            .Cascade.All();
        HasManyToMany(x => x.ParentParts)
            .Table("SubPartLink")
            .ParentKeyColumn("Parent_PartId")
            .ChildKeyColumn("Child_PartId")
            .Inverse() //saving done from the child side of the relationship, right?
            .Cascade.All();
        HasManyToMany(x => x.SubParts)
            .Table("SubPartLink")
            .ParentKeyColumn("Child_PartId")
            .ChildKeyColumn("Parent_PartId")
            .Cascade.All();
    }
}   

If the above doesn't work, let me know. Apparently there is a bug in some versions of FNH where you will need to employ a special workaround. You can find the workaround in this question (look for self-submitted answer by the OP), which is based on the same scenario as yours (many-many on the same entity).

EDIT: If you want to obtain all the parts and subparts for a Car, you will need to recursively access the SubParts for every single Part in your Car.AllParts collection.

Car Car1 = new Car();
//code to get car object
IList<Part> AllParts = new List<Part>();
GetAllParts(Car.AllParts, ref AllParts); //call a recursive method to add all the parts and subparts to the list
//do something with the list


public void GetAllParts(IList<Part> parentList, ref IList<Part> partsList)
{
    foreach (Part part in parentList)
    {
        if (!partsList.Contains(part)) //validate if the list already contains the part to prevent replication
            partsList.Add(part); //add this part to the list
        if (part.SubParts.Count > 0) //if this part has subparts
            GetSubParts(part.SubParts, ref partsList); //add all the subparts of this part to the list too
    }
}

Edit2: This blog post seems to be exactly what you need...

session.CreateQuery(
        "select parts from Car as car " +
        "join car.AllParts as parts join fetch parts.SubParts where ...")
         .SetResultTransformer(new DistinctRootEntityResultTransformer())
         .List<Employee>();
Community
  • 1
  • 1
JW Lim
  • 1,794
  • 3
  • 21
  • 41
  • Can you tell me which version of FNH and nHibernate you are using? – keyr Feb 09 '14 at 05:53
  • @keyr I believe I am using NH 3.3.0 and FNH 1.3. Does the above not work for you? – JW Lim Feb 09 '14 at 08:11
  • No it did not. I had 2 parts for a car and each part had one sub part. So in all 4 parts i was expecting in my AllParts collection but i am getting only two parts. If you are getting all 4 parts then i must be doing something wrong or on a wrong version. – keyr Feb 09 '14 at 08:15
  • @keyr The logic is correct. If your car only has 2 parts, then `Car.AllParts` should return only 2. If you wish to obtain the subparts of those parts too, then you'll need to take it one step further. I've edited my post to include the code for what you need. – JW Lim Feb 09 '14 at 15:21
  • My first approach was query based, which got changed to OOPs type model approach same as that of your approach. Trouble was the vehicle was having more than a million parts and subparts together and out of which depending upon the need (applying where condition) user would be needing few 100 records. With this method one will have many select queries will be fired at the client side which is huge time consumer and hence this approach is not what i wanted and hence the question posted. I should have clarified this in the beginning itself. Sorry for the trouble. – keyr Feb 11 '14 at 15:31
  • @keyr Is it essential for you to obtain *all* parts and subparts of the car? I mean, that's obviously going to be resource-heavy, especially if you have *that* many records. Could you make do with one or two levels deep e.g. only parts and their direct subparts? If you still need the entire collection of children then I'm afraid I'm not aware of a simple/lightweight solution to your problem. – JW Lim Feb 11 '14 at 15:53
  • 1
    @keyr See the latest edit in my answer. I hope that helps solve your problem. If that doesn't work disable lazy-loading and try again. – JW Lim Feb 12 '14 at 06:01
  • 1
    +1 for the answer. I did not want a query rather a map. I have currently created a view which does what i wanted and used as table in the map. I am currently having some difficulty in the versioning mechanism which I shall try to solve and post my answer. – keyr Feb 13 '14 at 09:10