How can I use Fluent NHibernate Automapping with multiple Lists of the same type in an Entity? - nhibernate

It appears that NHibernate cannot automap more than one IList of a given type in an entity.
Consider the following two entities (based on the Examples.FirstProject sample code that is included with the Fluent NHibernate source code).
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Store
{
public virtual int Id { get; private set; }
public virtual IList<Employee> Staff { get; set; }
public virtual IList<Employee> Managers { get; set; }
}
This seems to be a perfectly valid object model - each store has several staff employees and several manager employees.
But when I automap, the Staff and Managers lists are stored in the Employee table,all with the same foreign key.
Employee Table
Id FirstName LastName Store_id
3 Daisy Harrison 1
4 Jack Torrance 1
5 Sue Walkters 1
6 Tom Tommorow 1
7 Dick Diggler 1
The net result is that when the data is read back out of the database, both Staff and Managers lists are populated with every row in the table.
This looks like a bug in Automapping to me, but I'm fairly new to NHibernate in any form, and don't fully know it's limitations yet.
In any case, how can I make NHibernate treat the two lists as distinct?
If possible, I'd appreciate an Automapping code fragment that directly addresses the sample code I've provided (e.g. something like "put this exact override in the .Mappings section of your CreateSessionFactory").
This is because I'm only somewhat familiar with Automapping, and not at all familiar with the older ways of doing things, which means I can't "fill in the blanks" very well yet.
But if you only have time to point me in the right direction, that would be helpful too.
Here's my CreateSessionFactory code, to give some context:
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<Product>()
.Where(t => t.Namespace == "Examples.FirstProject.Entities")
.Conventions.Add( DefaultCascade.All() )
;
sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile)
.ShowSql()
)
.Mappings(m => m.AutoMappings.Add(autoPersistenceModel)
.ExportTo(autoMapExportDir)
)
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory()
;
}
catch (Exception e)
{
Console.WriteLine(e);
}
return sessionFactory;
}

Paul Batum answered my question here, and provided a standalone working example here (click the Download button after you navigate to the linked page).
The following code is copied from his answer. The key point is in the StoreMap class at the end of the listing, which sets up an override with a Where clause that uses the IsManager property in Employee.
Note that (at least with v. 1.0.0.594) there is one big gotcha with Automapping - the mapping class (e.g. StoreMap) cannot be in the same Namespace as the domain class (e.g. Store)!
Otherwise, NHibernate will throw "NHibernate.MappingException:
(XmlDocument)(2,4): XML validation error: ..." , with
absolutely no indication of what or where the real problem is.
This is probably a bug that may be fixed in later versions of Fluent NHibernate.
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual bool IsManager { get; set; }
}
public class Store
{
public virtual int Id { get; private set; }
public virtual IList<Employee> Staff { get; private set; }
public virtual IList<Employee> Managers { get; private set; }
public Store()
{
Staff = new List<Employee>();
Managers = new List<Employee>();
}
public void AddManager(Employee employee)
{
employee.IsManager = true;
this.Managers.Add(employee);
}
public void AddStaff(Employee employee)
{
this.Staff.Add(employee);
}
}
Here is the mapping override for store:
// Must be in different Namespace from class Store!!!
public class StoreMap : IAutoMappingOverride<Store>
{
public void Override(AutoMapping<Store> mapping)
{
mapping.HasMany(x => x.Managers)
.Cascade.All()
.Where("(IsManager = 1)");
mapping.HasMany(x => x.Staff)
.Cascade.All()
.Where("(IsManager = 0)");
}
}

Related

Doesn't update many to many attribute using NHibernate

