How to map unrelated(no direct relation) tables in Fluent NHibernate - c#

I have following tables
Car(CarId, Name,...)
CarPartLink(CarId, PartId)
Part(PartId, Name)
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.

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>();

Related

C# EF6: Two navigation properties of the same type

Simply, in C# EF6, how do you map two navigation properties to the same table while keeping their result sets separate? In plain English, I have a class that I want two collections of in another class. In other words, I want two collections of the same type but with different elements. Unfortunately, EF6 seems to treat both collections the same and gives them both the same elements (every record in the table).
The best I found from dozens of StackOverflow answers was this, but it has the problem described. In this example, a Father has many Sons and many Daughters, and they each have the one Father. Ideally, both Sons and Daughters can be stored in the same table Child.
class Father
{
[Key]
public long Id { get; set; }
public virtual ICollection<Child> Sons { get; set; }
public virtual ICollection<Child> Daughters { get; set; }
}
class Child
{
[Key]
public long Id { get; set; }
public long FatherId_1 { get; set; }
public Father Father_1 { get; set; }
public long FatherId_2 { get; set; } // One for each collection???
public Father Father_2 { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(e => e.Father_1)
.WithMany(e => e.Sons)
.HasForeignKey(e => e.FatherId_1);
modelBuilder.Entity<Child>()
.HasRequired(e => e.Father_2)
.WithMany(e => e.Daughters)
.HasForeignKey(e => e.FatherId_2);
}
The problem with this is, when reading the data back from the Child table, it doesn't discriminate between Sons and Daughters. That is, the Sons collection will not only contain Sons but also Daughters, and so will the Daughters collection. I might have expected EF6 to try to use a discriminator column, but it doesn't.
Question: How do you map two navigation properties to the same table and still be able to read its records back into their corresponding navigation properties? Or, is the example correct, and my problem is elsewhere? Or, is this not possible, and they need to be mapped to their own tables (with identical schemas).
I am a little confused, with your explanation. But I found some code for what I understand:
For "It's one model "Child" that needs to be split into two collections" :
modelBuilder.Entity<Child>()
.Map(m =>
{
m.Properties(t => new { t.Id /*other props*/ });
m.ToTable("Sons");
})
.Map(m =>
{
m.Properties(t => new { t.Id /*other props*/});
m.ToTable("Daughters");
});
I think you don't require separate mappings altogether to bifurcate same FatherId for Son and daughter. The better solution is to make a unique identifier in Child Table like enum as previous answer suggests or add a Gender field in your Child model and keep only one mapping of Father model. (I don't know your exact requirements, but Father, Child can also be generalized in one super class human or person in strict Object Oriented terms).
But since I am not sure of your requirements, if you really want to continue with your mapping then the solution I propose is this.
class Father
{
[Key]
public long Id { get; set; }
public int SonId{get;set;}
public int DaughterId{get;set;}
[ForeignKey("SonId")]
public virtual Child Child_Son{get;set;}
[ForeignKey("DaughterId")]
public virtual Child Child_Son{get;set;}
}
class Child
{
[Key]
public long Id { get; set; }
public string Gender{get;set;}
}
Explanation:
Two foreign keys each for Son and Daughter of same Child class in Father class will easily help you achieve separate collections using basic Linq or any sql query. Moreover, This preserves the rule "If parent exists only then child exists".
I found a way to solve this problem by using a domain object backed by a state object. Basically, you use a state object to store the data the way EF likes, and your domain object is what exposes that data to the rest of your application. For example:
public class Father
{
//--- Constructor ---
internal Father(FatherState state)
{
State = state;
}
//--- Properties ---
public long Id => State.Id;
public IList<Child> Sons => Children.Where(child => child.Type == ChildType.Son).ToList().AsReadOnly();
public IList<Child> Daughters => Children.Where(child => child.Type == ChildType.Daughter).ToList().AsReadOnly();
//--- Methods ---
public void AddChild(Child child)
{
State.Children.Add(child);
}
public void RemoveChild(Child child)
{
State.Children.Remove(child);
}
}
internal class FatherState
{
[Key]
public long Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public long Id { get; set; }
public long FatherId { get; set; }
public Father Father { get; set; }
public ChildType Type { get; set; }
}
public enum ChildType
{
Son,
Daughter
}
Of course, this can only be used with the repository pattern because it has to translate the FatherState object provided by EF into the Father object consumed by the application.
I think the real solution is to switch to a fully featured ORM like NHibernate. EF is far too underdeveloped, and that's only gotten worse with .NET Core. I don't even see how it's possible to do proper DDD with EF. A lot of developers must be compromising on their models. NHibernate has a lot of advantages, and the only caveat I've found is it doesn't provide async methods, but there's one or two arguments to made against it, and it can be done anyway. There's also forks of NHibernate that provide them as a last resort.
What I could see in your modeling is that, the child entity needs to have property fatherId and gender. Check the code below:
public class Child
{
[Key]
public long Id {get; set;}
public string Name {get; set;}
public Gender Gender{get; set;} //Usually use Enum containing Female and Male
public long FatherId{get; set;}
public virtual Father Father {get; set;}
}
public class Father
{
public Father()
{
Children = new HashSet<Child>();
}
[Key]
public long Id {get; set;}
public string Name {get; set;}
public virtual ICollection<Child> Children{get; set;}
}
Getting child base on gender will work for what you want to do.

