I am using NHibernate. This is the employee class
public class Employee
{
public virtual int Id { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
This is the store class:
public class Store
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Employee> Staff { get; set; }
public Store()
{
Staff = new List<Employee>();
}
}
The following are the mapping classes. Employee Map:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap ()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store);
}
}
Store Map:
public class StoreMap:ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Staff);
// HasManyToMany(x => x.Products).Cascade.All();
//.Table("StoreProduct");
}
}
When I run this code:
using (session.BeginTransaction())
{
var stores = session.CreateCriteria(typeof(Store)).List<Store>();
//for (int i=0; i<stores.Count;)
//{
// Response.Write(st
//}
foreach (var item in stores)
{
Response.Write(item.Staff.ToList());
}
}
I receive the following error:
could not initialize a collection: [test.Models.Store.Staff#1][SQL:
SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id
as Id0_0_, staff0_.LastName as LastName0_0_, staff0_.FirstName as
FirstName0_0_, staff0_.Store_id as Store4_0_0_ FROM [Employee] staff0_
WHERE staff0_.Store_id=?]
You code seems to work fine for me. But I'm generating an empty scheme from scratch, don't know if you are maybe missing something or if the id references are set correctly.
If you do not specify the reference Id column for staff->Store it uses Sore_id as you can see in your query text. Does this column exist?
Anyways, if it is still not working for you, try to explicitly define a name for the .KeyColumn for your HasMany(x => x.Staff); mapping
:edit:
You need to change StoreMap to have HasMany(x => x.Staff).KeyColumn("store");
And you need to change EmployeeMap to have References(x => x.Store).Columns("store");
So that both sides link to the same reference column...
Related
I'm using NHibernate in my C# website and am having trouble trying to get it to run since the addition of the PatientInformation domain and mapping. If someone can review the code below and point to the area that is wrong, I would really appreciate the help.
I've tried updating the PatientInformationMap to include the ID field, but it doesn't seem to help prevent the error from occuring. I've tried changing the many-to-many in the PatientInformationMap to HasManyToMany<Form> but the error persists.
Judging from what I have seen in other stackoverflow posts, there is something wrong in my mapping. I haven't seen any posts that aren't using that hbm file, and I don't even know what that is so I'm not sure how those posts would help me much.
**** I'm aware I'm at risk of being voted down because my question is similar to others', but I really don't see how something pertaining to an hbm file is helpful to my situation. Thanks in advance for answering instead of immediately trying to get this question removed.
Exception occurred getter of Form.PatientInformation
Object does not match target type.
at CommonSessionManager.UnbindCurrentSession() in \CommonSessionManager.cs:line 54
at CommonSessionManager.Application_EndRequest(Object sender, EventArgs e) in \CommonSessionManager.cs:line 25
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
************* Mapping
public PatientInformationMap()
{
Schema("FormsLibrary");
Table("PatientInformation");
Map(x => x.FullName);
Map(x => x.DateOfBirth);
Map(x => x.ContactAccount);
HasManyToMany<PatientInformation>(x => x.Forms)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("PatientInformationID")
.ChildKeyColumn("FormID")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public FormMap()
{
Schema("FormsLibrary");
Table("Form");
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.FileName);
Map(x => x.MembersOnly);
Map(x => x.Status);
Map(x => x.Active);
Map(x => x.DisplayFileName);
Map(x => x.LastModified);
Map(x => x.CreatedDate);
References(x => x.User, "UserID").LazyLoad();
References(x => x.Site, "SiteID").LazyLoad();
References(x => x.Category, "CategoryID").Cascade.SaveUpdate().LazyLoad();
HasManyToMany<Form>(x => x.PatientInformation)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("FormID")
.ChildKeyColumn("PatientInformationID")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
************* Domain
public class PatientInformation : EntityBase<int>
{
public PatientInformation()
{
this.Forms = new List<Form>();
}
public virtual IList<Form> Forms { get; set; }
public virtual string FullName { get; set; }
public virtual string DateOfBirth { get; set; }
public virtual string ContactAccount { get; set; }
}
public class Form : OrderedEntityBase<int>
{
public Form()
{
this.Active = true;
this.LastModified = DateTime.Now;
this.CreatedDate = DateTime.Now;
this.PatientInformation = new List<PatientInformation>();
}
public Form(Site site)
{
this.Site = site;
this.Active = true;
this.LastModified = DateTime.Now;
this.CreatedDate = DateTime.Now;
this.PatientInformation = new List<PatientInformation>();
}
public Form(Site site, aspnet_User user)
{
this.User = user;
this.Site = site;
this.Active = true;
this.LastModified = DateTime.Now;
this.CreatedDate = DateTime.Now;
this.PatientInformation = new List<PatientInformation>();
}
public virtual void AddCategory(Category category)
{
this.Category = category;
category.Forms.Add(this);
}
public virtual Category Category { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual string FileName { get; set; }
public virtual string DisplayFileName { get; set; }
public virtual Site Site { get; protected set; }
public virtual bool MembersOnly { get; set; }
public virtual string Status { get; set; }
public virtual bool Active { get; set; }
public virtual aspnet_User User { get; set; }
public virtual DateTime LastModified { get; set; }
public virtual DateTime CreatedDate { get; protected set; }
public virtual IList<PatientInformation> PatientInformation { get; set; }
// do not map
public virtual void AddPatientInformation(PatientInformation patientInformation)
{
if (this.HasPatientInformation(patientInformation))
{
this.RemovePatientInformation(patientInformation);
}
patientInformation.Forms.Add(this);
this.PatientInformation.Add(patientInformation);
}
public virtual void RemovePatientInformation(PatientInformation patientInformation)
{
patientInformation.Forms.Remove(this);
this.PatientInformation.Remove(patientInformation);
}
public virtual bool HasPatientInformation(PatientInformation patientInformation)
{
return this.PatientInformation.Contains(patientInformation);
}
public virtual void ClearPatientInformation()
{
var deletePatientInformation = new List<PatientInformation>();
foreach (var patientInformation in this.PatientInformation)
{
deletePatientInformation.Add(patientInformation);
}
foreach (var patientInformation in deletePatientInformation)
{
this.RemovePatientInformation(patientInformation);
}
}
}
This is where I am adding data to PatientInformation, which includes some commented out code because it wasn't working, but it shows what I have tried.
PatientInformation patientInfo = new PatientInformation();
StatusPlaceHolder.Visible = true;
form.Status = StatusRadioButtonList.SelectedValue != null ? StatusRadioButtonList.SelectedValue : null;
PatientPlaceHolder.Visible = true;
patientInfo.FullName = PatientNameTextBox.Text != null ? PatientNameTextBox.Text : null;
patientInfo.DateOfBirth = DateOfBirthTextBox.Text != null ? DateOfBirthTextBox.Text : null;
patientInfo.ContactAccount = ContactAccountTextBox.Text != null ? ContactAccountTextBox.Text : null;
// need to get form ID to associate this patientinfoID to the formID in PatientInformationToForms table
//form.PatientInformation.Add(patientInfo);
//patientInfo.Form.Add(form);
form.AddPatientInformation(patientInfo);
EDITS
I changed the IList name so as not to confuse with the class PatientInformation
public virtual IList<PatientInformation> PatientInformationList { get; set; }
HasManyToMany<Form>(x => x.PatientInformationList)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("FormID")
.ChildKeyColumn("PatientInformationID")
.LazyLoad()
.Cascade.SaveUpdate();
EDIT
I added .Inverse below and got this error:
The relationship PatientInformation.Forms to PatientInformation.Forms has Inverse specified on both sides. Remove Inverse from one side of the relationship.
HasManyToMany<PatientInformation>(x => x.Forms)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("PatientInformationID")
.ChildKeyColumn("FormID")
.LazyLoad()
.Inverse()
.Cascade.SaveUpdate();
Psychic debugging isn't working for me today. But I think I just saw it. Your HasManyToMany mappings has generic type declaration in the mappings. remove those types. Fluent should be able to infer the type by the lambda expression you give it.
HasManyToMany<Form>(x => x.PatientInformation)
is in direct conflict. You are saying the many to many is expectings a Form, but you are mapping it to a PatientInformation. remove that type declaration from both sides of your mapping.
public PatientInformationMap()
{
Schema("FormsLibrary");
Table("PatientInformation");
Map(x => x.FullName);
Map(x => x.DateOfBirth);
Map(x => x.ContactAccount);
HasManyToMany(x => x.Forms)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("PatientInformationID")
.ChildKeyColumn("FormID")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public FormMap()
{
Schema("FormsLibrary");
Table("Form");
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.FileName);
Map(x => x.MembersOnly);
Map(x => x.Status);
Map(x => x.Active);
Map(x => x.DisplayFileName);
Map(x => x.LastModified);
Map(x => x.CreatedDate);
References(x => x.User, "UserID").LazyLoad();
References(x => x.Site, "SiteID").LazyLoad();
References(x => x.Category, "CategoryID").Cascade.SaveUpdate().LazyLoad();
HasManyToMany(x => x.PatientInformation)
.Schema("FormsLibrary")
.Table("PatientInformationToForms")
.ParentKeyColumn("FormID")
.ChildKeyColumn("PatientInformationID")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
Also check out this series of posts on nhibernate mappings. it is invaluable
http://notherdev.blogspot.com/2012/01/mapping-by-code-onetomany-and-other.html
I have following domain classes:
public class News: EntityBase
{
public virtual DateTime CreationDate { get; set; }
public virtual IList<DomainNameToNews> DomainNameToNews { get; set; }
public News()
{
DomainNameToNews=new List<DomainNameToNews>();
}
}
public class DomainNameToNews : EntityBase
{
public virtual DomainName DomainName { get; set; }
public virtual News News { get; set; }
}
Mapping:
public class NewsMap : ClassMap<News>
{
public NewsMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.CreationDate).Not.Nullable();
HasMany(x => x.DomainNameToNews).Cascade.AllDeleteOrphan();
}
}
public class DomainNameToNewsMap : ClassMap<DomainNameToNews>
{
public DomainNameToNewsMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.News).UniqueKey("UQ_DOMAIN_NEWS").Cascade.Delete();
References(x => x.DomainName).UniqueKey("UQ_DOMAIN_NEWS");
}
}
Explanation:
News can be used in different domains so DomainNameToNews is relationship between them. One news can be used by multiple domains.
Problem:
What I want is to delete and add DomainToNews objects through News repository.
On update of News Object this object will have a list of DomainNameToNews so When I will update News the row from DomainNameToNews that will not be in this list I want to delete at all from database .Now the row that is not in this List will have Null News ,but I want to delete at all. How I must to map my object to achieve this?
If I didn't explain myself enough clear please ask more details. Thnaks!!
You need to specify 'inverse' at the one-to-many collection association.
public class NewsMap : ClassMap<News>
{
public NewsMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.CreationDate).Not.Nullable();
HasMany(x => x.DomainNameToNews).Inverse.Cascade.AllDeleteOrphan();
}
}
public class DomainNameToNewsMap : ClassMap<DomainNameToNews>
{
public DomainNameToNewsMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.News).UniqueKey("UQ_DOMAIN_NEWS");
References(x => x.DomainName).UniqueKey("UQ_DOMAIN_NEWS");
}
}
Explanation on 'inverse' in this blog post
I have the following database structure, which I cannot change:
CREATE TABLE Users (
ID INT NOT NULL IDENTITY(1,1),
PRIMARY KEY(ID)
)
CREATE TABLE UserAvatars (
UserID INT NOT NULL,
Width INT NOT NULL,
Height INT NOT NULL,
PRIMARY KEY(UserID),
FOREIGN KEY(UserID) REFERENCES Users(ID),
)
Basically, a User can have Zero-or-one avatars (I removed columns to simplify the example).
class User
{
public int ID { get; protected set; }
public UserAvatar Avatar { get; set; }
}
class UserAvatar
{
public User User { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
I am using the following mapping:
class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.ID, m => {
m.Generator(Generators.Identity);
});
OneToOne(x => x.Avatar);
}
}
class UserAvatarMapping : ClassMapping<UserAvatar>
{
public UserAvatar()
{
Id(x => x.User, m =>
{
m.Generator(Generators.Foreign<User>(u => u.ID));
});
Property(x => x.Width);
Property(x => x.Height);
}
}
However, when trying run my app, I get an NHibernate.MappingException:
NHibernate.MappingException : Could not compile the mapping document:
mapping_by_code----> NHibernate.MappingException : Could not determine
type for: MyApp.Models.UserAvatar, MyApp, for columns:
NHibernate.Mapping.Column(User)
I can't make any sense of this cryptic error.
How do I accomplish the mapping where a User could have zero or one Avatars?
Taking from Radim Köhler's guidence, the final classes / mapping that solved the problem:
class User
{
public int ID { get; protected set; }
public UserAvatar Avatar { get; set; }
}
class UserAvatar
{
public int UserID { get; protected set; }
public User User { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
With the following mapping:
class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.ID, m => {
m.Generator(Generators.Identity);
});
OneToOne(x => x.Avatar, m =>
{
m.Cascade(Cascade.All);
m.Constrained(false);
});
}
}
class UserAvatarMapping : ClassMapping<UserAvatar>
{
public UserAvatar()
{
Id(x => x.UserID, m =>
{
m.Generator(Generators.Foreign<UserAvatar>(u => u.User));
});
OneToOne(x => x.User, m =>
{
m.Constrained(true);
});
Property(x => x.Width);
Property(x => x.Height);
}
}
The ID is important almost for every entity mapped by NHibernate (ORM). So, we should extend the Avatar class:
class UserAvatar
{
// every(stuff) should have its ID
public int ID { get; protected set; }
public User User { get; set; }
...
The ID value will be managed by one-to-one relationship. So, now let's adjust the mapping.
public UserAvatar()
{
Id(x => x.ID);
HasOne(x => x.User)
.Constrained()
.ForeignKey();
...
public UserMapping()
{
Id(x => x.ID);
HasOne(s => s.Avatar).Cascade.All();
Related and really interesting reading:
How to do a fluent nhibernate one to one mapping?
Fluent NHibernate & HasOne(): how to implement a one-to-one relationship
public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> LatestNews { set; get; }
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.LatestNews);
}
}
IRepository<Category> newsRepo = new NHibernateRepository<Category>();
using(var session = newsRepo.GetSessionFactory().OpenSession())
using(var transaction = session.BeginTransaction())
{
var result = session.Query<Category>().OrderBy(x => x.CategoryOrder);
transaction.Commit();
}
I have this category class Which I want to display a (only one) News per category. Is this correct mapping? or should i change it to Map
When i run this, it gets all the news per category. But i want the latest news per category (only one). I can get the latest news by querying News.DateUpdated.
How should i change the query to get one news per category?
or how do I get some of the News? ie: limit the number of news I can query?
I think you can have your cake and eat it too. You have a list of News items, so just leave that. But add in another property that gets just the News item you want.
public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> AllNews { set; get; }
public virtual News LatestNews
{
get
{
// probably needs some work
return this.AllNews.OrderByDescending(n => n.SomeDateField).Take(1);
}
}
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.AllNews);
}
}
you can use lazy mode extra to achieve this,
public News GetLatestNews(Category cat)
{
var session = SessionFactory.CurrentSession;
var news = session.CreateFilter(cat.LatestNews , "order by categoryorder").SetFirstResult((page - 1) * pageSize).SetFirstResult(0).SetMaxResults(1).List().FirstOrDefault();
return news;
}
however if you try to access the LatestNews collection directly, this won't be of any use as it will load all the news objects associated with Category from db which would lower the performance.
i'm new to nhibernate so maybe the response depends on my lack of knowledge.
I created these two tables:
(sorry for italian language, i hope you can understand withouth any problems).
Then, i have these object in my model:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual Profilo Profilo { get; set; }
}
This is the way i mapped entities using fluent nhibernate:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede");
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.None();
}
}
Now, i'd like to insert a new Profilo instance on my. Everything seems to work but nhibernate does not insert values on TBA_Profilo.codice_sede column. I noticed that the insert statement is composed by two parameters (matricola, ruolo) - why does it forget the third parameter?
I read somewhere (on nhibernate mailing list) that's quite normal 'cause nhiberate insert values with null first and then update the same records with right values contained in the list property.
Is it right?
Am i doing any errors?
I hope to have clarified the situation.
thx guys
ps: I'm using Nhibernate 2.1 and fluent nhibernate 1.1
UPDATE: This is the code i use to save entity.
var sesionFactory = NHibernateHelper.createSessionFactory();
using (NHibernate.ISession session = sesionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(entity);
transaction.Commit();
session.Flush();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
}
UPDATE 2: Following sly answer i slightly modified my solution. These are new model entities:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual IList ProfiliAssociati { get; set; }
}
And new mappings:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
HasMany(x => x.ProfiliAssociati)
.AsBag()
.KeyColumns.Add("codice_sede")
.Cascade.All();
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.Inverse()
.KeyColumns.Add("codice_sede")
.Cascade.SaveUpdate();
}
}
Now it seems quite to work: i mean that nhibernate can write TBA_Profilo.codice_sede column even if it doesn't cicle on Profilo.IList (it inserts the last element of this List).
Any ideas?
KeyColumns mapping is applied only child table in many-to-one connection.
If you want to have connection you will need to use Id column from TBA_portfolio table and reference it from TBA_Sede.
Something like this:
Tba_portfolio
Id|matricola|ruolo
Tba_sede
Id|PorfolioId|descrizione
Your mapping is wrong, try this:
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede")
.Cascade.None()
.Inverse();
}
}
The key is, you need the parent profilio to save its child items. Have the child items do nothing to its parent. Also, your table diagram looks wrong, profolio should not have a key to sede, but sede should have a fk to profolio.