NHibernate bulk restore of backup seems impossible - c#

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.
}
}

Related

Why does using AsNoTracking() still give an error for duplicates?

Either I don't understand how AsNoTracking is supposed to work, or I'm doing something wrong. From what I've read it's supposed to pull the record without saving it in the EF cache, effectively allowing me to re-add it as a new row.
I have this bit of code, and I'm changing the primary key, but when I try to save the changes, it's throwing an exception saying that it can't add it because of a duplicate key. The weird thing is the exception lists the OLD id instead of the newly generated one.
Any thoughts? I'm pulling out my hair.
var coverage = db.Coverages.AsNoTracking().Where(c => c.ID == oldCoverageID).FirstOrDefault();
coverage.ID = Guid.NewGuid();
coverage.PolicyID = newPolicy.ID;
db.Coverages.Add(coverage);
db.SaveChanges(); // erroring here
Edit:
As it turns out, I was using a sub-entity of the original record to grab the ID before calling that method. I'm assuming that even though I called AsNoTracking() on the original record as well.. the EF still caches the sub-entities. And there was no method exposed to call it. So, what I did was grabbed the ID using another call to the DB instead of using the sub entity. I don't know why it worked. But it did. If someone could explain either a better solution or why the problems exists, please do. I'll award the answer to whoever helps figure out why EF behaves this way.
To add context here are the ways I am/was grabbing the oldID before sending it to that method.
// fetching the id this way gave me fits
Guid oldCoverageID = existingPolicy.Coverage.Where(c => c.PolicyID == "policy id").First().ID;
// grabing the id doing this made everything happy
Guid oldCoverageID = db.Coverages.Where(c => c.POLICYHDRID == oldPolicyID).FirstOrDefault().ID;
I also did AsNoTracking() when fetching the policy as well.. I wouldn't think that would matter.. but just throwing out as much info as I can.

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

Why does the old value stay in this object after it is reset?

I've shown three programmers this problem and we're all stumped. I call a Sql Server stored procedure in a foreach loop and the result always is the same as the first call. Even if I hard code parameters (removing the loop) only the first result is assigned to all subsequent calls.
The stored procedure is called by an Entity Framework function import (EF4 database first using the designer). The calling code lives in a repository that is a class library. The repository is called by a separate Asp.net webforms project. The problem code looks like this:
IEnumerable<WorkOrder> orders = _context.GetWorkOrders(UserName, workOrder, customerCode).ToList();
OrderStatus lastStatus = new OrderStatus();
foreach (Order order in orders)
{
lastStatus = _context.GetOrderStatus(order.OrderNumber).FirstOrDefault();
order.LastOrderStatus = lastStatus.OrderStatus;
}
As you can see this is pretty basic stuff. Depending on the order numbers passed in I always get the result of the first order number in the loop. I've turned off Ajax (part of the Telerik controls I use) because that has caused baffling errors for me in the past. I really hope you can suggest a way to debug this problem! Thanks in advance.
EDIT: Daniel J.G.'s comment led me to this possible solution. Now I need to figure out how to apply Ladislav Mrnka's answer..."Try to call ExecuteFunction directly with MergeOption.OverwriteChanges."
I'm answering my own question (since no one else has after a few days). The problem is caused by the Entity Framework database first designer. It generates code that caches the first stored procedure result causing the bad results in subsequent calls.
As I mentioned in the edit to my question the fix involves replacing the default MergeOption parameter used by ExecuteFunction. You need to use MergeOption.OverwriteChanges instead of the default (which I believe is MergeOption.PreserveChanges).
You could change that parameter in the generated code but your changes would be lost each time the designer is rebuilt. Instead I simply copied the generated code to my repository class, changed the MergeOption to OverwriteChanges, and stopped using the generated code. The end result looks like this:
IEnumerable<WorkOrder> orders = _context.GetWorkOrders(UserName, workOrder, customerCode).ToList();
OrderStatus lastStatus = new OrderStatus();
foreach (Order order in orders)
{
ObjectParameter workOrderParameter;
if (wo.WorkOrder != null)
{
workOrderParameter = new ObjectParameter("WorkOrder", order.WorkOrder);
}
else
{
workOrderParameter = new ObjectParameter("WorkOrder", typeof(global::System.String));
}
lastStatus = _context.ExecuteFunction<OrderStatus>("GetOrderStatus", MergeOption.OverwriteChanges, workOrderParameter).FirstOrDefault();
if (status != null)
{
order.LastOrderStatus = status.OrderStatus;
}
}
I also see that there is a way you can modify the T4 template to make the generated code use the correct MergeOption parameter. I haven't tried it though. If you're interested take a look here.
I'm back with a second answer to my own question. Be sure the Entity Key is truly a unique identifier for each Entity!
In my case, the OrderStock Entity was missing the OrderID (along with StockID) as the Entity Key. Typically the designer culls the primary key fields from the database but I have a unique situation (where my entity is based on a view). Since I left off OrderID from the Entity Key I saw duplicate rows for a single OrderStock Entity.
When I marked OrderID Entity Key = True the duplicate problem went away.

NHibernate: Saving stops when hitting a unique constraint

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.

Categories

Resources