nhibernate mapping by code approach, mapping object

I have following model
public class Car : Entity<int>
{
public virtual int Id{ get; set; }
...
public virtual Engine Engine { get; set; }
}
and I'm using nhibernate mapping by code approach
public class CarMap : ClassMapping<Car>
{
public CarMap()
{
Id(x => CarId, m => m.Generator(Generators.Identity));
// how map reference Engine?
**// edit**
HasOne(x=>x.Engine, m=>{}) // is this good enough?
}
}
how map Engine in this CarMap object?
You need a little more information in the question, but here's a couple of options.
Is this really a one to one relationship? One to one relationships are somewhat unique in that both sides of the relationship tend to share the same Id. Like David Osborne said you most likely want a One to Many relationship. But you you want it to be bi-directional? i.e. you can navigate down from the Engine to all the cars that may have that engine or up from the car to a specific engine. i.e. engine is Chrysler Hemi engine 5.7L and it is in the cars, Ram Pickup, Dodge Durango, Dodge Charger.
Then you may want to map the objects like this
public class Engine : Entity<int>
{
public Engine()
{
Cars = new List<Car>();
}
public virtual int Id { get; protected set; }
public virtual decimal Displacement { get; set; }
//more properties
public virtual IList<Car> Cars { get; }
public virtual void AddCar(Car car)
{
if (Cars.Contains(car)) return;
Cars.Add(car);
}
public virtual void RemoveCar(Car car)
{
if (!Cars.Contains(car)) return;
Cars.Remove(car);
}
}
public class Car : Entity<int>
{
public virtual int Id { get; set; }
public virtual Engine Engine { get; set; }
}
So if you are mapping the Engine you need to define the Cars mapping list this
Bag(x => x.Cars, map =>
{
map.Key(k => k.Column(col => col.Name("EngineId")));
map.Cascade(Cascade.All | Cascade.DeleteOrphans); //optional
},
action => action.OneToMany());
and the other side of the relationship like this
ManyToOne(x => x.Engine, map =>
{
map.Column("EngineId");
map.NotNullable(true); // if you require the Engine to be in a car
});
If you just want a one way mapping from the car to the engine, just remove all the references to the Cars list and delete the Bag mapping.
Using Fluent NH, I would use References(), which has a mapping-by-code equivalent of ManyToOne() apparently.

fluent nhibernate one to many collection, filtered by enum

I think I have a design issue here.
essentially I have a class called office
class Office
{
public virtual long Id { get; set; }
public virtual string Code { get; set; }
public virtual IList<Person> Managers { get; set; }
public virtual IList<Person> Developers { get; set; }
public virtual IList<Person> TeaMakers { get; set; }
}
and a class called Person
class Person
{
public virtual long Id { get; set; }
public virtual string Name {get; set;}
public virtual StaffType Type { get; set;}
public virtual Office Office { get; set; }
}
and an enum called StaffType
public enum StaffType
{
MANAGER,
DEVELOPER,
TEAMAKER
}
Mapping the Person table is easy:
public class PersonMap: ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Office).ForeignKey("Id").Not.Nullable()
Map(x => x.Type).CustomType<StaffType>();
}
}
but i am stumped on the office map. how to i get the map to use the enum to filter the 3 lists?
if i do this:
public class OfficeMap: ClassMap<Office>
{
public static string TableName = "Office";
public static string MappingColumn = TableName + "Id";
public OfficeMap()
{
Table(TableName);
Id(x => x.Id);
Map(x = x.Code);
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.Developers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.TeaMakers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
}
}
fluency won't have the foggiest idea how to split the 3 collections up by the StaffType enum
Thanks for the help
Extra note: the Person table's Type field allways gets mapped as an int.
NHibernate supports filtering as a part of the mapping. Please, read here more 6.2. Mapping a Collection.
The trick is to add more SQL into the mapping. In fact, some WHERE Condition, to be evaluated during the collection load. Small extract from the documentation:
<map // or <set or <bag ...
name="propertyName" (1)
table="table_name" (2)
...
where="arbitrary sql where condition" (9)
And the description of the WHERE:
where (optional) specify an arbitrary SQL WHERE condition to be used
when retrieving or removing the collection (useful if the collection
should contain only a subset of the available data)
In your case, the fluent syntax is similar: ...Where("MyColumn = 'myValue' ");
A draft for your solution:
...
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn)
.Where("Type = 1") // the Column name in the Person table
; // and the value 1 as the enum of the Manager
...
// The same for the others
I would model this as a simple one-to-many (Office has many Person) and add an extension method to IEnumerable<Person> to filter by StaffType. If needed, you can encapsulate access to the Person collection through AddManager etc. methods that enforce business rules.

