NHibernate fluent update problem (one to many relation) - c#

I have a object OtherFaclilityEntity which contains a IList of OtherFcs objects, and the mapping is as follows:
public OtherFacilityMap()
{
Schema("SOME");
Table("OTHER_FACILITY");
Id(x => x.Id, "OTHER_FACILITY_S").GeneratedBy.TriggerIdentity();
Map(x => x.RowCreator, "ROW_CREATOR");
Map(x => x.RowCreateDate, "ROW_CREATE_DATE");
Map(x => x.Description, "DESCRIPTION");
Map(x => x.ExistenceKdNm, "R_EXISTENCE_KD_NM");
References(x => x.FacilityClassItem, "FACILITY_CLASS_S").LazyLoad(Laziness.False).Fetch.Join().Not.Insert().Not.Update();
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Not.LazyLoad();
}
When i try to do a SaveOrUpdate on the OtherFacility entity, it also updates all the entities in the FacilityCmList, which is fine, but in the last sql that is run tries to remove all relations between the parent and the child objects:
NHibernate.SQL: 2011-07-19 10:29:33,111 [361] DEBUG NHibernate.SQL [(null)] - UPDATE SOME.FACILITY_CMS SET WHOLE_S = null WHERE WHOLE_S = :p0;:p0 = '26021842'
I assume it has something to do with my mapping, any ideas?

After reading NHibernate sets Foreign Key in secondary update rather than on initial insert violating Not-Null Constrain on key column i found that i needed to add Inverse to the HasMany relation.
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Inverse().Not.LazyLoad();

Related

session.Save forces insert on id 0 relation objects - Fluent NHibernate

So yeah, I'm trying to insert a new entity on my database, which has some foreign keys pointing to auxiliar tables, which in this case shouldn't be updated.
It has 8 total foreign keys, which are all working but for one which has to be id = 0;
using (var session = FluentNHibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(p);
transaction.Commit();
return p;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
I guess there's no need to show the structure of the entity itself but for the map of the object:
References(x => x.Jerarquia).Column("TCODJERARQUIALIN").Cascade.All().Not.Nullable();
References(x => x.Conformidad).Column("TCODCONFORMIDADL").Cascade.All().Not.Nullable();
References(x => x.Tramitacion).Column("TCODESTADOTRAMIT").Cascade.All().Not.Nullable();
References(x => x.SistemaReferencia).Column("TCODSISTEMAREFER").Cascade.All().Not.Nullable();
References(x => x.Vigencia).Column("TCODVIGENCIALINE").Cascade.All().Not.Nullable();
References(x => x.CategoriaGeometrica).Column("TCODCATGEOMETRIC").Cascade.All().Not.Nullable();
References(x => x.Territorio1).Column("TCODIDTERRITORI1").Cascade.All().Not.Nullable();
References(x => x.Territorio2).Column("TCODIDTERRITORI2").Cascade.All().Not.Nullable();
Structure of the rest of the mappings comes to be like this, no matter which the related item is:
Id(x => x.TCODJERARQUIA).Column("TCODJERARQUIA").Not.Nullable();
Map(x => x.TTEXDESCJERAR_ES).Column("TTEXDESCJERAR_ES").Not.Nullable();
Map(x => x.TTEXDESCJERAR_EU).Column("TTEXDESCJERAR_EU").Not.Nullable();
Table("TARLLJERAR");
To sum it up, if one of the foreign key items is id = 0, it wont set the foreign key but will insert a new item on aux table
How can i solve this issue?
Thanks in advance!

Adding a new item to a collection using Fluent NHibernate doesn't INSERT the new row in the DB

I have a model called ShoppingLists and each ShoppingLists has a collection of ShoppingListItems called Items. What I would like to be able to do is add a new item to my list as such:
dbList.Items.Add(new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw));
I would expect the ShoppingListItems to automatically be linked to its parent ShoppingLists class, and for NHibernate to create the appropriate SQL INSERT statement when the transaction is committed. However, instead I get the exception:
NHibernate.StaleStateException was unhandled
HResult=-2146232832
Message=Unexpected row count: 0; expected: 1
Source=NHibernate
What I have to do instead is create the object, save it, then add it to the collection:
var newItem = new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw);
newItem.ShoppingList = dbList;
session.Save(newItem);
dbList.Items.Add(newItem);
I'd like to eliminate the need to do this. My mappings for ShoppingLists is as such:
Id(x => x.ShoppingListId);
Map(x => x.UserId).Not.Nullable();
Map(x => x.Title).Not.Nullable();
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Cascade.Delete(); // If Shopping List is deleted, delete all the Items that reference this list
And my mappings for ShoppingListItems is:
Id(x => x.ItemId);
Map(x => x.Raw).Length(50);
Map(x => x.Qty);
Map(x => x.Unit);
Map(x => x.UserId).Not.Nullable();
Map(x => x.CrossedOut).Not.Nullable();
References(x => x.Recipe).Column("RecipeId");
References(x => x.Ingredient).Column("IngredientId");
References(x => x.ShoppingList).Column("ShoppingListId");
I've tried playing around with Cascade.All() on each, to no avoid. Any ideas?
The issue here is related to the ShoppingListItem Guid ID handling (see the code snippets of the item creation in the question).
The "working" scenario, when the ShoppingListItem is created and persisted, does the following step - generates the Guid ID and passes it into the constructor
var newItem = new new ShoppingListItems(Guid.NewGuid()...)
Next, NHiberante is asked to do session.Save(newItem), so the INSERT statement is issued...
But what will happen in the second case, when the session.Save(newItem) is not called, only Add() the item into the collection of the parent ShoppingLists?
As mentioned here: 5.1.4.7. Assigned Identifiers (an extract)
...entities that use assigned generator cannot be saved via the ISession's SaveOrUpdate() method. Instead you have to explicitly specify to NHibernate if the object should be saved or updated...
What is NHibernate doing in this case (when the session.Flush() is called) is guessing: should be INSERT or UPDATE issued for each of the collection items?
When it comes to the newItem, it compares its Guid ID with the UnsavedValue(), which is in this case Guid.Empty.
This comparison will result in a decision: Issue UPDATE statement, the Guid ID does not represent new object.
The UPDATE is executed. DB responds: Update done, no errors, but also no rows updated*. NHibernate throws:
Unexpected row count: 0; expected: 1
Solution
Leave the GUID generation on the NHibernate. E.g.:
Id(x => x.ItemId)
.GeneratedBy.GuidComb()
.UnsavedValue(Guid.Empty);
And also, rather explicitly set the newItem reference to its parent, an optimize the cascade with Inverse() setting
// both assignments
newItem.ShoppingList = dbList;
dbList.Items.Add(newItem);
The list mapped with inverse:
// improved write operations handling
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Inverse()
...
please recheck the mappings for ShoppingListItems
You have to do a cascade all and inverse should be true
you have a bidirectional association between shoppinglist and item.
You may specify a bidirectional many-to-many association simply by mapping two many-to-many associations to the same database table and declaring one end as inverse.

