How do I add records using Nhibernate ManyToMany? - nhibernate

I have an Actor and Movie class. An actor can play in many movies and a movie can have many actors. So I have written these class and mappings
class Actor
{
public Actor()
{
Movies = new List<Movie>();
}
public virtual int ActorId { get; set; }
public virtual string ActorName { get; set; }
public virtual IList<Movie> Movies { get; set; }
}
class ActorMap : ClassMap<Actor>
{
public ActorMap ()
{
Id(x => x.ActorId).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.Movies)
.Cascade.All()
.Table("ActorMovies");
}
}
class Movie
{
public virtual Guid MovieId { get; set; }
public virtual string MovieName { get; set; }
public virtual IList<Actor> Actors { get; set; }
}
class MovieMap : ClassMap<Movie>
{
public MovieMap ()
{
Id(x => x.MovieId).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Actors)
.Cascade.All()
.Inverse()
.Table("ActorMovies");
}
}
And db connections
public class Demo
{
private static ISessionFactory _sessionFactor;
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactor == null)
{
InitializeSessionFactory();
}
return _sessionFactor;
}
}
private static void InitializeSessionFactory()
{
_sessionFactor = Fluently.Configure()
.Database(MySQLConfiguration.Standard
.ConnectionString(
"Server=localhost;Database=movie;Uid=xx;Pwd=xx;"
))
.Mappings(mappings => mappings.FluentMappings
.AddFromAssemblyOf<Program>())
.BuildSessionFactory();
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
I am stuck at this point. How do I add actors and movies? When think of creating an actor, there should already movies in which he/she played and in this case If I add a movie first, the same problem still occurs since I need actors. I am new to database operations and nhibernate if the answer is obvious I am sorry.
class Program
{
static void Main(string[] args)
{
using var session = Demo.OpenSession();
using var transaction = session.BeginTransaction();
Actor actor = new Actor();
transaction.Commit();
}
}

You can try something like this:
Create Movie object
Movie killBill = new Movie {
Name = "Kill Bill"
};
Create Actor object
Actor umaThurman = new Actor
{
Name = "Uma Thurman",
};
In your actor object Add to the Movies the movie object which has been created:
umaThurman.Movies.Add(killBill);
In your movie object Add to the Actors the actor object which has been created:
killBill.Actors.Add(umaThurman);
And again with the next actor:
Actor davidCarradine = new Actor
{
Name = "David Carradine",
};
davidCarradine.Movies.Add(killBill);
killBill.Actors.Add(davidCarradine);
Just use the session's save method passing the killBill movie object (save once and the NHibernate will do all, it will save killBill movie object and you actors associated in both ways: Movies with actors and actors with movies):
session.Save(killBill);
Then commit:
transaction.Commit();
PS.: Don't forget to initialize your Lists in Actor and Movie classes:
// Actor Class
public virtual IList<Movie> Movies { get; set; } = new List<Movie>();
// Movie Class
public virtual IList<Actor> Actors { get; set; } = new List<Actor>();

Related

NHibernate bi-directional association

I am trying to model a parent/child association where a Parent class (Person) owns many instances of a child class (OwnedThing) - I want the OwnedThing instances to be saved automatically when the Person class is saved, and I want the association to be bi-directional.
public class Person
{
public class MAP_Person : ClassMap<Person>
{
public MAP_Person()
{
this.Table("People");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.FirstName);
this.HasMany(x => x.OwnedThings).Cascade.AllDeleteOrphan().KeyColumn("OwnerID").Inverse();
}
}
public virtual Guid ID { get; private set; }
public virtual string FirstName { get; set; }
public virtual IList<OwnedThing> OwnedThings { get; set; }
public Person()
{
OwnedThings = new List<OwnedThing>();
}
}
public class OwnedThing
{
public class MAP_OwnedThing : ClassMap<OwnedThing>
{
public MAP_OwnedThing()
{
this.Table("OwnedThings");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.Name);
this.References(x => x.Owner).Column("OwnerID").Access.BackingField();
}
}
public virtual Guid ID { get; private set; }
public virtual Person Owner { get; private set; }
public virtual string Name { get; set; }
}
If I set Person.OwnedThings to Inverse then the OwnedThing instances are not saved when I save the Person. If I do not add Inverse then the save is successful but person.OwnedThings[0].Owner is always null after I retrieve it from the DB.
UPDATE
When saving the data NHibernate will set the single association end in the database because it is set via the many-end of the association, so when I retrieve the OwnedThing from the DB it does have the link back to the Person set. My null reference was from Envers which doesn't seem to do the same thing.
Am I understanding you correctly that your problem only occur on "history" entities read by nhibernate envers?
If so, it might be caused by this bug
https://nhibernate.jira.com/browse/NHE-64
The workaround for now is to use Merge instead of (SaveOr)Update.
OwnedThings[0].Owner is most likely null because you are not setting it when you do the add. When using bidirectional relationships you have to do something like the below:
Person person = new Person();
OwnedThing pwnedThing = new OwnedThing();
pwnedThing.Owner = person;
person.OwnedThings.Add(pwnedThing);
If you do not explicity set the pwnedThing.Owner and you query that same object in the same ISession that you created it on it will be null. Typically I have add or remove methods that do this "extra" work for me. Take the below example:
public class Order : Entity
{
private IList<OrderLine> orderLines;
public virtual IEnumerable<OrderLine> OrderLines { get { return orderLines.Select(x => x); } }
public virtual void AddLine(OrderLine orderLine)
{
orderLine.Order = this;
this.orderLines.Add(orderLine);
}
public virtual void RemoveLine(OrderLine orderLine)
{
this.orderLines.Remove(orderLine);
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
DynamicUpdate();
Table("ORDER_HEADER");
Id(x => x.Id, "ORDER_ID");
HasMany(x => x.OrderLines)
.Access.CamelCaseField()
.KeyColumn("ORDER_ID")
.Inverse()
.Cascade.AllDeleteOrphan();
}
}

