Nhibernate and the case of Auto Updating Entities - c#

Best title I could come up with, A little more involved however.
// Hit the database once and get all the categories;
IQueryable<Category> qryCategoryList = _repository.Select<Category>();
// get initial parents
var parentCategories = qryCategoryList.Where(x => x.ParentCategoryId == null);
foreach (Category parentCategory in parentCategories)
{
HttpContext.Current.Response.Write(parentCategory.CategoryName = "This should not happen");
BuildCategoryList(qryCategoryList, parentCategory.CategoryId);
}
This line
HttpContext.Current.Response.Write(parentCategory.CategoryName = "This should not happen");
performs this
UPDATE Categories
SET ParentCategoryId = NULL /* #p0_0 */,
CategoryName = '->' /* #p1_0 */,
CategoryDescription = 'The Fruit Category' /* #p2_0 */,
Active = 1 /* #p3_0 */,
DateCreated = '2012-01-20T12:03:41.00' /* #p4_0 */,
LastUpdated = '2012-01-20T12:03:41.00' /* #p5_0 */
WHERE CategoryId = 'aa8ca9ba-663c-45c8-950b-159a28e6635d' /* #p6_0 */
I am not calling save from my repository not wanting to do an update. How is this possible?
EDIT: Here is the mapping
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
LazyLoad();
Id(x => x.CategoryId).GeneratedBy.GuidComb().Column("CategoryId");
Map(x => x.ParentCategoryId).Column("ParentCategoryId");
Map(x => x.CategoryName).Column("CategoryName").Not.Nullable().Length(50);
Map(x => x.CategoryDescription).Column("CategoryDescription").Not.Nullable();
Map(x => x.Active).Column("Active").Not.Nullable();
Map(x => x.DateCreated).Column("DateCreated").Not.Nullable();
Map(x => x.LastUpdated).Column("LastUpdated").Not.Nullable();
HasMany(x => x.PostingsCategories).KeyColumn("CategoryId");
}
}

This usually happens when something in the mapping or object declaration doesn't quite jive with what's in the database. For example, you might have a UNIQUEIDENTIFIER in the database that's nullable but mapped that to a non-nullable Guid on an object. NHibernate selects, sees a Guid.Empty instead of a null, and says "Hey! The object changed! Whelp, reckon I should update it..."
That's just one case of how it can happen. If you post your mapping, we might be able to help you debug it a bit further.
--
Actually, I should have read it a bit further. If this is within the scope of a transaction, NHibernate will auto update any changed entities without needing an explicit call to SaveOrUpdate(). It's called autoflush and it's on by default. You'll need to set the FlushMode to Never if you want to explicitly call transaction.Commit() or session.Flush().

Related

Nhibernate not inserting parentid into child

I have a weird problem at hand. First have a look at my table schema.
A(ID)
B(ID,AID)
C(ID,AID)
D(ID,CID)
The map files are as below:
MAP A
{
HasMany(x => x.B).Cascade.AllDeleteOrphan().Inverse().KeyColumn("AID");
HasMany(x => x.C).Cascade.AllDeleteOrphan().Inverse().KeyColumn("AID");
}
MAP B
{
References(x => x.A).Column("AID");
}
MAP C
{
References(x => x.A).Column("AID");
HasMany(x => x.D).Cascade.AllDeleteOrphan().Inverse().KeyColumn("BID");
}
MAP D
{
References(x => x.C).Column("CID");
}
While doing SaveORUpdate/Merge on A it doesn't insert AID into B and C. But it does insert CID into D. Any suggestions.
If this happens, you for sure missed to assign both sides of relation. If this would be in place:
var parent = ...;
var child = ...;
parent.Children.Add(child);
child.Parent = parent;
All will work. Because the most supsected here is that your code is like:
var parent = ...;
var child = ...;
parent.Children.Add(child);
// child.Parent = parent; // this is missing
and that won't insert children. Why?
Because we used the .Inverse() mapping. This is a very powerful but fragile setting. It allows NHibernate to do some important optimizations, but that requires - PARENT must be set in child.
Check this nice article
Inverse = “true” example and explanation by mykong

