How to Map One-To-Many nhibernate class mapping - c#

here is my order and orderItem classe:
public class Order : AggregateRootBase<OrderId>
{
public string CustomerName { get; private set; }
public IList<OrderItem> Items { get; set; }
public DateTime RegisterDatetime { get; private set; }
}
public class OrderItem : ValueObjectBase
{
public long Id { get; private set; }
public long OrderId { get; set; }
public long Number { get; private set; }
public long Goods { get; private set; }
public double UnitPrice { get; private set; }
}
Im using nhibernate as my orm. in mapping this code i want order to be in Orders table & orderItem to be stored in a diffrente table called OrderItems.
here is my mapping:
public class OrderMapping : ClassMapping<Order>
{
public OrderMapping()
{
Table("Orders");
Lazy(false);
ComponentAsId(a => a.EntityId, a => { a.Property(x => x.DbId, x => x.Column("Id")); });
Property(a=>a.CustomerName);
Property(a => a.RegisterDatetime);
Bag(a => a.Items,
mapper => {
mapper.Inverse(true);
mapper.Cascade(Cascade.None);
mapper.Table("OrderItems");
mapper.Key(k => k.Column(columnMapper => columnMapper.Name("OrderId")));
},
relation => { relation.OneToMany(); });
}
}
public class OrderItemMapping : ClassMapping<OrderItem>
{
public OrderItemMapping()
{
Lazy(false);
Id(a => a.Id);
Table("OrderItems");
Property(a => a.OrderId);
Property(a => a.Number);
Property(a => a.Goods);
Property(a => a.UnitPrice);
}
}
also i have created tables in database but when i insert order with 3 orderItems, it inserts order but not orderitems
thanks for your help

You have mapped the parent Order as mapper.Inverse(true); for the Items bag which tells NHibernate that you do not want the parent to map this relation.
Since the child OrderItem has no mapping to the parent, then nothing is saving the relation. When you write a collection mapping, at least one side must be inverse(false).
You have also set mapper.Cascade(Cascade.None);, which tells NHibernate that you do not want the parent Order to deal with any operations on the Items when they change in Session.
So unless you are explicitly calling Save() on each Item, then they will never be saved as it stands.
There is a trade off in NHibernate between free class layout, and optimum database performance (although very minor in this case).
If you really don't want the OrderItem to have an Order property linking back to it's parent, then you will get an additional UPDATE call to SQL whenever the parent Order of an OrderItem changes, this cost is in practice negligible if creation of OrderItems is less than ~10% of operations that you do with them.
In that case you can set inverse(false) on the OrderMapping to Items.
But my adivce would be to give OrderItem an Order field or property (you can map a private field using NHibernate!) and then give the OrderItemMapping a map back to the parent with inverse(false), so that when children are saved, they will deal with the relation. You will have to make sure that each OrderItem has it's Order field/property filled in before save though!
You might be able to wing this by using the OrderId property instead of a full reference to Order, but you'd have to look that up.
As for making them save to the DB, the easiest way is to change mapper.Cascade(Cascade.None); to mapper.Cascade(Cascade.AllDeleteOrphan); (may not be exact class name). This will make sure that whenever you modify that collection on the Order, then Save()/Update() the Order, all OrderItems in the Items collection will be updated in the DB accordingly.
You could also check out less strict Cascades or save them manually as your current setup would require.
Lastly check out bag vs set in the Nhibernate docs, I suspect you want a set here, I would only use bag with inverse(true) ever. If you use an inverse(false) bag there is a performance penalty, unless the items can be identified (your OrderItem has an Id, so it can!), but if the items can be identified, then why not just make it a set!
The only reason you'd have an inverse(false) bag with identifiable items, is if those identifiers didn't correlate to a Primary Key in the database, that's not a situation that comes up very often (or ever in my case!).

