NHibernate: Saving stops when hitting a unique constraint - c#

I'd like to save a list of stores into the database using NHibernate. However, there is a unique constraint on each store, causing the save to stop when a store triggers the constraint.
try
{
_storeRepository.Save(stores);
}
catch (Exception e)
{
}
finally
{
session.Commit();
}
In accordance with to the code above, all stores before the unique constraint is hit are saved, but not the ones after. How can I fix this without having to loop the whole list and check for duplicates?

The rules are:
Never catch sql (or NH) Exceptions in the transaction.
in case of an error, always rollback to whole stuff
in case of an error, always destroy the session.
The NH session cache gets out of synch and you can't use it anymore. So "finally commit" is a very bad thing.
You need to make sure that you don't violate any database constraint in your business logic.

You could always use the SaveOrUpdate so NHibernate updates the entity if one with the given entity is found in the database.

Related

NHibernate bulk restore of backup seems impossible

I am trying to perform a restore of data using NHibernate but I am getting all sorts of errors, from foreign key violations to primary key violations and everything in between.
To give some background, I created a "Base" class from which every class in my application inherits (please don't comment on this, this is what i need/want).
So to perform a backup, i simply call session.QueryOver<BaseClass>().List<BaseClass>() and I get all the data, serialize it to javascript, zip it and save it. That's how I create backups.
Now the restore....
I deserialize the backup with ease, get the right types and everything.
I've tried using session.save(item, item.Id), to put the items back with the same ID's as in the original database, but NHibernate doesn't seem to like this, especially when I have foreign keys between tables (or classes).
Browsing the internet, it seems my answers would lie with stateless sessions. I tried these, but I still get all sorts of errors.
One thing i tried was to wrap all the inserts in a try-catch, and retry until i no longer get errors. This sort of worked, but when i call session.Commit I get an error message with a lot of 'Violation of PRIMARY KEY constraint' messages. I have wrapped all of my inserts into 1 transaction (while writing this I am thinking to try take out the transaction).... Without the transaction it seems to have saved some of the data. I think I should have a transaction, as I want to be able to guarantee all or none of the data was restored, to make restores more reliable.
Using try-catch doesn't seem reliable, also it means I have to guess howmany times to retry the insert action on failed items.
One important note I want to add is that when my code is running, I know nothing about the classes or types other than they are of type BaseClass, with an Id field. So one class that is giving an error is a Menu class. It has a property which is List<Menu>-childMenus and another property of type Menu-parentMenu. These 2 properties are mapped using fluent nhibernate to be HasMany and References, this is how I believe these should be mapped. This is the sort of class that is causing problems for me, because NHibernate has created foreign keys. This is good in my opinion, except that now I can't do a restore easily.
If I don't get a suitable answer or figure this out soon, my solution will be to try and order the items to be restored in such a way that any item which "looks" like it might have a parent object (property of type BaseClass) with a foreign key, i will sort those items into a list and insert them last, and hopefully avoid foreign key constraint violations.
But I am hoping there are other alternatives.
Also, when I do the restore, the Id generator is set to assigned, so I don't think my problem has to do with unknown or invalid id's. In the original data my id's are GUID's. (I may change this to hilo integers later on, but one problem at a time).
Any help will be much appreciated.
Thanks in advance...
Unless I figure out a better alternative, my solution will involve brute forcing the data into the database, using code similar to the following:
var existingCount = 0l;
var lastCount = -1l;
while (existingCount < items.Count)
{
using (var session = factory.OpenSession())
{
existingCount = session.CreateCriteria<BaseClass>()
.SetProjection(Projections.RowCountInt64())
.List<long>()
.Sum();
session.Flush();
}
if (existingCount == items.Count)
{
break; // success
}
if (lastCount == existingCount)
{
throw new Exception("Error restoring backup, no change after retrying inserting new items.");
}
lastCount = existingCount;
try
{
using (var session = factory.OpenSession())
{
var existingItems = session.QueryOver<BaseClass>().List<BaseClass>().ToList();
SaveItemsToDb(existingItems, items, session); // checks if item already exists, if not, tries to save it. Also has some try-catch processing
session.Flush();
}
}
catch (Exception exception)
{
//Do nothing, just try again.
}
}

Entity Framework: Remove and Add entities with same key in a single request

I need to Remove and Add entities with the same primary key value in a single request, can anybody suggest me the solution?
Below is my sample code that gives the error: Violation of PRIMARY KEY constraint 'PK_Table'. Cannot insert duplicate key in object 'dbo.Table'.
context.Set<Entity>().Attach(existingEntityObj);
Entry(existingEntityObj).State = EntityState.Deleted;
context.Set<Entity>().Add(newEntityObj);
context.Entry<Entity>(newEntityObj).State = EntityState.Added;
context.SaveChanges();
Assume both the objects (existingEntityObj and newEntityObj) have the same value in the primary key property.
thanks in advance!!
You'll need to do two SaveChanges() calls in order to make this work. The problem here is that, while it appears you are first deleting the record and then adding a new one, the framework is actually doing the insert first.
The reason is because Entity Framework doesn't give you granular control over what orders the operations happen in. So your best bet is going to be to wrap the two in separate TransactionScope's which will let you control the individual transactions that are occurring.
You can read more here: https://blogs.msdn.microsoft.com/alexj/2009/01/11/savechangesfalse/
A single update statement can work for you so the solution is to update the old entity with the new entity values, if you want to keep the same primary key value. As primary key identifies an entity, deleting old one and adding a new one in place of it will have the same effect as update.
Correct me if you feel I am wrong, by giving me a perfect example.
You can also try using explicit DbContextTransaction, like so:
using (DbContextTransaction transaction = context.Database.BeginTransaction())
{
context.DoSomething();
context.SaveChanges();
context.DoSomethingElse();
context.SaveChanges();
transaction.Commit();
}
Don't forget to also catch exceptions and then do transaction.Rollback();

Nhibernate throws GenericAdoException instead I would expect ObjectNotFoundException

I have a entity object, that has via FK referened other entity object. In my example batch.Equipment references Equipment entity. If I try to insert the object using this code:
var batch = new Batch();
batch.Equipment = session.Load<Equipment>(someEquipmentId);
session.Save(batch);
Everything is working fine, but I would expect that, if it happens that Equipment with the someEquipmentId doesn't exist, nhibernate would throw me ObjectNotFoundException, instead it throws GenericAdoException saying that there was violation of foreign key, which is obvious because the someEquipmentId doesn't exist in database, so the batch cannot be inserted with that equipment id, but I thought nhibernate would handle that for me.
So the question is, is there a way (some mapping attributes or something) that would make nhibernate throw ObjectNotFoundException in such cases, or do I have to do session.Get(someEquipmentId) and check it for null? I mean I can do that, but it gives me in my opinion unecessary db roundtrips, feels like repetitive code and I don't like checking for every reference like that as there are many and the cases where this actually happens are rare and really exception, I prefer putting it whole in try catch and processing the ObjectNotFoundException in one place. I need it to report back to user, why the insert failed, specifying which entity doesn't exist (requirement).
The answer here is pretty straightforward: Load(id) is a contract, representing the existing value. So, if the passed id value could be wrong (not existing), you do not trust it: you must use Get(id) and check the null.
Please, do read this article: Ayende - NHibernate – The difference between Get, Load and querying by id, some extract:
Get() and Load() are here for a reason, they provide a way to get an entity by primary key. That is important for several aspects, most importantly, it means that NHibernate can apply quite a few optimizations for this process.
But there is another side to that, there is a significant (and subtle) difference between Get and Load.
Load will never return null. It will always return an entity or throw an exception. Because that is the contract that we have we it, it is permissible for Load to not hit the database when you call it, it is free to return a proxy instead.
...
Get, however, is different. Get will return null if the object does not exist. Since this is its contract, it must return either the entity or null, so it cannot give you a proxy if the entity is not known to exist. Get will usually result in a select against the database, but it will check the session cache and the 2nd level cache first to get the values first.
session.Load(id) will never return null. It will return a proxy instead in your case, because the id doesn't exist. Load is purposed to NOT hit the database, but to load the object from cache.
"Load will never return null. It will always return an entity or throw an exception. Because that is the contract that we have we it, it is permissible for Load to not hit the database when you call it, it is free to return a proxy instead.
Why is this useful? Well, if you know that the value exist in the database, and you don’t want to pay the extra select to have that, but you want to get that value so we can add that reference to an object, you can use Load to do so:
s.Save(
new Order
{
Amount = amount,
customer = s.Load<Customer>(1)
}
);
The code above will not result in a select to the database, but when we commit the transaction, we will set the CustomerID column to 1. This is how NHibernate maintain the OO facade when giving you the same optimization benefits of working directly with the low level API." - Ayende Rahien
http://ayende.com/blog/3988/nhibernate-the-difference-between-get-load-and-querying-by-id

How to deal with a stale cache in Entity Framework?

I had been getting very strange behavior form entity framework. I am coding a WebApi application so the objects I get from the browser are disconnected/detached. The data I get back is transactional such that it does not match any given table in the database. I have to do a number of lookups and data manipulation to get the actual updates to be done on the database.
The problem I seem to have is that in querying the data I am filling up the Tracked Changes cache. That wouldn't seem to be a problem to me since the true source of data should be the database. When I finally make the data changes and I call SaveChanges I get constraint errors. Here are my steps.
Query data.
Create rows to be inserted.
compare rows to db and make db changes.
After reviewing the data in Ctx.ChangeTracker.Entries() I found that an entry to be deleted was marked as Modified when it was supposed to be deleted. The way I worked around it was by Creating a new context for step 3. And it magically started working. I thought that was it, but in my test case I do a last read from the database to verify that my transaction was writing correctly. And I was getting an extra row that should already be deleted. And in fact was, when checking the db directly. Again a new context to do that last read fixed the problem.
I just assumed the default cache setting would just be used to track changes and not to speed up queries.
If I try to use AsNoTracking in my queries I also get into trouble because if I try to delete a row queried like that I get an error. And in my code I don't know if I am going to delete or modify until later on. Is there a way to clear the cache so I don't need to create a new context?
Is there a better way to deal with these issues?
EDIT:
AsNoTracking will do the trick, to some extent. I still found myself instantiating more copies of DbContext in order to prevent errors. Many to one entities have to be deleted in order or null foreign key errors are triggered.
var details = oldInvoice.details.ToList();
Context.Entry(oldInvoice).State = EntityState.Unchanged;
Context.Entry(oldInvoice).State = EntityState.Deleted;
details.ForEach(a => Context.Entry(a).State = EntityState.Deleted);
Entity Framework offers an exception DbUpdateConcurrencyException that you can catch on your calls to SaveChanges(). you could loop through the errors something like this:
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency(currentValues, databaseValues,
resolvedValues);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
} while (saveFailed);
also, your update code sounds suspicious as well. Usually when you pass data out to a client through WebApi or other mechanisms, the data that is returned doesn't have the tracking data, so you should be checking to see if it exists and re-attaching it to the context and changing it's state to EntityState.Modified if so before calling SaveChanges().