Why are my Fluent NHibernate SubClass Mappings generating redundant columns?

I have the following entities
public abstract class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Product Product { get; set; }
public virtual Sprint Sprint { get; set; }
}
public class Story:Card
{
public virtual double Points { get; set; }
public virtual int Priority { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Story> Stories { get; private set; }
public Product()
{
Stories = new List<Story>();
}
}
And the following mappings
public class CardMap:ClassMap<Card>
{
public CardMap()
{
Id(c => c.Id)
.Index("Card_Id");
Map(c => c.Name)
.Length(50)
.Not.Nullable();
Map(c => c.Description)
.Length(1024)
.Not.Nullable();
References(c=>c.Product)
.Not.Nullable();
References(c=>c.Sprint)
.Nullable();
}
}
public class StoryMap : SubclassMap<Story>
{
public StoryMap()
{
Map(s => s.Points);
Map(s => s.Priority);
}
}
public class ProductMap:ClassMap<Product>
{
public ProductMap()
{
Id(p => p.Id)
.Index("Product_Id");
Map(p => p.Name)
.Length(50)
.Not.Nullable();
HasMany(p => p.Stories)
.Inverse();
}
When I generate my Schema, the tables are created as follows
Card
---------
Id
Name
Description
Product_id
Sprint_id
Story
------------
Card_id
Points
Priority
Product_id
Sprint_id
What I would have expected would have been to see the columns Product_id and Sprint_id ONLY in the Card table, not the Story table.
What am I doing wrong or misunderstanding?
NB: Tested on the NH2 project only
Well, you are probably going to want to chew on a door once you read this, but the TLDR reason is because the Product_id and Spring_id columns in your Story table are not redundant - they exist for the HasMany(x => x.Stories) relations in your SpringMap and ProductMap. They just happen to be share the same naming convention as the CardMap References(x => x.Product and References(x => x.Sprint).
Validate this for yourself by commenting out ProductMap.cs:24-25 and SprintMap.cs:22 and rebuilding.
If the above does not make sense, let me know and I will try to explain in further detail.
So, it should work fine as is. If you want to clarify the columns, you could explicitly define the column names like so:
ProductMap.cs
HasMany(p => p.Stories)
.KeyColumn("ProductOwner_id")
.Inverse();
SprintMap.cs
HasMany(s => s.Stories)
.KeyColumn("SprintOwner_id")
;
CardMap.cs
References(c=>c.Product)
.Column("Product_id")
.Not.Nullable();
References(c=>c.Sprint)
.Column("Sprint_id")
.Nullable();
Here I am guessing that the 1:N relationships between a Story and a Product/Sprint are an "owner". You would want to rename it to whatever is appropriate semantically.
One other thing. I would have thought the last changes (the changes to CardMap.cs) would be unnecessary - but they seem to be for some reason, or the Sprint_id column becomes SprintOwner_id. I have no idea why this would happen - I would speculate that this is some sort of bidirectional relationship inferencing on fluent/nhibernates part gone awry, but I'd put very little money on that.
I see that the Story entity inherits from the Card entity you created, but you don't know why you have Product_Id and Sprint_Id properties in the Story table Schema, since they're virtual properties in the Card class.
I'm guessing that this happens because in NHibernate, all properties need to be virtual but only at first. They don't really stay virtual. The NHibernate framework overrides them, and probably because of this, this is happening to you.

Getting Data from all tables instead of one with HQL Query that should only get 1 table's data

I have created 3 tables in my database and put data into them. The 3 tables all have foreign keys joining them together. Below are the table classes and there mappings. When I run the query listed at the end I get IList<> of the objects and they have the data from all 3 tables. However, my HQL query is only from the top most table. How can I get back just the results from the top most table?
These are my classes:
public class Technology
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual int SortOrder { get; set; }
public virtual string Abbreviation { get; set; }
public virtual IList<TechnologyDescription> TechnologyDescriptions { get; private set; }
public Technology()
{
TechnologyDescriptions = new List<TechnologyDescription>();
}
public virtual void AddTechnologyDescription(TechnologyDescription technologyDescription)
{
technologyDescription.Technology = this;
TechnologyDescriptions.Add(technologyDescription);
}
}
public class TechnologyDescription
{
public virtual int Id { get; private set; }
public virtual Technology Technology { get; set; }
public virtual string Description { get; set; }
public virtual DescriptionType DescriptionType { get; set; }
}
public class DescriptionType
{
public virtual int Id {get; private set;}
public virtual string Type { get; set; }
}
These are my mapping objects:
public class TechnologyMap : ClassMap<Technology>
{
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.SortOrder);
Map(x => x.Abbreviation);
HasMany(x => x.TechnologyDescriptions)
.Inverse()
.Cascade.All();
}
}
public class TechnologyDescriptionMap : ClassMap<TechnologyDescription>
{
public TechnologyDescriptionMap()
{
Id(x => x.Id);
References(x => x.Technology);
Map(x => x.Description);
References(x => x.DescriptionType);
}
}
public class DescriptionTypeMap : ClassMap<DescriptionType>
{
public DescriptionTypeMap()
{
Id(x => x.Id);
Map(x => x.Type);
}
}
And this is my HQL code:
IQuery q = session.CreateQuery("from Technology T");
IList technologies = q.List();
I don't know if it is possible using HQL, but using NHibernate's Criteria API, you can do this:
ICriteria criteria = session.CreateCriteria (typeof(Technology));
criteria.SetFetchMode ("TechnologyDescriptions", FetchMode.Lazy);
var list = criteria.List<Technology>();
However, this is probably not really what you want. The TechnologyDescriptions won't be fetched right now, but they will be fetched once you access them (that is: the first time you call the TechnologyDescriptions property).
When working with NHibernate, you shouldn't think in terms of 'data'. Rather, you should think in terms of 'entities'.
When retrieving an entity, you want to retrieve the entity entirly (directly, or in a lazy fashion). It is not possible to retrieve an entity partially, and this is quite obvious;
What should NHibernate do with an entity that you've retrieved partially, when you try to save that entity ?
Something else that pops in my mind:
I suppose you want to retrieve the Technologies, and nothing related because you want to display them in an overview or something like that ?
In such case, you should take a look at 'Transformations'.
You could for instance create an additional class which is called TechnologyView, which looks like this:
public class TechnologyView
{
public int Id
{
get;
private set;
}
public string Name
{
get;
private set;
}
public string Abbreviation
{
get;
private set;
}
private TechnologyView()
{
// Private constructor, required for NH
}
public TechnologyView( int id, string name, string abbreviation )
{
this.Id = id;
this.Name = name;
this.Abbreviation = abbreviation;
}
}
Once you've done this, you must inform NHibernate about the existance of this class.
You do this by Importing the class in an hbm.xml file for instance . (I do not know how to do it using Fluent).
<import class="MyNamespace.TechnologyView" />
After that, you can create a query (using HQL or Criteria) which retrieves TechnologyView instances. NHibernate is smart enough to generate a performant SQL query.
using HQL:
IQuery q = s.CreateQuery ("select new TechnologyView (t.Id, t.Name, t.Abbreviation) from Technology t");
using Criteria:
ICriteria criteria = s.CreateCriteria (typeof(Technology));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(TechnologyView));
var result = criteria.List<TechnologyView>();
I think what you're looking for is for the TechnologyDescriptions to be lazy loaded. That way the descriptions get loaded from the database only when they are accessed (NHibernate will issue a second db query. Note that this can lead to N+1 selects in some situations and you might prefer the all at once query depending on the usage.)
By NHibernate xml mappings default to lazy loading of collections. In the past it seems that the Fluent NHibernate did not have the same default. You need to add .LazyLoad() to the mapping.
Recently it looks like lazy loading has become the default fluent mapping:
Is the default behavior with Fluent NHibernate to lazy load HasMany<T> collections?

Categories

Resources