NHibernate : update an ISet collection

See the class and mapping below. I'd like in some case update the address (at this time, it's all the time one address).
I do this :
var customer = session.Get<Customer>(customerId);
customer.Address.Clear();
customer.Address.Add(address);
address is coming from a form, the id field is not = 0 (when 0, at creation, no problem)
but when I do this :
session.Save(customer);
session.Commit();
I receive an exception on the commit (14 is the id of CustomerAddress) :
a different object with the same identifier value was already associated with the session: 14, of entity: CustomerAddress
What is the way to update this address ?
Thanks,
Class and Mapping
public class Customer
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual Iesi.Collections.Generic.ISet<CustomerAddress> Address { get; set; }
public Customer()
{
Address = new Iesi.Collections.Generic.HashedSet<CustomerAddress>();
}
}
public class CustomerAddress
{
public virtual int Id { get; set; }
public virtual string Street { get; set; }
public virtual Customer Customer { get; set; }
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.LastName)
.Length(50)
.Not.Nullable();
HasMany(x => x.Address)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan()
.Not.LazyLoad();
}
}
public class CustomerAddressMap : ClassMap<CustomerAddress>
{
public CustomerAddressMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Street).Length(50);
References(x => x.Customer);
}
}
If the address you are saving already has an id assigned to it you can simply do the following:
var customer = session.Get<Customer>(customerId);
Session.Merge(address); // This will copy your transient entity into
// ..an entity with the same id that is in the
// ..first level cache.
session.SaveOrUpdate(customer);
You are getting that error because and address with an ID of 14 is already associated with your NHibernate session. So when you create the new detached transient entity and try to save it with that id NHibernate throws an error. Generally this is a good thing as it is very rare to want to do what you are doing.
A far better pattern would be to have a ViewModel for the fields of an address that are changeable by a user, and then do the following:
var address = Session.Get<Address>(addressVM.Id);
Mapper.Map(addressVM, address); // This is some type of mapper to copy properties
// ..from one object to another. I like automapper
// ..for this
Session.SaveOrUpdate(address);
If you are updating an address, why are you clearing the collection and re-adding?
using (var tx = session.BeginTransaction())
{
var customer = session.Get<Customer>(customerId);
var address = customer.Address.Single(/*optional condition here*/);
//or, if you are not updating the Customer, this might be better
var address = session.Get<Address>(addressId);
address.Street = updatedStreetInfo;
tx.Commit();
}

