I have a Users table where the "ID" field is a GUID field.
Using ASPNET I am creating a user account and getting the GUID back. I am trying to create associated records in my tables with that GUID as the main ID.
The problem I am running into is that when I manually set Users.ID NHibernate tries to do an Update, not an Insert. I see this with the NHibernate Profiler before it bombs out with "Unexpected row count: 0; Expected: 1".
My UsersMap for the table Users looks like:
public class UsersMap : ClassMap<Users>
{
public UsersMap()
{
Id(x => x.ID, "ID"); //GUID
Map(x => x.Name, "Name"); //string
Map(x => x.PhoneNumber, "PhoneNumber"); //string
Map(x => x.FaxNumber, "FaxNumber"); //string
Map(x => x.EmailAddress, "EmailAddress"); //string
HasMany<UsersAddressBook>(x => x.usersAddressBook).KeyColumn("ID");
}
}
Any ideas? Thanks in advance.
You need to specify that your id will be assigned.
Id(x => x.ID)
.GeneratedBy.Assigned();
This will allow you to specify the value, without NHibernate trying to perform an update.
ISession.Save has an overload that allows you to specify identifier. Try using it, and don't set your id property manually.
As added value to James's answer I used:
Id(x => x.ID)
.GeneratedBy.Assigned()
.UnsavedValue(default_value);
Note that default_value is default value for your Id type
as in me is 0L because I use long for my Id
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'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..
I am using fluent nhibernate(v1.2) and nhibernate(v3.1) and I am having a weird Problem.
I have this
public class GradeMap : ClassMap<Grade>
{
public GradeMap()
{
Id(x => x.GradeId);
Map(x => x.TaskName).NvarcharWithMaxSize().Not.Nullable();
Map(x => x.Weight).Not.Nullable().Precision(5).Scale(2);
Map(x => x.OutOf).Not.Nullable().Precision(5).Scale(2);
Map(x => x.Mark).Not.Nullable().Precision(5).Scale(2);
Map(x => x.CourseBackgroundColor).Not.Nullable();
Map(x => x.CoursePrefix).Not.Nullable();
References(x => x.Student).Not.Nullable();
References(x => x.Course);
}
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Id(x => x.Id).Column("CourseId");
Map(x => x.Prefix).NvarcharWithMaxSize().Not.Nullable();
HasMany(x => x.Tasks).Cascade.Delete().Inverse();
HasMany(x => x.CoursePermissions).Cascade.All().Inverse();
HasMany(x => x.CourseSharing).Cascade.All().Inverse();
HasMany(x => x.Grades).Cascade.None().Inverse();
}
}
I then do something like this
return session.Query<Grade>().ToList();
If I would try to grab a propety such as Grade.Course.Id it would crash and I would get.
Grade.Course = {Castle.Proxies.CourseProxy}
Grade.Course.Id = '((new System.Collections.Generic.Mscorlib_CollectionDebugView<OnlGrade>(grades)).Items[0].Course).Id' threw an exception of type 'NHibernate.ObjectNotFoundException'
Grade.Course.Prefix = above error except .Prefix instead of .Id
I would have thought Course Object would be empty or null. Not that it would have some Proxy with all properties throwing exceptions.
Edit
I found this posting but I don't have this attribute anymore so maybe they got rid of it or moved it.
Anyone know?
Is it possible to avoid NHibernate.ObjectNotFoundException when there is a foreign key but the referenced row does not exist?
I think the error here is because you're accessing the Id property of an object which does not exist.
Do you get the same error if you do TableA.TableB ?
What about NotFound.Ignore()?
public class GradeMap : ClassMap<Grade>
{
public GradeMap()
{
Id(x => x.GradeId);
Map(x => x.TaskName).NvarcharWithMaxSize().Not.Nullable();
Map(x => x.Weight).Not.Nullable().Precision(5).Scale(2);
Map(x => x.OutOf).Not.Nullable().Precision(5).Scale(2);
Map(x => x.Mark).Not.Nullable().Precision(5).Scale(2);
Map(x => x.CourseBackgroundColor).Not.Nullable();
Map(x => x.CoursePrefix).Not.Nullable();
References(x => x.Student).Not.Nullable();
References(x => x.Course)
.NotFound.Ignore();
}
}
EDIT:
Let's say database schema looks like this:
Grade(GradeId, TaskName, Course_id)
Course(CourseId, Prefix)
If there is no foreign key constraint on Course_id column, then row in Course table with CourseId that corresponds to Course_id column in Grade table can be deleted. E.g.:
Course table:
CourseId Prefix
1 Course1Prefix
Grade table:
GradeId TaskName Course_id
1 Grade1Task 1
without foreign key constraint you can issue this dml query:
delete from Course where CourseId = 1
And it could be reason of problem explained by sJhonny.
When "NotFound.Ignore()" is used in the mapping then NHibernate tries to load Courses that belong to all Grades that were loaded by the query:
session.Query<Grade>().ToList();
No proxies are generated and when course referenced by grade is not found in database then the Course property is simply null.
If you can modify the database schema it would be helpful to create PK constraint on Course_id column.
having a little trouble with a mapping for the following table setup currently:
Shop
[1] [1]
/ \
[n] [n]
Category-[m]---[n]-Article
The behaviour should be the following :
1 - when deleting a shop, all Articles and Categories Should be deleted
2 - when deleting a Category, related Articles should be unassigned but not deleted
3 - when deleting an Article, related Categories should be unassigned but not deleted
Here's the current mapping:
public class ShopMap: ClassMap<Shop>
{
public ShopMap()
{
this.Table("shop");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
HasMany(x => x.Categories).Cascade.AllDeleteOrphan;
HasMany(x => x.Articles).Cascade.AllDeleteOrphan;
}
}
public class CategoryMap: ClassMap<Category>
{
public CategoryMap()
{
this.Table("category");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Articles).Cascade.AllDeleteOrphan()
.Table("article_category")
.ChildKeyColumn("article_id")
.ParentKeyColumn("category_id")
.Inverse();
}
}
public class ArticleMap: ClassMap<Article>
{
public ArticleMap()
{
this.Table("article");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Categories).Cascade.All()
.Table("article_category")
.ParentKeyColumn("article_id")
.ChildKeyColumn("category_id");
}
}
When deleting a Category (Session.Delete()), NH tries to delete the related Articles as well. Changing the Cascade-Mode to SaveUpdate will fix this, but will leave the entries in the link table *article_category*. Summing up : Cascade.SaveUpdate is too lazy, Cascade.All is too eager.
I tried everything that came to my mind in the mappings, but couldn't find a correct way to map this (rather simple schema).
Any ideas on how to (fluently) map this are greatly appreciated!
Thanks in advance
Sebi
The entries are left in the link table because Category.Articles is defined as the inverse side of the relationship. You need to remove the Category from Article.Categories before deleting it in order for the link record to be removed.
I have a set of fluent object mappings that looks like this:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Map(x => x.Id);
Map(x => x.Status);
}
}
public class SpecialUserMap : SubClassMap<SpecialUser>
{
public SpecialUserMap()
{
Map(x => x.Property);
}
}
public class DirectoryMap : ClassMap<Directory>
{
public DirectoryMap
{
Map(x => x.Id);
HasMany(x => x.SpecialUsers).Where("Status = 0");
}
}
User is a join table, which SpecialUser joins against to get things like status. However, when I try to reference a SpecialUser in Directory's SpecialUsers collection, I get an error of "Undefined column 'Status'", as in the generated SQL, NHibernate tries to grab the Status column from the SpecialUser table, and not the User table.
Is there a way to explicitly tell NHibernate which table to get the Status column in the DirectoryMapping?
The Status property of a User / SpecialUser needs to map onto a single column in the database. You can't have it coming sometimes from User and sometimes from SpecialUser.
As a workaround, you could add a SpecialUserStatus property to SpecialUser, and then you could query on that easily.
That mappings looks right for table-per-subclass mapping, assuming that SpecialUser extends User. My guess is that it's a bug.