Entity Framework separate DbContext per c(r)ud operation?

I encountered some strange issue using entity framework and I am not sure what's the moral.
Should I use a separate context object for every create, update and delete operation?
I have an entity, that has a unique constraint. I receive values from a different thread and want to store them in an entity and persist them to the database. If I am using one DbContext and adding an entity fails because of unique constraint violation, all following attempts to store an valid entity will fail, too. This is because the invalid entity is still in the DbContext's set.
The problem reduces to this, I think:
/*
* m_context is a memeber variable of the class and created once
* new MyEntity("unique") => creates new entity and sets the unique attribute to the passed value
* the entity's key is set automatically
*/
//works fine
try { m_context.MyEntities.Add(new MyEntity("unique"); m_context.SaveChanges() } catch{}
//fails because of unique constraint violation => this is okay
try { m_context.MyEntities.Add(new MyEntity("unique"); m_context.SaveChanges() } catch{}
//fails, too => not okay and not expected
try { m_context.MyEntities.Add(new MyEntity("unique2"); m_context.SaveChanges() } catch{}
Exceptions are System.Data.Infrastructure.DbUpdateException (an error occurred while updating the entries) -> System.Data.UpdateException (an error occurred while updating the entries) -> System.Data.SQLite.SQLiteException (constraint failed column ... is not unique)
=> In the scenario above twice, with separate context (see below), once, only.
I think the problem will occur with every database provider and is not related to SQLite.
So there are at least two solutions:
Remove the second entity manually from the DbSet in MyContext
Use a seperate context for each create operation
I tend to use the second. This is because I saw a lot of using statements in tutorials/code.
I never understood the reason for this massive recreation of the same object, but this might be a (the) reason. Is this true, or am I completely wrong.
This problem also exists for update and delete operations. And another reasons for failure might be a foreign key violation.
So what's the best practice here? Separate context object per operation? But is the object really that light-weight? And should I apply the "one context per operation" to read operations, too?
As already mentioned, the fix is obvious, but the moral is not totally clear to me, and I am interested in what's the best practice here. Thanks!

Categories

Resources