thank you #starlight54 but my problem solved like this.
i had to change my classes a little like this:
public class Order : AggregateRootBase<OrderId>
{
private readonly IList<OrderItem> _items;
public string CustomerName { get; private set; }
public DateTime RegisterDatetime { get; private set; }
public IReadOnlyCollection<OrderItem> Items => new ReadOnlyCollection<OrderItem>(_items);
}
public class OrderItem : ValueObjectBase
{
private readonly long _number;
private readonly long _goods;
private readonly double _unitPrice;
public long Number => _number;
public long Goods => _goods;
public double UnitPrice => _unitPrice;
}
notice that in order item you have to create fields like _number, _goods & ...
and this is the mapping of order:
public class OrderMapping : ClassMapping<Order>
{
public OrderMapping()
{
Table("Orders");
Lazy(false);
ComponentAsId(a => a.EntityId, a => { a.Property(x => x.DbId, x => x.Column("Id")); });
Property(a => a.CustomerName);
Property(a => a.RegisterDatetime);
IdBag(a => a.Items, map => {
map.Access(Accessor.Field);
map.Table("OrderItems");
map.Key(a => a.Column("OrderId"));
map.Id(a => {
a.Column("Id");
a.Generator(Generators.Identity);
});
}, relation => relation.Component(map => {
map.Access(Accessor.Field);
map.Property(a => a.Number, a => a.Access(Accessor.Field));
map.Property(a => a.Goods, a => a.Access(Accessor.Field));
map.Property(a => a.UnitPrice, a => a.Access(Accessor.Field));
}));
}
}
in nhibernate there is IdBag that helped me do exactly what i needed. notice than there is no need to create class mapping for orderItem class. nhibernate inserts it automatic.
but you have to create databases manually.
i hope this helps you.

Related

Tracking changes in Entity Framework for many-to-many relationships with behavior