NHibernate Criteria Queries - how use in One to One relationship

I have simple 3 POCO classes:
public class User
{
//PK
public virtual int UserId { get; set; }
//ONE to ONE
public virtual Profil Profil{ get; set; }
//ONE to MANY
public virtual IList<PhotoAlbum> Albums { get; set; }
}
public class Profil
{
//PK
public virtual int ProfilId { get; set; }
public virtual int Age { get; set; }
public virtual int Sex { get; set; }
}
public class PhotoAlbum
{
//PK
public virtual int PhotoAlbumId { get; set; }
public virtual string Name { get; set; }
public virtual int NumberOfPhoto { get; set; }
}
I created these mapping classes:
public class UserMap : ClassMap<User>
{
public UserMap()
{
//PK
Id(p => p.UserId)
.GeneratedBy.Identity();
//FK
References(p => p.Profil)
.Column("ProfilId")
.Cascade.All();
//ONE TO MANY
HasMany(p => p.Albums)
.Cascade.All();
Table("Users");
}
}
public class ProfilMap: ClassMap<Profil>
{
public ProfilMap()
{
Id(p => p.ProfilId)
.GeneratedBy.Identity();
Map(p => p.Age)
.Not.Nullable();
Map(p => p.Sex)
Table("Profiles");
}
}
public class PhotoAlbumMap : ClassMap<PhotoAlbum>
{
public PhotoAlbumMap()
{
Id(p => p.PhotoAlbumId)
.GeneratedBy.Identity();
Map(p => p.Name)
.Not.Nullable();
Map(p => p.NumberOfPhoto)
.Not.Nullable();
Table("PhotoAlbums");
}
}
Then I created simple NHibernate repository class with this method:
public IList<T> GetItemsByCriterions(params ICriterion[] criterions)
{
ICriteria criteria = AddCriterions(_session.CreateCriteria(typeof(T)),
criterions);
IList<T> result = criteria.List<T>();
return result ?? new List<T>(0);
}
For test I created repository for some entity, for example User:
_userRepo = new NHibRepository<User>(NHibeHelper.OpenSession());
and I would like have possibility make query in this style:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Gt("Profile.Age",10)
});
this attempt finished with error:
could not resolve property: Profile of: Repository.User
User has property Profile type of Profile and this property has properties ProfileId, Age
and sex.
** #1 EDITED:**
# I tried this:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Where<User>(u=>u.Profil.Sex==0)
});
finished with error:
could not resolve property: Profil.Sex of: Repository.User
#2 EDITED
I tried use Nathan’s advice:
var result = _userRepo.Session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Sex", 0));
IList<User> users=null;
if (result != null)
users = result.List<User>();
If I tried convert result to List I again get this error: could not resolve property: Profile of: Repository.User
Looking at your example, User has a Profil property not a Profile property.
If it is supposed to be Profil then I would change the Restrictions.Gt(Profile.Age,10) to Restrictions.Gt(Profil.Age,10) otherwise change the name of the property and mapping to match the query.
Edit:
You are trying to query the User Object. you need to include the CreateAlias let nhibernate know that you want to link to a different object.
Try This.
var users = session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Age", 10));

Fluent Nhibernate many-to-many issue

