I'm a new in nHibernate help me to save an object and his connections in the base.
I've got the base with two tables:
person (idPerson, firstName, secondName, idService
service (idService, name)
The table service has 3 positions (gold, silver, brilliant)
The Mapping:
public class PersonMap:ClassMap<Person>
{
public PersonMap()
{
Id(x => x.id);
Map(x => x.firstName);
Map(x => x.lastName);
Map(x => x.status);
References(x => x.serviceType).Column("idServiceType");
Table("Person");
}
}
public class ServiceMap : ClassMap<ServiceType>
{
public ServiceMap()
{
Id(x => x.id);
Map(x => x.serviceName);
Table("Service");
}
}
I use the next method in Repository for saving:
public void Saves(Person entity)
{
using (var session = hibernateHelp.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
ServiceRepository srp = new ServiceRepository();
NHibernateUtil.Initialize(entity.service);
session.Save(entity);
transaction.Commit();
}
}
}
I recive the date (firstName="My" lastName="Go" status=TRUE, serviceType="gold"), then I create a Person:
Person df=new Person{
firstName="My",
lastName="Go",
status=true,
serviceType=new ServiceType{serviceName="gold"}
};
When I send it to the Repository method Save(see above) the mapping is working and saving the new object in the table person and create the new note in the table service.
I need't to create the new note in the table service since it containts one. How to make the saver's method in order to save the link to the table server, but not create the new one???
I appreciate any links and suggestions.
Person newPerson = new Person
{
FirstName = "My",
LastName = "Go",
Status = true,
serviceType = session.Load<ServiceType>(idOfGold) // returns the service if loaded or a proxy representing it which is enough to save the reference
// or
serviceType = session.Query<ServiceType>().Where(st.name == "gold").Single()
};
session.Save(newPerson);
Related
I have a many to many relationship between
Portfolio and PortfolioTags
A portfolio Item can have many PortfolioTags
I am looking at the best way of saving tags to a portfolio item. My Nhibnerate maps are like so:
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AliasTitle).Column("AliasTitle").Not.Nullable();
Map(x => x.MetaDescription).Column("MetaDescription").Not.Nullable();
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Client).Column("Client").Not.Nullable();
Map(x => x.Summary).Column("Summary").Not.Nullable();
Map(x => x.Url).Column("Url");
Map(x => x.MainImage).Column("MainImage");
Map(x => x.TitleAlt).Column("TitleAlt");
Map(x => x.Description).Column("Description").Not.Nullable();
HasMany(x => x.PortfolioImage).KeyColumn("PortfolioId").Inverse();
HasMany(x => x.PortfolioTag).KeyColumn("PortfolioId").Cascade.All().Table("PortfolioTag").Inverse();
}
}
public class PortfoliotagMap : ClassMap<Portfoliotag> {
public PortfoliotagMap() {
Table("PortfolioTag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Portfolio).Column("PortfolioId");
References(x => x.Tag).Column("TagId");
}
}
public class TagMap : ClassMap<Tag>
{
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.TagId).GeneratedBy.Identity().Column("TagId");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolio).Table("PortfolioTag").ParentKeyColumn("TagId").ChildKeyColumn("PortfolioId").Inverse();
}
}
In my portfolio controller I am first trying to save my tags that do not exist. I tried using SaveOrUpdate on a tag repository. However as the ids are different multiple save of tags occurs at this point.
I thought about the following steps but it seems long winded:
1) getting all tags:
var tags = _tagRepository.GetAll();
2) Iterating over the tags from the item to save and seeing if they exist in the database. If so I would need to get the tag and associate with the portfolio item. If not i would need to save the tag one by one and then associate with the portfolio item.
var tags = _tagRepository.GetAll();
foreach (var tagInPortfolio in StringUtilities.SplitToList(model.Tags, new[] { ',' }))
{
// tag does not exist so save it
if (tags.Any(i => i.TagVal == tagInPortfolio))
{
_tagRepository.SaveOrUpdate(new Tag {TagVal = tagInPortfolio});
}
}
3) I then need to delete any relationships i.e. tags to portfolio items that dont exist.
4) Finally need to add the tag to to the portfolioTag. I would need to get all the tags again and then associate:
portfolio.PortfolioTag.Add(new Portfoliotag {Portfolio = portfolio, Tag = tag});
_portfolioRepository.UpdateCommit(portfolio);
This seems to long winded. Can anyone explain the most simplest way of doing this please.
I have looked at saveandcommit on tags but i get multiple inserts because of ids being different. Do I need to delete all existing tag relationships also as this seems to much logic for something simple.
Create now works with a commit -
public void CreateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Save(entity);
transaction.Commit();
}
}
However using the below and the above maps still meant duplicates where occurring in the tag table. So if one portfolio record added a tag like abc and another portfolio record added a tag abc i need the join table to reference the same record in the tag and not create another instance of abc. Do i need to do a lookup on the tag table to avoid this
public void UpdateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Update(entity);
transaction.Commit();
}
}
If I understood correctly, I think you misunderstood the many-to-many mapping. If you really have a relationship like this between the Portifolio and the Tag classes, you should not map the PortfolioTag table.
In a simple many-to-many relationship the table used to connect the other two main tables should have only the foreign keys from the two tables (that would also be a composite key of this intermediate table). In this case, the PortfolioTag table would have only two columns: PortfolioId and TagId, that would be not only foreign keys for the Portfolio and Tag tables, but also the primary key of this intermediate table.
In this case, your Portfolio class should have a list of Tags instead of a list of PortfolioTag, and the Tag class a list of Portfolios. And you should map the Portfolio and the Tag like this (with no need of mapping the intermediate table):
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
//
// Code intentionally omitted for clarity
//
HasManyToMany(x => x.Tags)
.Table("PortfolioTag")
.ParentKeyColumn("PortfolioId")
.ChildKeyColumn("TagId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class TagMap : ClassMap<Tag> {
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolios)
.Table("PortfolioTag")
.ParentKeyColumn("TagId")
.ChildKeyColumn("PortfolioId")
.Inverse();
}
}
You will also have to save the Portfolio inside the context of a transaction for the intermediate table to be populated, like bellow:
using (var trans = Session.BeginTransaction()) {
try {
Session.SaveOrUpdate(obj);
trans.Commit();
} catch {
trans.Rollback();
throw;
}
}
//or like this (with TransactionScope)
using (var trans = new TransactionScope()) {
Session.SaveOrUpdate(obj);
trans.Complete();
}
With this approach, you would also be excused of the need to iterate through the Tags list to save each one. You would be able to just save the Portfolio and everything should be fine.
P.S.: I tested this code with FluentNHibernate v1.4.0.0 and NHibernate v3.3.1.4000.
I have a One to One relation between a TimeRecord and the Location.
This implementation is exactly the same es described in documentation:
https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-mapping
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All();
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord).Unique();
}
}
Now I query my TimeRecords with the following method:
public IList<TimeRecord> GetTimeRecords(string userid)
{
var query = Session.Query<TimeRecord>().Where(tr => tr.User.Id == userid);
return query.ToList();
}
Unfortunalelty my Location object is always null even if there is a coresponding entry in Location table but when I query for the coresponding Location with the desired TimeRecordId it is returned correctly.
See code here (code is inside a loop -> trCurrent is the current object in list received from "GetTimeRecords")
Location location = _locationRepo.getLocationByTimeRecordId(trCurrent.Id);
//trCurrent.Location = location; <- don't want to do it that way
if (trCurrent.Location != null)<- always null
{
... do stuff here
}
Implementation of my LocationRepository method:
public Location getLocationByTimeRecordId(int timeId)
{
var query = Session.Query<Location>()
.Where(tr => tr.TimeRecord.Id == timeId && tr.IsDeleted == false);
List<Location> lstReturn = query.ToList();
if (lstReturn.Count() == 0)
{
return null;
}
else
{
return lstReturn.First();
}
}
Can someone tell me why my Location is not resolved corretly?
Cheers,
Stefan
People claim that
HasOne / one-to-one is usually reserved for a special case. Generally, you'd use a References / many-to-one relationship in most situations (see: I think you mean a many-to-one). If you really do want a one-to-one, then you can use the HasOne method.
If you really do want a one-to-one and use it, you should remember that entities are joined by their ids by default.
If you check generated SQL you'll see something like JOIN Location ON Location.Id = TimeRecord.Id.
In order to get SQL like JOIN Location ON Location.TimeRecordId = TimeRecord.Id you should specify the foreign key via PropertyRef() method. So your mapping could be the folloving:
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All().PropertyRef(it => it.TimeRecord);
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord/*, "TimeRecordId"*/).Unique().Not.Nullable();
}
}
In order to make sure that any location has TimeRecord you can add .Not.Nullable() into your LocationMap class.
I'm currently working on my first NHibernate project.
To test it out I am building an entity graph and that graph I try to persist to the database. When I add children to the parent via the add on the IList the insert is not working in the database because I get null column exceptions on the database (code snippet 1)
When I set a reference on the child to the parent it is working (though this feels not natural for me, see code snippet
Is this normal behavior or am I doing something wrong?
Snippet 1:
var country = new Country();
var countryLocale = new CountryLocale { LangCode = "nl-nl", Name = "Nederland" };
country.CountryLocales.Add(countryLocale);
var city = new City();
var cityLocale = new CityLocale { LangCode = "nl-nl", Name = "Amsterdam" };
city.CityLocales.Add(cityLocale);
country.Cities.Add(city);
Snippet 1 Error:
Cannot insert the value NULL into column 'CountryId', table
'ArtWorld.dbo.City'; column does not allow nulls. INSERT
fails.\r\nThe statement has been terminated.
snippet 2:
var country = new Country();
var countryLocale = new CountryLocale { Country = country,LangCode = "nl-nl",
Name = "Nederland" };
var city = new City{Country = country};
var cityLocale = new CityLocale { City = city, LangCode = "nl-nl",
Name = "Amsterdam" };
session.SaveOrUpdate(country);
City and CityLocale Map:
public class CityMap : ClassMap<City>
{
public CityMap()
{
Table("City");
Id(x => x.Id);
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
HasMany(x => x.CityLocales).KeyColumn("CityId").Cascade.SaveUpdate();
}
}
public class CityLocaleMap : ClassMap<CityLocale>
{
public CityLocaleMap()
{
Table("City_Locale");
Id(x => x.Id);
Map(x => x.LangCode).Not.Nullable();
Map(x => x.Name).Not.Nullable();
References(x => x.City).Column("CityId").Cascade.SaveUpdate();
}
}
this is my Country/Countrylocale map:
public class CountryMap : ClassMap<Country>
{
public CountryMap()
{
Table("Country");
Id(x => x.Id);
HasMany(x => x.Cities).KeyColumn("CountryId").Cascade.SaveUpdate();
HasMany(x => x.CountryLocales).KeyColumn("CountryId").Cascade.SaveUpdate();
}
}
public class CountryLocaleMap :ClassMap<CountryLocale>
{
public CountryLocaleMap()
{
Table("Country_Locale");
Id(x => x.Id);
Map(x => x.LangCode).Not.Nullable();
Map(x => x.Name).Not.Nullable();
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
}
}
NHibernate Mapping changes :
On the Country side you have
HasMany(x => x.CountryLocales).KeyColumn("CountryId").Cascade.SaveUpdate()
On the CountryLocale side you have
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
One of the relations should be marked as inverse using .Inverse().
The .Cascase.SaveUpdate() makes sure that NHibernate manages the life cycle of the child entity CountryLocale when a transient child is added to the parent Country's list of CountryLocales. If you dont want to explicitly handle the life cycle of CountryLocale yourself , I would suggest to mark the Many-To-One side from CountryLocale -> Country as Inverse.
There are two ways you could make your referencing Country in CountryLocale less of a sour eye and more intutive
If Country manages the Locales
Add a method in Country which will manage the Locales which are being added. A client (user of Country and CountryLocale need not explicitly play with the references)
public virtual bool AddCountryLocales(CountryLocale locale)
{
if(!this.CountryLocales.Contains(locale))
{
locale.Country =this;
this.CountryLocales.Add(locale);
return true;
}
return false;
}
CountryLocale is more domain driven
By redefining it such that you cannot have a CountryLocale without a Country
public class CountryLocale
{
public CountryLocale(Country country)
{
this.Country = country;
}
//you need a no-agrument constructor for NHibernate
protected CountryLocale()
{
}
}
I'm trying to map a very basic parent-child relation with Fluent NHibernate.
However, when analyzing the SQL, only the parent-INSERT statement is created.
The situation is a simple class with a list of other classes. No relation back to the parent is needed. The children needs to be inserted/updated when the parent is inserted/updated.
var room = new Room();
room.Name = "Room1";
room.Courses.Add(new Course(){ Name = "Course1"});
room.Courses.Add(new Course(){ Name = "Course2"});
using (var session = sessionFactory.OpenStatelessSession())
{
using (var transaction = session.BeginTransaction())
{
session.Insert(room);
transaction.Commit();
}
}
The mapping looks like this.
public class RoomMapping : ClassMap<Room>
{
public RoomMapping()
{
Table("Rooms");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_rooms", "1000");
Map(x => x.Name);
HasMany(x => x.Courses)
.Cascade.All();
}
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Table("Courses");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_courses", "1000");
Map(x => x.Name);
}
}
I already played with multiple options of the HasMany, however non with any success.
Sorry people. I just found it out.
I'm working in a Stateless session. So no relationships are managed ;)
I'm getting the following error message:
NHibernate.HibernateException: NHibernate.HibernateException: Unable to resolve property: Id.
This error is thrown from the following line of code:
User userFound = session.QueryOver<User>()
.Where(x => x.Id == testObjects.TestUser.Id)
.SingleOrDefault();
My abbreviated mappings are as follows:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("USER_HEADER");
Id(x => x.Id, "USER_ID")
.GeneratedBy.Foreign("UserLocation");
HasOne(x => x.UserLocation)
.PropertyRef(x => x.Id)
.Cascade.All();
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Table("LOC_HEADER");
Id(x => x.Id, "LOC_ID");
HasOne(x => x.User)
.PropertyRef(x => x.Id);
}
}
I was able to query a User object before I added this relationship to Location so I know it has something to do with it but I'm not sure what exactly. I can successfully create a User object that is tied to a Location but cannot query it. Using ISession.Get produces the same error as the above QueryOver statement.
Below is the overall unit test I am running that is failing:
public void Can_Create_User()
{
using (NHibernate.ISession session = SessionFactory.GetCurrentSession())
{
using (NHibernate.ITransaction tran = session.BeginTransaction())
{
session.Save(testObjects.TestValidationDetail);
session.Save(testObjects.TestUser);
tran.Commit();
}
}
using (NHibernate.ISession session = SessionFactory.GetCurrentSession())
{
User userFound = session.QueryOver<User>().Where(x => x.Id == testObjects.TestUser.Id).SingleOrDefault();
Assert.IsNotNull(userFound);
Assert.AreEqual(userFound.Id, userFound.UserLocation.Id);
}
}
It turns out this was caused by me incorrectly using PropertyRef. In my instance I did not need to use this. The error was being generated because there was no property named Id but there was an ID named Id. I corrected my issues by changing my mappings to:
HasOne(x => x.UserLocation)
.PropertyRef(x => x.Id)
.Cascade.All();
to
HasOne(x => x.UserLocation)
.Cascade.All();
and
HasOne(x => x.User)
.PropertyRef(x => x.Id);
to
HasOne(x => x.User)
PropertyRef maps to property-ref is a legacy feature, it is meant to allow you to create many-to-one associations when the association is not done on the primary key of the association.
I am guessing you want to specify on what property the join is to be made and that is why you used PropertyRef.. if you are using Nhibernates default convention in the mapping for the Id of UserLocation you dont need to explicitly specify the property.. if you are explicitly giving the column name then you need to do the same here, but in that case you need to specify the exact same column name.
Hope that helps..