I'm currently attempting to use Entity Framework's ChangeTracker for auditing purposes. I'm overriding the SaveChanges() method in my DbContext and creating logs for entities that have been added, modified, or deleted. Here is the code for that FWIW:
public override int SaveChanges()
{
var validStates = new EntityState[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && validStates.Contains(x.State));
var entriesToAudit = new Dictionary<object, EntityState>();
foreach (var entity in entities)
{
entriesToAudit.Add(entity.Entity, entity.State);
}
//Save entries first so the IDs of new records will be populated
var result = base.SaveChanges();
createAuditLogs(entriesToAudit, entityRelationshipsToAudit, changeUserId);
return result;
}
This works great for "normal" entities. For simple many-to-many relationships, however, I had to extend this implementation to include "Independent Associations" as described in this fantastic SO answer which accesses changes via the ObjectContext like so:
private static IEnumerable<EntityRelationship> GetRelationships(this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => new EntityRelationship(
e.EntitySet.Name,
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
Once implemented, this also worked great, but only for many-to-many relationships that use a junction table. By this, I'm referring to a situation where the relationship is not represented by a class/entity, but only a database table with two columns - one for each foreign key.
There are certain many-to-many relationships in my data model, however, where the relationship has "behavior" (properties). In this example, ProgramGroup is the many-to-many relationship which has a Pin property:
public class Program
{
public int ProgramId { get; set; }
public List<ProgramGroup> ProgramGroups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public IList<ProgramGroup> ProgramGroups { get; set; }
}
public class ProgramGroup
{
public int ProgramGroupId { get; set; }
public int ProgramId { get; set; }
public int GroupId { get; set; }
public string Pin { get; set; }
}
In this situation, I'm not seeing a change to a ProgramGroup (eg. if the Pin is changed) in either the "normal" DbContext ChangeTracker, nor the ObjectContext relationship method. As I step through the code, though, I can see that the change is in the ObjectContext's StateEntries, but it's entry has IsRelationship=false which, of course, fails the .Where(e => e.IsRelationship) condition.
My question is why is a many-to-many relationship with behavior not appearing in the normal DbContext ChangeTracker since it's represented by an actual class/entity and why is it not marked as a relationship in the ObjectContext StateEntries? Also, what is the best practice for accessing these type of changes?
Thanks in advance.
EDIT:
In response to #FrancescCastells's comment that perhaps not explicitly defining a configuration for the ProgramGroup is cause of the problem, I added the following configuration:
public class ProgramGroupConfiguration : EntityTypeConfiguration<ProgramGroup>
{
public ProgramGroupConfiguration()
{
ToTable("ProgramGroups");
HasKey(p => p.ProgramGroupId);
Property(p => p.ProgramGroupId).IsRequired();
Property(p => p.ProgramId).IsRequired();
Property(p => p.GroupId).IsRequired();
Property(p => p.Pin).HasMaxLength(50).IsRequired();
}
And here are my other configurations:
public class ProgramConfiguration : EntityTypeConfiguration<Program>
{
public ProgramConfiguration()
{
ToTable("Programs");
HasKey(p => p.ProgramId);
Property(p => p.ProgramId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Program).HasForeignKey(p => p.ProgramId);
}
}
public class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
ToTable("Groups");
HasKey(p => p.GroupId);
Property(p => p.GroupId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Group).HasForeignKey(p => p.GroupId);
}
When these are implemented, EF still does not show the modified ProgramGroup in the ChangeTracker.
While the concept of "relationship with attributes" is mentioned in the theory of entity-relationship modelling, as far as Entity Framework is concerned, your ProgramGroup class is an entity. You're probably unwittingly filtering it out with the x.Entity is BaseEntity check in the first code snippet.
I believe the problem lies in the definition of your Program and Group class and overridden SaveChanges method. With the current definition of the classes the EF is unable to use change tracking proxies, that catch changes as they are being made. Instead of that the EF relies on the snapshot change detection, that is done as part of SaveChanges method. Since you call base.SaveChanges() at the end of the overridden method, the changes are not detected yet when you request them from ChangeTracker.
You have two options - you can either call ChangeTracker.DetectChanges(); at the beginning of the SaveChanges method or change definition of your classes to support change tracking proxies.
public class Program {
public int ProgramId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
public class Group {
public int GroupId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
The basic requirements for creating change tracking proxies are:
A class must be declared as public
A class must not be sealed
A class must not be abstract
A class must have a public or protected constructor that does not have parameters.
A navigation property that represents the "many" end of a relationship must have public virtual get and set accessors
A navigation property that represents the "many" end of a relationship must be defined as ICollection<T>
Entity Framework represents many-to-many relationships by not having entityset for the joining table in CSDL, instead it manages this through mapping.
Note: Entity framework supports many-to-many relationship only when the joining table does NOT include any columns other than PKs of both the tables
you should have to define navigation property yourself to coupe with this proplem.
this link can be of your help.

Automapper projection (EF) with encapsulated child collections

I use Automapper to map from EF entities to view models.
I now have this entity
public class MenuGroup : IEntity
{
public int MenuGroupId { get; set; }
protected ICollection<MenuGroupItem> _menuGroupItems { get; set; }
public IEnumerable<MenuGroupItem> MenuGroupItems { get { return _menuGroupItems; } }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
_menuGroupItems.Add(menuGroupItem);
}
}
That is an encapsulated collection, I followed instructions here to make this work: http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/
So I configure it like so this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
Now the problem I get is when I try to use automapper to map my MenuGroup into a viewmodel.
I run this code: menuGroup = _context.MenuGroups.Project().To<MenuGroupEditModel>().Single(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName);
and get this error: The specified type member 'MenuGroupItems' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Now I can work with the collection, it saves correctly to the database and all is well there it's only when i want to user automapper here that it fails.
If I replace the protected ICollection and public IEnumerable with simply: public ICollection<MenuGroupItem> MenuGroupItems { get; set; } it works right away so the problem lies in automapping with my encapsulated collection.
Update: I also tried this menuGroup = _context.MenuGroups.Include(x => x.MenuGroupItems).Where(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName).Project().ToSingleOrDefault<MenuGroupEditModel>(); with no difference other than that it errored in the ToSingleOrDefault instead.
Your problem is that Automapper can't modify MenuGroupItems because there is no public setter.
Your solution is changing it to this:
public IEnumerable<MenuGroupItem> MenuGroupItems { get; set; }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
MenuGroupItems.Add(menuGroupItem);
}
After some more debugging I figured out the Config file looking like this
public MenuGroupConfiguration()
{
this.HasMany(x => x.MenuGroupAssigments).WithRequired(x => x.BelongTo).WillCascadeOnDelete(true);
this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
}
had not been included leading to that error that now makes sense.
I can add as a general tip that if you don't use auto-mapper for a query but still use your encapsulated collection remember that you have to call decompile for it to work.
like so
var menuGroupsWithType =
_context.MenuGroups.Include(x => x.MenuGroupItems).Include(x => x.MenuGroupAssigments).Where(x => x.MenuGroupAssigments.Any(y => y.AssignToAll == selectedStructureType))
.OrderBy(x => x.Name).Decompile().ToList();

Fluent NHibernate Mapping / Parent/Child delete fail using composite key

I'm struggling to understand why when I remove a child Settings object from MyUser.Settings and SAVE MyUser I get SQL errors like below:
Cannot insert the value NULL into column 'MyUserId', table '###.Settings'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
What I would expect to happen is that removing the item from the collection, then saving MyUser causes NHibernate to issue a DELETE command for the given child. However, what it does is UPDATE the relevant row for the Settings object, setting MyUserId to NULL - which isn't allowed as I'm using a Composite Key.
I've tried so many combinations of Inverse() and the various Cascade options but nothing seems to work. I should point out that Adding to the collection works perfectly when I save MyUser.
I'm totally baffled!
Below is pseudo code to try and explain my entities and mappings.
public class SettingType
{
public virtual int SettingTypeId { get; set; }
public virtual string Name { get; set; }
public virtual bool Active { get; set; }
}
public class Setting
{
public virtual MyUser MyUser { get; set; }
public virtual SettingType SettingType { get; set; }
public virtual DateTime Created { get; set; }
}
public class MyUser
{
public virtual int MyUserId { get; set; }
public virtual IList<Setting> Settings { get; set; }
public virtual string Email { get; set; }
public void AddSetting(SettingType settingType, DateTime now)
{
var existing = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existing != null)
{
existing.Updated = now;
}
else
{
var setting = new Setting
{
MyUser = this,
SettingType = settingType,
Created = now,
};
_settings.Add(setting);
}
}
public void RemoveSetting(SettingType settingType)
{
var existingPref = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existingPref != null)
{
_settings.Remove(existingPref);
}
}
private readonly IList<Setting> _settings = new List<Setting>();
}
And my mappings:
public class SettingTypeMap : IAutoMappingOverride<SettingType>
{
public void Override(AutoMapping<SettingType> mapping)
{
mapping.Table("SettingTypes");
mapping.Id(m => m.SettingTypeId).GeneratedBy.Identity();
mapping.Map(m => m.Name).Not.Nullable().Length(100);
mapping.Map(m => m.Active).Not.Nullable().Default("0");
}
}
public class SettingMap : IAutoMappingOverride<Setting>
{
public void Override(AutoMapping<Setting> mapping)
{
mapping.Table("Settings");
mapping.CompositeId()
.KeyReference(m => m.MyUser)
.KeyReference(m => m.SettingType);
mapping.Map(m => m.Created).Not.Nullable().Default("CURRENT_TIMESTAMP");
mapping.Map(m => m.Updated).Nullable();
}
}
public class MyUserMappingOverride : IAutoMappingOverride<MyUser>
{
public void Override(AutoMapping<MyUser> mapping)
{
mapping.Table("MyUsers");
mapping.Id(m => m.MyUserId).GeneratedBy.Identity();
mapping.Map(m => m.Email).Not.Nullable().Length(200);
mapping.HasMany(m => m.Settings).KeyColumn("MyUserId").Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
All using:
FluentNHibernate v1.3.0.733
NHibernate v3.3.1.4000
UPDATE: After a few suggestions I've tried to change the mapping for MyUser entity.
First to this:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This gives the error: Given key was not present in the dictionary
So tried to add second key column:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.KeyColumn("SettingTypeId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
But this then causes odd behaviour when loading the Settings collection from the DB for a given MyUserId. Looking at the nh profiler I see a second SELECT ... FROM Settings but setting the SettingTypeId same as value for MyUserId.
Still totally baffled. Has cost me too much time so going to revert to adding a primary key id field to the Settings entity. Maybe you just can't do what I'm trying using NHibernate. In pure SQL this is simple.
You should use the Inverse mapping
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This will allow NHibernate to ask the setting itself to be deleted. Otherwise, NHibernate firstly tries to delete the relation, and would try to delete the entity.
See: 6.4. One-To-Many Associations
Very Important Note: If the column of a
association is declared NOT NULL, NHibernate may cause constraint
violations when it creates or updates the association. To prevent this
problem, you must use a bidirectional association with the many valued
end (the set or bag) marked as inverse="true". See the discussion of
bidirectional associations later in this chapter.

Mapping an Array using Fluent N Hibernate

I am not sure if fluent n hibernate can do this or not, but I cannot figure out how.
I have a table - cases and some properties
ownerId, brokerId, shipperId
I want to map this to my property:
int[] OrgsWithAccess
Is this possible?
This way when I am checking if an org has access to the case, I can check the property OrgsWithAccess rather than OwnerId == myorg.id or brokerId == myorg.id etc.
If I understand your question correctly, I wouldn't recommend trying to map in the way that you have asked.
cases table looks like it is some form of junction table between other tables. I'll assume that these other tables each contain data that are represented as entities in the application, and that there are three tables, Owner, Broker and Shipper.
OrgsWithAccess should be mapped using the references to the entities that is has in the application i.e. assume the class looks something like
public class OrgsWithAccess
{
public virtual Owner { get; set; }
public virtual Broker { get; set; }
public virtual Shipper { get; set; }
}
Then the mapping will look like
public class OrgsWithAccessMap : ClassMap<OrgsWithAccess>
{
public OrgsWithAccessMap()
{
References(x => x.Owner);
References(x => x.Broker);
References(x => x.Shipper);
}
}
Then when querying, you would simply look at the properties on OrgsWithAccess
session.QueryOver<OrgsWithAccess>().Where(x => x.Owner.Id == id);

Fluent NHibernate M2M Mapping ([One] to [Many - Many] to [One])

i have searched the "whole" internetz for this question, and its a damn hard one to search for as its rather complicated. Try searching for "Fluent NHibernate Many to Many with a bridge table with extra columns" etc...
Okay, to make it easier to explain ill define some tables i can refer to.
Table: User, Table: Function, Table: User_Has_Function.
One User can have many Functions, and a Function can have many Users, this is linked in the bridge table User_Has_Function. The bridge table has extra columns which is only relevant to the relationship.
Well anyways iv found that FNH doesn't have any automatic solution to this, basically you have to use a one to many relation from User to User_Has_Function and many to one from User_Has_Function to Function, hence "[One] to [Many - Many] to [One]".
I have solved it like in this link http://sessionfactory.blogspot.com/2010/12/many-to-many-relationships-with.html just with FNH class mapping instead of xml obviously.
But im not satisfied with the solution, do i really have to do all this manually work to make this function properly? Also as it is now it inserts duplicates in the bridge table.
In my head i'm doing something wrong, cause i cant imagine there is no support for this. Just use SaveAndUpdate(), no duplicates are inserted and when i remove an entity the relation is removed as well, if no relations are left remove the entity itself etc.
Okay here are my entities and mappings, I am VERY new to Fluent NHibernate so don't yell to much if i have done something very wrong. :)
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
public virtual XUser XUser { get; set; }
public virtual XFunction XFunction { get; set; }
public virtual DateTime DeployedDate { get; set; }
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("USER_HAS_FUNCTION");
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).ForeignKey("XUSER_ID").Cascade.SaveUpdate();
References(x => x.XFunction).ForeignKey("XFUNCTION_ID").Cascade.SaveUpdate();
}
}
I don't understand the "do i really have to do all this manual work" part. What "all this manual work"? There is nothing special there. The mapping is simple and the c# code doesn't have to do anything with persistency, it's plain old OO design.
If you get duplicated rows, there is something wrong with your mapping. It might be because of a inverse collection which had not been mapped as inverse.
If you don't need to navigate from Function to User, it's very easy. Either map the relation as entity, as described in the blog, or even easier, map it as a composite element.
(Sorry, I don't know Fluent)
<bag name="Functions" table="User_Has_Function">
<key column="UserId" />
<composite-element>
<many-to-one class="Function"/>
</composite-element>
</bag>
Edit:
From the comments:
The manual work I am talking about is the manual getting and checking
to remove and add relations from a user or function.
Are you talking about the required Add and Remove methods, which maintain the consistency of the relations? This is plain OO design. If you hadn't NHibernate, you would have to write it exactly the same (given the same class model).
delete a user from a function make it cascade all the way to user and
so forth...
No. Delete-cascading happens when an object is deleted. When you delete a user, you should cascade the user_has_function. From there, you may or may not cascade the functions. The same in the other direction. There is also the concept of "cascade-all-delete-orphans". It means that additionally to regular cascading, an object is deleted automatically when it is removed from the collection. This is not cascading. It is a kind of very basic garbage collection. If you want to make use of this in your case, you should not apply it to both the user->user_has_function collection and the function->user_has_function collection, because it would try to delete the object twice.
Don't forget to map both collections inverse. If you don't, you may get duplicated entries.
Make sure that the three mappings (the user->user_has_function collection, the function->user_has_function and the user_has_function class mapping) are using the same table name and foreign key names.
You don't need to mess around with composite keys.
I ended up doing something similar a while ago with user, group, user_group and ended up having to use a hacky method of having both objects exist on both sides and also manually choose between save or update.
I don't think there is a NICE way to do what you want, and I agree it is something that from a database point of view is fairly logical to do, but from a modelling point of view is a pain.
As I also assume you are having to use a composite key for your user_has_function table to make sure that you can have multiple functions for multiple users. Which I think most people try to avoid and end up using surrogate keys or some other approach.
I know this isn't an answer, but I never found a real answer to the same question when I posted it.
Here is a similar question I posted a while back:
Nhibernate composite key question
I ended up using an ISet instead of having the relations in ILists. ISet does not allow duplicates, but IList does. To use ISet you have to override the Equals and GetHashCode methods for the object stored in the ISet.
I cascade from XUser and XFunction and not the other way around, ended up that every record in all 3 tables were deleted when i deleted one entity because of cascading.
Here is how i solved it.
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xFunction.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xUser.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
...
public virtual DateTime DeployedDate { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as XUserHasXFunction;
if (t == null)
return false;
return XUser == t.XUser && XFunction == t.XFunction;
}
public override int GetHashCode()
{
return (XUser.Id + "|" + XFunction.Id).GetHashCode();
}
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).KeyColumn("XUSER_ID").Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions)KeyColumn("XFUNCTION_ID").Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER_HAS_XFUNCTION");
...
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).Column("XUSER_ID");
References(x => x.XFunction).Column("XFUNCTION_ID");
}
}
Usage:
To add relations.
xFunction.AddXUser(xUser, isActive); //visa versa if you like to add a function to a user...
dao.Store(xFunction); //to actually add the relation in the db
now to remove relation
xFunction.RemoveXUser(xUser); //Realtion is removed but neither of the objects xFunction or xUser
dao.Store(xFunction); //...same
to remove a user and its relations.
dao.delete(xUser); //but the xFunction object it was connected to is not removed
//if you want the xFunction object to be removed you have to do that manually.

Categories

Resources