Suppose I have only two classes: Group and User. User has groups and Group has members (instance of users)
public class User {
public virtual int id { set; get; }
public virtual string username { set; get; }
public virtual IList<Group> groups { set; get; }
public User()
{
groups = new List<Group>();
}
public virtual void joinGroup(Group group)
{
if (this.groups.Contains(group))
throw new AlreadyJoinedException();
group.members.Add(this);
this.groups.Add(group);
}
public class Group
{
public virtual int id { set; get; }
public virtual string name { set; get; }
public virtual User administrator { set; get; }
public virtual IList<User> members { set; get; }
public Group()
{
members = new List<User>();
}
As you can see the domain it's quite simple. I've already mapped both classes correctly using Fluent NHibernate,
public class UserMapping : ClassMap<User>
{
public UserMapping()
{
this.Id(user => user.id).GeneratedBy.Identity();
this.Map(user => user.username).Not.Nullable().Length(50).Not.LazyLoad();
this.HasManyToMany(user => user.groups).Table("MemberPerGroup").ParentKeyColumn("id_user").ChildKeyColumn("id_group").Not.LazyLoad();
}
}
public class GroupMapping : ClassMap<Group>
{
public GroupMapping()
{
this.Id(group => group.id).GeneratedBy.Identity();
this.Map(group => group.name).Not.Nullable().Length(50).Not.LazyLoad();
this.References(group => group.administrator).Not.Nullable().Not.LazyLoad();
this.HasManyToMany(group => group.members).Table("MemberPerGroup").ParentKeyColumn("id_group").ChildKeyColumn("id_user").Not.LazyLoad();
}
}
I'm progamming a web application using ASP MVC 4. My problem shows up when a user tries to join group. It doesn't break but it neither works fine (doesn't insert into the table the new row in MemberPerGroup). I'm doing something like it:
public void JoinGroup(User user,Group group){
this.userRepository.GetSessionFactory().TransactionalInterceptor(() =>
{
user.joinGroup(group);
});
}
Thanks in advance.
Ivan.
It seems your mapping has no cascading set?
this.HasManyToMany(group => group.members)
.Table("MemberPerGroup")
.ParentKeyColumn("id_group")
.ChildKeyColumn("id_user")
.Not.LazyLoad()
.Cascade.SaveUpdate();
I'm curious - why do you use GetSessionFactory()? our repositories take an ISession object in the constructor, (injected by autofac, but that's irrelevant) from which we start our queries:
// even better to use a transaction, but this is just a sample
_session.SaveOrUpdate(user);
_session.Flush();

How do I use Fluent NHibernate ReferencesAny mapping?

I've read a lot about Fluent NHibernate's ReferencesAny but I haven't seen a complete example. I think I understand most of it, but there is one part I don't get. In the class mapping ReferencesAny(x => x.MemberName) is used to define the relationship to the one or more referenced classes. What is MemberName? How is it defined and how is it used to create the data in the database.
I have three tables, the records in one table can reference records in one of the other two tables. The first two are auto mapped, so the Id field is not specifically defined.
public class Household
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
public class Client
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
I'm not sure if the AddressXref table can be auto mapped. If so I need to find out how to do that too. For now I'll do it the conventional way with Fluent.
public class AddressXref
{
public virtual int id { get; set; }
public virtual string TableName { get; set; }
public virtual Int32 Table_id { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
class AddressXrefMap : ClassMap<AddressXref>
{
public AddressXrefMap()
{
Table("AddressXref");
Id(x => x.id);
Map(x => x.TableName);
Map(x => x.Table_id);
Map(x => x.Street);
Map(x => x.City);
ReferencesAny(x => x.TableRef)
.AddMetaValue<Household>(typeof(Household).Name)
.AddMetaValue<Client>(typeof(Client).Name)
.EntityTypeColumn("TableName")
.EntityIdentifierColumn("Table_id")
.IdentityType<int>();
}
}
The part I need help with is how is the TableRef, referred to in ReferencesAny(), member of AddressXref defined in the class?
Also, how it is used in the code when creating data records? I image it will be similar to this:
Household Household = new Household();
Household.Name = "Household #1";
AddressXref AddrXref = new AddressXref();
AddrXref.Street1 = "123 Popular Street";
AddrXref.City = "MyTown";
AddrXref.TableRef = Household;
Session.SaveOrUpdate(AddrXref);
I love using Fluent with NHibernate, but I'm still amazed at the learning curve. :)
Thanks,
Russ
since both Household and Client don't share a base class other than object you have to declare it as this:
public class AddressXref
{
public virtual int Id { get; set; }
public virtual object TableRef { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
and test it like this
if (addrXref.TableRef is HouseHold)
// it's a household

FluentNhibernate many-to-many and Inverse()

I have the following database tables defined:
Club: Id, Name
Member: Id, Name
ClubMember: ClubId, MemberId
I have the following entity Classes defined:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Member> Members { get; set; }
}
public class Member() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Club> Clubs { get; set; }
}
I have the following overrides defined:
public class MemberOverride : IAutoMappingOverride<Member>
{
public void Override(AutoMapping<Member> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Clubs)
.ParentKeyColumn("MemberId")
.ChildKeyColumn("ClubId")
.Cascade.All()
.Table("ClubMembers");
}
}
public class ClubOverride : IAutoMappingOverride<Club>
{
public void Override(AutoMapping<Club> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Members)
.ParentKeyColumn("ClubId")
.ChildKeyColumn("MemberId")
.Inverse()
.Table("ClubMembers");
}
}
I can see from my overrides that the Inverse on the ClubOverride means you cannot do the following
session.Save(club.Members.Add(member));
but this works:
session.Save(member.Clubs.Add(club);
But it doesn't make logical sense. I want to be able to save either the club with members or member with clubs.
Am I trying to do something impossible with FluentNhibernate?
TIA
Yes, you're right, that's not possible. But it's not a question of FluentNhibernate, NHibernate works like that.
Only one side is the owner of the relation and on charge of adding elements.
From official documentation:
Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:
You can create add or remove methods on your entities that will help accomplish this:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
private IList<Member> members;
public virtual IEnumerable<Member> Members { get { return members.Select(x => x); } }
public Club() {
members = new List<Member>();
}
public virtual void AddMember(Member member){
if (members.Contains(member))
return;
members.Add(user);
member.AddClub(this);
}
public virtual void RemoveMember(Member member){
if (!members.Contains(member))
return;
members.Remove(member);
member.RemoveClub(this);
}
}

How do I map a typed Dictionary in fluent nHibernate using automapping?

My class:
[PersistClass]
public class ExternalAccount
{
public virtual AccountType AccountType { get; set; }
public virtual int Id { get; private set; }
public virtual User User { get; set; }
public virtual Dictionary<string, string> Parameters { get; set; }
public ExternalAccount()
{
Parameters = new Dictionary<string, string>();
}
}
The Dictionary is not getting mapped. I understand that automapping doesn't work by default with Dictionaries, how do I configure the mapping? All Parameters is is a list of key/value pairs - so I would expect them to be stored in a table with a foreign key to the externalaccount table. I know I can do this with another class - but it makes access to the parameters in the class more difficult - I'd rather have to configure the complexity once.
Please bear in mind I am new Fluent and to nHibernate.
Thanks
Using a simple class relationship such as the following:
public class Foo {
public virtual IDictionary<string, Bar> Bars { get; set; }
}
public class Bar {
public virtual string Type { get; set; }
public virtual int Value { get; set; }
}
You can map this with Fluent NHibernate in this way:
mapping.HasMany(x => x.Bars)
.AsMap(x => x.Type);
Where Bar.Type is used as the index field into the dictionary.
FluentNHibernate mapping for Dictionary

Fluent Nhibernate Mapping Single class on two database tables

I am having problems with Mapping.
I have two tables in my database as follows: Employee and EmployeeManagers
Employee
EmployeeId int
Name nvarchar
EmployeeManagers
EmployeeIdFk int
ManagerIdFk int
So the employee can have 0 or more Managers. A manager itself is also an Employee.
I have the following class to represent the Employee and Managers
public class Employee
{
public virtual int Id
{
get;
set;
}
public virtual string Name
{
get;
set;
}
public virtual IList<Employee> Managers
{
get;
protected set;
}
public Employee()
{
Managers = new List<Employee>();
}
}
I don't have any class to represent Manager because I think there is no need for it, as Manager itself is an Employee.
I am using autoMapping and I just can't figure out how to map this class to these two tables. I am implementing IAutoMappingOverride for overriding automappings for Employee but I am not sure what to do in it.
public class NodeMap : IAutoMappingOverride
{
public void Override(AutoMapping<Node> mapping)
{
//mapping.HasMany(x => x.ValidParents).Cascade.All().Table("EmployeeManager");
//mapping.HasManyToMany(x => x.ValidParents).Cascade.All().Table("EmployeeManager");
}
}
I also want to make sure that an employee can not be assigned the same manager twice. This is something I can verify in my application but I would like to put constraint on the EmployeeManager table (e.g. a composite key) so a same manager can not be assigned to an employee more than once.
Could anyone out there help me with this please?
Awaiting
Nabeel
I did something like this, maybe it could help you start out?
http://www.dbones.co.uk/blog/post/2010/04/Nhib-Self-Reference-Object.aspx
Edit opps i can see its multiple managers
Bones
I end up doing like this
public abstract class Node
{
public virtual int Id
{
get;
set;
}
public virtual Node ParentNode
{
get;
set;
}
public virtual IList<Node> ChildNodes
{
get;
protected set;
}
protected Node()
{
ChildNodes = new List<Node>();
}
public virtual void AddChildNode( Node childNode )
{
childNode.ParentNode = this;
ChildNodes.Add( childNode );
}
}
public class NodeMap : IAutoMappingOverride<Node>
{
public void Override( AutoMapping<Node> mapping )
{
//self referencing
//http://stackoverflow.com/questions/1547956/fluent-nhibernate-automappings-with-self-reference
mapping.References( x => x.ParentNode ).Column( "ParentNodeFk" ).Cascade.SaveUpdate();
mapping.HasMany( x => x.ChildNodes ).Cascade.SaveUpdate().KeyColumn( "ParentNodeFk" );
}
}