Fluent NHibernate PersistentSpecification Test fails

I currently have these Maps
public class CountryMap : ClassMap<Country>
{
public CountryMap()
{
Table("tblCountry");
Id(x => x.Id, "intCountryId");
Map(x => x.Name, "strCountryName");
HasMany(x => x.FlagImages)
.Table("tblImage")
.KeyColumn("intRelId")
.Where("intObjId=29")
.Not.Cascade.All();
}
}
public class ImageMap : ClassMap<Image>
{
public ImageMap()
{
Table("tblImage");
Id(x => x.Id, "intImgId");
Map(x => x.ObjId, "intObjId");
Map(x => x.RelId, "intRelId");
Map(x => x.ImageName, "strImage");
}
}
and the following persistent specification test:
PersistenceSpecification<Image>(session)()
.CheckProperty(r => r.Id, 1)
.CheckProperty(r => r.ImageName, "ss")
.CheckProperty(r => r.ObjId, (int)ObjectType.Country)
.CheckProperty(r => r.RelId, 102)
.VerifyTheMappings();
Somewhat the test of Image failed.
The failure message is
Assert.AreEqual failed. Expected:<(null)>. Actual:<NHibernate.Exceptions.GenericADOException: could not insert: [HansaCrew.Models.Images.Image][SQL: INSERT INTO tblImage (intObjId, intRelId, strImage) VALUES (?, ?, ?); select last_insert_rowid()] ---> System.Data.SQLite.SQLiteException: constraint failed
foreign key constraint failed
I've checked it for 2 days and haven't find the reason. Any help?
Okay, I got it.
The problem of this piece of code lies here
HasMany(x => x.FlagImages)
.Table("tblImage")
.KeyColumn("intRelId")
.Where("intObjId=29")
.Not.Cascade.All();
NHibernate automatically creates a foreign key constraint with tblImage, which has a lot of intRelIds which is not in the column of intCountryId in the table tblCountry. That's why the test fails saying foreign key constraint failure.
The way to solve this problem is to create a view in the database and then map the view into the Image model

Possible to use CustomSqlType with CompositeId?