I have three tables:
Person (Id, FirstName)
Organization (Id, Name)
PersonOrganization (PersonId, OrganizationId, Details) many-to-many table
When I first mapped this using Fluent NHibernate I did not have a details column in the PersonOrganization table and mapped this using HasManyToMany in the PersonMap and OrganizationMap (no need to create a PersonOrganization domain object or map). I could writethe following code:
Organization org = new Organization { Name = "org" };
People people = new People { FirstName = "firstname", Organization = org };
peopleRepository.Add(people); // ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
NHhibernate happily committed the data to all three tables.
The issue comes up when I added the details column in the PersonOrganization table. After some research it turns out that I now have to create a new PersonOrganization domain object and map and setup a HasMany relationship for both Person and Organization. My updated model and maps below:
public class People
{
public People()
{
LinkToOrganization = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual IList<PeopleOrganization> LinkToOrganization { get; set; }
}
public class Organization
{
public Organization()
{
LinkToPeople = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PeopleOrganization> LinkToPeople { get; set; }
}
public class PeopleOrganization
{
public virtual People People { get; set; }
public virtual Organization Organization { get; set; }
public virtual string Details { get; set; }
}
public class PeopleMap : ClassMap<People>
{
public PeopleMap()
{
Id(p => p.Id);
Map(p => p.FirstName).Length(100);
HasMany(x => x.LinkToOrganization)
.Table("PeopleOrganization")
.KeyColumn("PeopleId")
.AsBag()
.Inverse();
}
}
public class OrganizationMap : ClassMap<Organization>
{
public OrganizationMap()
{
Id(p => p.Id);
Map(p => p.Name).Length(50);
HasMany(x => x.LinkToPeople)
.Table("PeopleOrganization")
.KeyColumn("OrganizationId")
.AsBag()
.Inverse();
}
}
public class PeopleOrganizationMap : ClassMap<PeopleOrganization>
{
public PeopleOrganizationMap()
{
CompositeId()
.KeyReference(p => p.People, "PeopleId")
.KeyReference(p => p.Organization, "OrganizationId");
Map(p => p.Details).Length(100);
}
}
I now have to write the following code:
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // ITransaction.Begin() ISession.Save(people)
organizationRepository.Add(org); // ISession.Save(org)
peopleOrganizationRepository.Add(po); // ISession.Save(po)
unitOfWork.Commit(); // ITransaction.Commit()
My questions are:
Are my mappings correctly setup to support this kind of many-to-many scenario?
Is there are way for me to just be able to do the following (which would write to all three tables):
-
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // Just ONE ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
Any input is highly appreciated.
Thanks
Firstly, you'll need to add your newly created PeopleOrganization to the collection on both entities (Organization and People). Then if you add a Cascade.All() to your HasMany chain, the saves should propagate (cascade) down to the PeopleOrganization and subsequently to Organization.
As a purely semantic suggestion, I'd recommend encapsulating the PeopleOrganization creation into a method on Person. Something like this:
class Person
{
public void AddOrganization(Organization org, string details)
{
var link = new PeopleOrganization { ... };
LinkToOrganization.Add(link)
org.LinkToPeople.Add(link);
}
}
That way you never have to deal with creating the intrim entity yourself.

Many to Many relationship with Fluent NHibernate

I'm getting the following error: "Can't figure out what the other side of a many-to-many should be."
Team Entity:
public class Team : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Employee> Employee { get; set; }
public Team()
{
Employee = new List<Employee>();
}
}
Employee Entity:
public class Employee : IEntity
{
public int Id { get; set; }
public String LastName { get; set; }
public string FirstName { get; set; }
public IList<Team> Team { get; set; }
public string EMail { get; set; }
public Employee()
{
Team = new List<Team>();
}
}
Team mapping:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
// identity mapping
Id(p => p.Id);
// column mapping
Map(p => p.Name);
// relationship mapping
HasManyToMany<Employee>(m => m.Employee);
}
}
Employee mapping:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
// identifier mapping
Id(p => p.Id);
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// relationship mapping
HasManyToMany<Team>(m => m.Team);
}
}
Nobody has an answer?
Edit: The error occurs on the following code:
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c=>
c.Database("Ariha")
.TrustedConnection()
.Server("localhost")
).ShowSql())
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<BookMap>()
.AddFromAssemblyOf<MagazineMap>()
.AddFromAssemblyOf<EmployeeMap>()
.AddFromAssemblyOf<TeamMap>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
edit: here the whole solution: http://rapidshare.com/files/309653409/S.O.L.I.D.Ariha.rar.html
Could you provide code that causes your error? I just tried your mappings and they seem to work fine (on fluent 1.0 RTM and NH 2.1.1 GA with an SQLite database), with a minor modification to your EmployeeMap (I have assumed the employee-team relationship is bidirectional, and as per documentation you need to mark one side as inverse).
// relationship mapping
HasManyToMany<Team>(m => m.Team).Inverse();
Of course if the employee-team relationship is not bidirectional, I would have thought you should be able to specify a different .Table(name) for each one - but I have not tested this and you seem to be getting different results anyway (hence why providing example code would be best)
I'd also add that I suspect Set semantics (instead of Bag) would be more appropriate for the Employee.Team and Team.Employee properties. (Irregardless, don't do anything that assumes order is preserved, there is no guarantee that it will be)
Suggested mapping and example:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employee { get; set; }
public Team() { Employee = new List<Employee>(); }
}
public class Employee
{
public int Id { get; set; }
public String LastName { get; set; }
public string FirstName { get; set; }
public ICollection<Team> Team { get; set; }
public string EMail { get; set; }
public Employee() { Team = new List<Team>(); }
}
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Not.LazyLoad();
// identity mapping
Id(p => p.Id);
// column mapping
Map(p => p.Name);
// relationship mapping
HasManyToMany<Employee>(m => m.Employee).AsSet();
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Not.LazyLoad();
// identifier mapping
Id(p => p.Id);
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// relationship mapping
HasManyToMany<Team>(m => m.Team).Inverse().AsSet();
}
}
[TestFixture]
public class Mapping
{
[Test]
public void PersistDepersist()
{
var fcfg = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile("testdb.sqldb"))
.Mappings(mc =>
{
mc.FluentMappings.Add(typeof (TeamMap));
mc.FluentMappings.Add(typeof (EmployeeMap));
})
.ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(false, true, false));
var sess = fcfg.BuildSessionFactory().OpenSession();
var teams = Enumerable.Range(0, 4).Select(i => new Team() {Name = "Team " + i}).ToArray();
var employees = Enumerable.Range(0, 10).Select(i => new Employee() {FirstName = "Employee " + i}).ToArray();
teams[0].Employee = new List<Employee>() {employees[0], employees[3], employees[5]};
teams[1].Employee = new List<Employee>() {employees[7], employees[2], employees[5]};
teams[3].Employee = new List<Employee>() {employees[0], employees[8], employees[9]};
foreach (var team in teams)
foreach (var employee in team.Employee)
employee.Team.Add(team);
Console.WriteLine("Dumping Generated Team/Employees:");
Dump(teams);
Dump(employees);
using (var t = sess.BeginTransaction())
{
foreach (var team in teams)
sess.Save(team);
foreach (var employee in employees)
sess.Save(employee);
t.Commit();
}
sess.Flush();
sess.Clear();
var teamsPersisted = sess.CreateCriteria(typeof (Team)).List<Team>();
var employeesPersisted = sess.CreateCriteria(typeof (Employee)).List<Employee>();
Assert.AreNotSame(teams, teamsPersisted);
Assert.AreNotSame(employees, employeesPersisted);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Dumping Depersisted Team/Employees:");
Dump(teamsPersisted);
Dump(employeesPersisted);
}
private static void Dump(IEnumerable<Team> teams)
{
foreach (var team in teams)
Console.WriteLine("Team: " + team.Name + " has members: " + string.Join(", ", team.Employee.Select(e => e.FirstName).ToArray()));
}
private static void Dump(IEnumerable<Employee> employees)
{
foreach (var employee in employees)
Console.WriteLine("Employee: " + employee.FirstName + " in teams: " + string.Join(", ", employee.Team.Select(e => e.Name).ToArray()));
}
}
Fluent NHibernate tries to determine what the other side of a many-to-many relationship is by looking at the entity names and the collection properties. I believe it's not working in your case because your collection properties aren't plural; try renaming your collections Employees and Teams respectively.
Another way is to manually set the many-to-many table name on both sides, as this will disable the prediction.