NHibernate - How to QueryOver in joined table with restrictions

I am stuck with a SQL query (using NHibernate 4).
I have 2 tables (Client and Technology) with many-to-many relationship so I created a junction table called ClientTechnology.
I am trying to retrieve all the Technologies available (that are non-custom) PLUS all the Technologies available (that are custom) and belong to a given Client.
In SQL this is the statement:
declare #clientId int = 1
select * from
[dbo].[Technology] t
where t.IsCustom = 0
union
select t.* from
[dbo].[Technology] t
join [dbo].[ClientTechnology] ct
on ct.TechnologyId = t.Id
where t.IsCustom = 1 and ct.ClientId = #clientId
My Fluent Mapping for Client table is:
public ClientMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
}
For Technology table is:
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
Map(x => x.IsCustom).Not.Nullable();
HasMany(x => x.ClientTechnologies)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Table("ClientTechnology")
.KeyColumn("TechnologyId");
}
and finally the junction table ClientTechnology:
public ClientTechnologyMap()
{
Id(x => x.Id);
Map(x => x.Alias).Not.Nullable();
Map(x => x.IsDeleted).Not.Nullable();
References<Client>(x => x.Client, "ClientId");
References<Technology>(x => x.Technology, "TechnologyId");
}
I a open to different options to achieve this.
Assuming I have available a Client object (the ClientId)
I could retrieve first a list of Technologies that match the requirement IsCustom = false
and then retrieve a list of Technologies that match the requirement
IsCustom = true AND "the provided client is the owner of this custom technology"
Within a method public IEnumerable<Technology> GetTechnologies(Client client) that must return the enumerable of Technology (given a Client instance)
I have tried the following to retrieve globalTechnologies:
var globalTechnologies = _session.QueryOver<Technology>()
.WhereNot(x => x.IsDeleted)
.WhereNot(x => x.IsCustom)
.List();
And the following for customTechnologies whose owner is the client:
Technology technology = null;
ClientTechnology clientTechnology = null;
var customTechnologies = _session.QueryOver<Technology>(() => technology)
.JoinAlias(() => technology.ClientTechnologies, () => clientTechnology)
.WhereNot(x => x.IsDeleted)
.Where(x => x.IsCustom)
.Where(clientTechnology.Client == client) //this doesn't compile
.List();
but I don't know how to access the junction table (joined) in order to apply the restriction.
Any help would be much appreciated. Thank you.
In your case, the only problem is, that you do not provide Expression inside of the .Where(), so this should do the job:
// instead of this
// .Where(clientTechnology.Client == client) //this doesn't compile
// use this
.Where(() => clientTechnology.Client == client)
But I would go even farther. We should be able to creat subquery, which
will return only such Techonology.Id which are belonging to client.
we then can use also OR and have one query which would either select these who are:
NOT IsCustom or
Do belong to Client
How to create subquery you can see here:
How to do a QueryOver in Nhibernate on child collection
And example with OR
Use OR Clause in queryover in NHibernate
NHibernate QueryOver with WhereRestriction as OR

Fluent NHibernate bulk data insert