Working with legacy tables, need to create a CompositeId based on two char(3) fields. Don't see any overloads that make this possible with Fluent.
The mapping I'm attempting looks like this:
CompositeId()
.KeyProperty(x => x.LegacyEntity1Id, "LegacyEntity1Id")
.KeyProperty(x => x.LegacyEntity2Id, "LegacyEntity2Id");
Map(x => x.LegacyEntity1Id).CustomSqlType("char(3)");
Map(x => x.LegacyEntity2Id).CustomSqlType("char(3)");
I've also tried:
CompositeId()
.KeyReference(x => x.LegacyEntity1, "LegacyEntity1Id")
.KeyReference(x => x.LegacyEntity2, "LegacyEntity2Id");
Map(x => x.LegacyEntity1Id).CustomSqlType("char(3)");
Map(x => x.LegacyEntity2Id).CustomSqlType("char(3)");
Both result in the same outcome - the table gets generated with a proper composite id, but both columns are the default nvarchar(255). As a result, the foreign keys fail to generate and I get an exception, since the parent tables are char(3).
Is this not possible to map via Fluent?
If not, is there any real difference in mapping it like this*:
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.LegacityEntity1Id).CustomSqlType("char(3)");
Map(x => x.LegacityEntity2Id).CustomSqlType("char(3)");
References(x => x.LegacityEntity1).Column("LegacityEntity1Id").UniqueKey("1").Not.Nullable();
References(x => x.LegacityEntity2).Column("LegacityEntity2Id").UniqueKey("1").Not.Nullable();
* I do have the ability to modify the tables slightly (enough to add an identity), since the legacy tables are being ETLed into a local SQL instance.
Or is there another alternative approach? Can't use a HasManyToMany in this case, for what it's worth (will have a payload).
KeyReference will search the map of the referenced entity and uses the sqltype there. go to the referenced entities and specify Id(x => x.Id).Length(3).
This is how to do it in 2017:
CompositeId()
.KeyProperty(
x => x.LegacyEntity1Id,
k => k.ColumnName("LegacyEntity1Id").Type("AnsiString").Length(3))
.KeyProperty(
x => x.LegacyEntity2Id,
k => k.ColumnName("LegacyEntity2Id").Type("AnsiString").Length(3))

NHibernate - how to delete an item from many-to-many relationship?

I've following mapping for two tables having a Many-to-Many relationship between them. How do I delete an entry from the mapping table, which is 'ProjectUser' in my case?
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.ProjectName);
Map(x => x.Description);
References<User>(x => x.Owner);
HasManyToMany(x => x.Users)
.Cascade.SaveUpdate()
.Table("ProjectUser")
.Not.LazyLoad();
}
public UserMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.UserName);
HasManyToMany(x => x.Projects)
.Cascade.SaveUpdate()
.Inverse()
.Table("ProjectUser")
.Not.LazyLoad();
}
EDIT: changed Cascade to SaveUpdate as suggested in answers. Here is the code I use to commit data to SQLite database.
using (var trans = session.BeginTransaction())
{
var existingUsers = project.Users.ToList();
foreach (var item in existingUsers)
{
if (selectedUsers.Count(x => x.Id == item.Id) == 0)
project.Users.RemoveAt(project.Users.IndexOf(item));
}
session.SaveOrUpdate(project); // This fixed the issue
session.Flush();
foreach (var item in selectedUsers)
{
if (project.Users.Count(x => x.Id == item.Id) == 0)
{
project.AddUser(session.Get<User>(item.Id));
}
}
session.SaveOrUpdate(project);
session.Flush();
trans.Commit();
}
// Add user code in Project class
public virtual void AddUser(User userToAdd)
{
if (this.Users == null)
this.Users = new List<User>();
userToAdd.Projects.Add(this);
this.Users.Add(userToAdd);
}
Whenever I try to save/update I'm getting the following error:
a different object with the same identifier value was already associated with the session: 10, of entity: Models.Project
EDIT2: Should use session.SaveOrUpdate(project) and session.Flush() to avoid error stated above.
If you only want to remove the ProjectUser entry and not actually delete the entity on the other side you need to change from Cascade.All() to Cascade.SaveUpdate().
Currently if you removed a user from a project and saved the project it would delete the ProjectUser entry and the User object.
My guess would be to remove the appropriate User entity from the Project.Users collection and save that project. The cascade would then remove the entry in "ProjectUser".
If you want to remove link between a Project and an User (it means removing record from the join table ProjectUser) you should act from noniverse side. In your case it means you should remove user entity from the Project's Users collection:
project.Users.Remove(user);
In my opinion cascade in many-to-many assoc. should be set to SaveUpdate. You don't want to delete the project when the user is deleted.

Categories

Resources