Basic question: How to I create a bidirectional one-to-many map in Fluent NHibernate?
Details:
I have a parent object with many children. In my case, it is meaningless for the child to not have a parent, so in the database, I would like the foreign key to the parent to have NOT NULL constraint. I am auto-generating my database from the Fluent NHibernate mapping.
I have a parent with many child objects like so:
public class Summary
{
public int id {get; protected set;}
public IList<Detail> Details {get; protected set;}
}
public class Detail
{
public int id {get; protected set;}
public string ItemName {get; set;}
/* public Summary Owner {get; protected set;} */ //I think this might be needed for bidirectional mapping?
}
Here is the mapping I started with:
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.ID);
HasMany<Detail>(x => x.Details);
}
}
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.ID);
Map(x => x.ItemName).CanNotBeNull();
}
}
In the Detail table, the Summary_id should be Not Null, because in my
case it is meaningless to have a Detail object not attached to the
summary object. However, just using the HasMany() map leaves the Summary_id foreign key nullable.
I found in the NHibernate docs (http://www.hibernate.org/hib_docs/nhibernate/html/collections.html) that "If the parent is required, use a bidirectional one-to-many association".
So how do I create the bidirectional one-to-many map in Fluent NHibernate?
To get a bidirectional association with a not-null foreign key column in the Details table you can add the suggested Owner property, a References(...).CanNotBeNull() mapping in the DetailsMap class, and make the Summary end inverse.
To avoid having two different foreign key columns for the two association directions, you can either specify the column names manually or name the properties in a way that gives the same column name for both directions. In this case you I suggest renaming the Details.Owner property to Details.Summary.
I made the Summary id generated by increment to avoid problems when inserting into the table since Summary currenty has no columns besides id.
Domain:
public class Detail
{
public int id { get; protected set; }
public string ItemName { get; set; }
// Renamed to use same column name as specified in the mapping of Summary.Details
public Summary Summary {get; set;}
}
public class Summary
{
public Summary()
{
Details = new List<Detail>();
}
public int id { get; protected set; }
public IList<Detail> Details { get; protected set; }
}
Mapping:
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.id)
.GeneratedBy.Native();
Map(x => x.ItemName)
.CanNotBeNull();
References<Summary>(x => x.Summary)
// If you don't want to rename the property in Summary,
// you can do this instead:
// .TheColumnNameIs("Summary_id")
.CanNotBeNull();
}
}
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.id)
.GeneratedBy.Increment();
HasMany<Detail>(x => x.Details)
.IsInverse()
.AsBag(); // Use bag instead of list to avoid index updating issues
}
}
Related
I am a beginner at using Fluent NHibernate.
I am developing a C# application that has to interact with an existing database.Let say I have 2 tables: Items and ItemsList.
Items: ID INT ItemName VARCHAR(100)
ItemsList: ID INT ChildItemID INT
I've built 2 classes and their mapping:
public class Items
{
public virtual int id {get; set;}
public virtual string itemName {get; set;}
}
public class ItemsMap : ClassMap<Items>
{
public ItemsMap()
{
Id(x => x.id).GeneratedBy.Increment();
Map(x => x.itemsName);
}
}
public class ItemsList()
{
public virtual int id {get; set;}
public virtual IList<Items> childItems {get; set;}
public ItemsList()
{
childItems = new List<Items>();
}
}
public class ItemsListMap : ClassMap<ItemsList>
{
public ItemsListMap()
{
Id(x => x.id).GeneratedBy.Increment();
HasMany(x => x.childItems).KeyColumn("childID").Cascade.All();
}
}
And finally, I insert an item in the itemsList and save it all:
try
{
using( ISession session = NH.OpenSession())
{
using(ITransaction transaction = session.BeginTransaction())
{
Items i = New Items()
i = session.get<Items>(1);
ItemsList il = new ItemsList();
il.childID.Add(i);
session.SaveOrUpdate(il);
transaction.Commit();
}
}
}
So when I commit, I have a new entry in ItemsList table, but the childID is blank.
Question:
All the examples I see has a reference to ItemsListID in Items table. But I don't want to have this reference since I want the item to be unique in the items table. How can I acheve that?
The NHibernate native way for expressing the unique reference, is:
5.1.12. one-to-one
There are two varieties of one-to-one association:
primary key associations
unique foreign key associations
Primary key associations don't need an extra table column; if two rows are related by the association then the two table rows share the same primary key value. So if you want two objects to be related by a primary key association, you must make sure that they are assigned the same identifier value!...
Other words, Tables would look like this (Table Items generates the value of ItemID, table ItemsList takes that value and stores it in the ItemID ) :
Items: ItemID INT ItemName VARCHAR(100)
ItemsList: ItemID INT
The C# would be (I changed Items into Item and ItemList into ItemMoreDetails, because it is not a list anymore)
public class Item
{
public virtual int ItemId { get; set; }
...
public virtual ItemMoreDetails ItemMoreDetails {get; set; }
public class ItemMoreDetails
{
public virtual int ItemId { get; set; }
...
public virtual Item Item {get; set;}
The mapping would be (in fluent):
// Parent side
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Id(x => x.id).GeneratedBy.Increment();
...
HasOne(x => x.ItemMoreDetails).Cascade.All();
// child side
public class ItemMoreDetailsMap: ClassMap<ItemMoreDetails>
{
public ItemMoreDetailsMap()
{
...
References(x => x.parent).Unique();
See the doc:
HasOne / one-to-one
I have a table called openTickets. I have another table called openTicketFollowers that relates to it using a foreign key. OpenTickets does not know about openTicketFollowers but I want openTickets to have a property that is a list of its followers. Is there anyway to do this with fluent nhibernate?
Check this Fluent mapping document. The OpenTicket class will contain IList of Followers:
public class OpenTicket
{
...
public virtual IList<OpenTicketFollower> Followers { get; set; }
}
public class OpenTicketFollowers
{
public virtual OpenTicket OpenTicket { get; set; }
}
And this is fluent mapping of the OpenTicketFollowercollection:
HasMany(x => x.Followers)
.KeyColumn("OpenTicketId");
and the OpenTicketFollower class mapping referencing the OpenTicket
References(x => x.OpenTicket)
.Column("OpenTicketId")
I have a class "Company" which has a list of "Operator"
public class Company
{
public IList<Opertator> Operators {get; set; }
public Int32 Id {get; set;}
}
public class Operator {
public Int32 Id {get; set; }
public Company Company {get; set; }
}
When I mapped as follows:
public class CompanyMapping : ClassMap<Company>
{
public ProductMapping() : base()
{
Id(x => x.Id, "CompanyId").GeneratedBy.Native();
HasMany(x => x.Operators);
}
}
public class OperatorMapping : ClassMap<Operator>
{
public OperatorMapping()
{
Id(x => x.Id);
Reference(x => x.Company);
}
}
I have a UI where user can add operators and remove operators by checking boxes.
in c# code, I query the stored company, and add to the list, or remove from the list.
then send SaveOrUpdate.
My problem is when I add one more operator to existing company, and save/Update, the NHibernate is deleting the whole list, and reinster them again.
I don't want to do that.
Can I have it, that NHibernate will detect the changed items (new items from the list, and insert them, and determine the deleted item and delete them)?
I tried change the mapping to have in the company mapping
HasMany(x => x.Operators).Inverse();
but it end up not deleting at all.
Any help?
Inverse says that the Operator is responsible for the association and you have to set it when adding an Operator to the Company
public class Company
{
public Int32 Id {get; set;}
public ICollection<Opertator> Operators {get; private set; }
public void Add(Opertator operator)
{
Operators.Add(operator);
operator.Company = this;
}
public Company()
{
Operators = new List<Operator>();
}
}
// and
HasMany(x => x.Operators).Inverse();
// use it like
company.Add(new Operator()); // instead of company.Operators.Add(new Operator());
I am trying to use fluent nHibernate to map a class that only exists as a container for other classes, and doesn't have an Id of its own. But I want the Id of the class to be a different class. That really doesn't make sense, it might be easier to explain with an example data structure.
abstract QueryBuilder
{
public IEnumerable<string> Category1Keys {get; set;}
public IEnumerable<int> Category2Ids {get; set;}
public IEnumerable<int> Category3Ids {get; set;}
}
Set
{
public int Id {get; set;}
public string Name {get; set;}
public SetQueryBuilder QueryBuilder{get; set;}
}
SetQueryBuilder : QueryBuilder
{
}
News
{
public int Id {get; set;}
public string Name {get; set;}
public NewsQueryBuilder QueryBuilder{get; set;}
}
NewsQueryBuilder : QueryBuilder
{
}
Now the QueryBuilder is the name is a concept that we use to map various (unrelated) items to the same categories. So things that might use QueryBuidler are News, Pages, Permissions.
Becuase all the various items that use QueryBuilder map that relationship in the same way, and ocasionally we actually use the paramters from one type of QueryBuilder as a the select criteriea for another type of QueryBuilder (ie if news has QB of A, get all the Sets that match the same criteria), I want to have a abstract QueryBuilder class, and then extend it for all the things that have mappings to all the categories.
Here is an example of the Set DB for query builder. There isn't an actual item in the DB for QueryBuilder - its composed of all the info from the category class join tables.
f_set
{
set_id (PK, int)
name (varchar)
}
f_set_cat1
{
set_id (PK, FK, int)
cat1_key (PK, FK, char(3))
}
f_set_cat2
{
set_id (PK, FK, int)
cat2_id (PK, FK, int)
}
f_set_cat3
{
set_id (PK, FK, int)
cat3_id (PK, FK, int)
}
My problem is how to map each instance of the QueryBuilder class in nHibernate, as it doesnt' have a real key or table entry, I would like to say that the Id is a reference to the Set that all the category mappings use.
Here are my fluent mappings so far:
SetMapping : ClassMap<Set>
{
public SetMapping()
{
Schema("cam");
Table("f_set");
Id(x => x.Id, "f_set_id").GeneratedBy.Identity();
Map(x => x.Name, "name").Not.Nullable();
HasOne<SetQueryBuilder>(x => x.QueryBuilder);
}
}
SetQueryBuilderMapping : ClassMap<SetQueryBuilder>
{
public SetQueryBuilderMapping()
{
References(x => x.Set, "set_id");
HasMany(x => x.Category1Keys).Table("f_set_cat1").Element("cat1_key").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat2").Element("cat2_id").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat3").Element("cat3_id").KeyColumn("set_id");
}
}
Any help with the final step of the mapping would be hugely appreciated
Thanks
Saan
FURTHER INVESTIGATION
Ok I have done a bit mroe investigating on this and have foudn that combining the two classes works fine if I do this:
CLASS:
Set
{
public int Id {get; set;}
public string Name {get; set;}
//public SetQueryBuilder QueryBuilder{get; set;}
public IEnumerable<string> Category1Keys {get; set;}
public IEnumerable<int> Category2Ids {get; set;}
public IEnumerable<int> Category3Ids {get; set;}
}
MAPPING
SetMapping : ClassMap<Set>
{
public SetMapping()
{
Schema("cam");
Table("f_set");
Id(x => x.Id, "f_set_id").GeneratedBy.Identity();
Map(x => x.Name, "name").Not.Nullable();
//HasOne<SetQueryBuilder>(x => x.QueryBuilder);
HasMany(x => x.Category1Keys).Table("f_set_cat1").Element("cat1_key").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat2").Element("cat2_id").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat3").Element("cat3_id").KeyColumn("set_id");
}
}
When doing nHibernate mappings this makes perfect sense, but I would really like to have all the category mappings in the separate SetQueryBuilder class.
Thanks again for any help
Saan
You are looking for subclassing strategy. Take a look at this http://nhibernate.info/doc/nh/en/index.html#inheritance
You probably want to follow a table per class with table per subclass strategy imo.
I'm an NHibernate and fluent-nhibernate newbie. And I've got some problem with unique constraint and nhibernate mapping.
I've got the following part of domain model.
public class Batch
{
public virtual int Id {get; set;}
public virtual string Name {get; set;}
public virtual IList<BatchParameter> BatchParameters {get; set;}
}
public class BatchParameter
{
public virtual int Id {get; set;}
public virtual string Name {get; set;}
public virtual Batch Batch {get; set;}
}
I'm trying to use fluent-nhibernate to map it on the db (SQLServer) using automapping.
I want to be set up my db in order to have :
Primary Keys on the "Id"s properties
a Foreign Key on the BatchParamets table
a Unique Constraint on the Batch table on column Name
a Unique Constraint on the BatchParameters table on columns Name and Batch_Id
So I've written down this code:
public class BatchMapping : IAutoMappingOverride<Batch>
{
public void Override(FluentNHibernate.Automapping.AutoMapping<Batch> mapping)
{
mapping.Id( b => b.Id);
mapping.HasMany<BatchParameter>(p => p.BatchParameters).Cascade.All().Inverse();
}
}
public class BatchParameterMapping : IAutoMappingOverride<BatchParameter>
{
public void Override(FluentNHibernate.Automapping.AutoMapping<BatchParameter> mapping)
{
mapping.Id( b => b.Id);
mapping.Map(b => b.Name).Unique();
//mapping.Map(p => p.Name).UniqueKey("Batch_Parameter");
//mapping.Map(p => p.Batch.Id).UniqueKey("Batch_Parameter");
}
}
No problems for the primary keys, the foreign key and the first Unique Constraint. A little bit of headache for the Unique Constraint.
Can someone show me the straight way???
Thanks!
First, it looks like you have a copy-and-paste error: ...Map(b => b.Name)... should go in BatchMapping, not BatchParameterMapping.
public class BatchMapping : IAutoMappingOverride<Batch>
{
public void Override(AutoMapping<Batch> mapping)
{
mapping.Map(b => b.Name).Unique();
}
}
Next, BatchParameter.Batch is a many-to-one relationship from BatchParameter to Batch, so it should be mapped with References(...) instead of Map(...). You use References for foreign keys to another entity and use Map for simple properties.
public class BatchParameterMapping : IAutoMappingOverride<BatchParameter>
{
public void Override(AutoMapping<BatchParameter> mapping)
{
mapping.Map(p => p.Name).UniqueKey("Batch_Parameter");
mapping.References(p => p.Batch).UniqueKey("Batch_Parameter");
}
}
Finally, you should remove the unnecessary mappings for the Id properties and Batch.BatchParameters. Fluent NHibernate's auto-mapping will map them as desired by default. In your Override methods you only need to specify the properties where you want to do something differently than the auto-mapping default, such as specifying unique keys.
If Id and Name are primary keys in your BatchParameter table you would need a composite id. Also if you want to have a reference back to Batch from your BatchParameter class you will need to use Reference. The following should be close to what you need:
public class BatchParameterMapping : IAutoMappingOverride<BatchParameter>
{
public void Override(FluentNHibernate.Automapping.AutoMapping<BatchParameter> mapping)
{
mapping.CompositeId()
.KeyProperty(x => x.Id)
.KeyProperty(x => x.Name);
mapping.References(x => x.Batch);
}
}