I need to transfer some data to other db, but when I start this mssql server freeze.
ds2 row count about 250,000. What am I doing wrong?
every time I try same funcion first looks working and after give error on random row. maybe 5, 95 or 280. until error records looks valid.
string inlineSql = "Select * from album";
DataSet ds2 = SqlHelper.ExecuteDataset(ConnectionString, CommandType.Text, inlineSql);
for (int i = 0; i < ds2.Tables[0].Rows.Count; i++)
{
Entity.Album.Album album = new Entity.Album.Album();
album.AlbumName = ds2.Tables[0].Rows[i]["AlbumName"].ToString();
album.WebSite = (Entity.Artist.WebSite)Convert.ToInt16(ds2.Tables[0].Rows[i]["WebSite"]);
album.Year = ds2.Tables[0].Rows[i]["Year"].ToString();
Entity.Artist.Artist artist = Entity.Artist.Artist.READ.ById(Convert.ToInt32(ds2.Tables[0].Rows[i]["ArtistId"]));
if (artist != null)
{
artist.AddAlbum(album);
album.Save();
}
}
AddAlbum Method
public virtual void AddAlbum(Album.Album album)
{
album.Artist = this;
AlbumList.Add(album);
}
Save Method
using (var session = NHibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(x);
transaction.Commit();
}
}
Error on session.SaveOrUpdate(x);
Error :
An exception of type 'NHibernate.Exceptions.GenericADOException'
occurred in NHibernate.dll but was not handled in user code
Additional information: could not insert: [Entity.Album.Album][SQL:
INSERT INTO [Album]
(AlbumName, WebSite, Year, ArtistId) VALUES (?, ?, ?, ?); select
SCOPE_IDENTITY()]
AlbumMap
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
Id(x => x.AlbumId);
Map(x => x.AlbumName);
Map(x => x.WebSite).CustomType<short>();
Map(x => x.Year);
HasMany(x => x.LyricList).Table("Lyric").KeyColumn("AlbumId").Cascade.All().Not.LazyLoad();
References(x => x.Artist).Column("ArtistId").ForeignKey("ArtistId").Fetch.Join();
}
}
ArtistMap
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
Id(x => x.ArtistId);
Map(x => x.ArtistName);
Map(x => x.ArtistURL);
Map(x => x.ImgName);
Map(x => x.ImgURL);
Map(x => x.Alfabet);
Map(x => x.SeoLink);
Map(x => x.WebSite).CustomType<short>();
HasMany(x => x.AlbumList).Table("Album").KeyColumn("ArtistId").Cascade.All().Not.LazyLoad();
}
}
More Exception
The INSERT statement conflicted with the FOREIGN KEY constraint
"FKA0CE20AA48AC4CAD". The conflict occurred in database "LYRICSWEB",
table "dbo.Artist", column 'ArtistId'. The statement has been
terminated.
There are some indications, which should lead to the answer. We can see that the error is:
Additional information: could not insert: [Entity.Album.Album][SQL: INSERT INTO [Album]
(AlbumName, WebSite, Year, ArtistId) VALUES (?, ?, ?, ?); select SCOPE_IDENTITY()]
while we are loading artist correct way...
Entity.Artist.Artist artist = Entity.Artist.Artist.READ
.ById(Convert.ToInt32(ds2.Tables[0].Rows[i]["ArtistId"]));
The issue would be in incorrect collection mapping. The reference from Albumt to Artist seem to be correct:
public AlbumMap()
{
..
References(x => x.Artist)
.Column("ArtistId")
.ForeignKey("ArtistId")
.Fetch.Join();
}
But the other end of that relation is mapped like this:
// wrong
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("AlbumId") // wrong
.Cascade.All().Not.LazyLoad();
}
Both above relations are the same! They are only mapped from different ends (from collection item perspective and from collection owner perespective).
That means that the mapping column must be the same, i.e. not "AlbumId" but "ArtistId"
// correct
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("ArtistId") // this is the relation
.Cascade.All().Not.LazyLoad();
}
Because NHibernate loads some artist, and its collection of AlubmList.. it could lead to some unexpected insert statements... fix of that mapping should help...
Second thing to mention: Inverse()
We are working with Album instances... and we are assigning their relation to collection holder - Artist. This is good and suggested way how to do that. Good.
But we can profit from NHibernate feature called Inverse() which will reduce few SQL statements... and make all the insert more simple... so extend your Artist mapping like this:
// correct
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("ArtistId") // this is the relation
.Cascade.All()
.Not.LazyLoad()
.Inverse(); // ESSENTIAL improvement of generated SQL
}
Check this for more details

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.

NHibernate fluent update problem (one to many relation)

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();

Categories

Resources