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

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.

Related

Why is this unused line breaking Entity Framework?

I am updating some existing code of a former colleague and have a strange issue where an unused line is causing an error with Entity Framework. If I comment out the code tagged with //This Line!, everything works.
foreach (Place item in ListOfPlaces)
{
//This line!
List<Place> PlacesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
long PlaceId = context.Places
.Where(x => x.PlaceNumberID == item.PlaceNumberID)
.Select(x => x.PlaceId)
.FirstOrDefault();
if (PlaceId != 0)
{
item.ID = PlaceId;
context.Places.Attach(item);
context.Entry(item).State = System.Data.Entity.EntityState.Modified;
}
}
If I include that line, I get the error shown here on the Attach(item) line:
Attaching an entity of type 'Place' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
I know how to fix this from a code point of view (remove the line!), but I can't work out why its breaking the application if somebody could kindly explain please.
I can't work out why its breaking the application
Looks to me like the line causes the download of some Place with ID N - see the ToList on the end? It will trigger the query to run and download data. EF creates objects from every row it receives because that's the default behavior(it can be disabled with eg AsNoTracking)
Later you try to create another object with the same primary key value and attach it to the context, but the context already knows about some object with ID 123 (for example) because the first line caused it to have been downloaded/tracked so you get an error when you try and associate another - if EF allowed both into its tracking memory it wouldn't know which one was the true authority of record that should be saved back to the db
Your interim query doesn't cause the problem, I believe, because it doesn't trigger the download of an entire entity, seeing as it just retrieves an ID
If you're trying to implement insert-if-not-exists style behavior, you should attempt to download an entity with ID x using some XOrDefault or Find, and if it results in null/default then create and add a new entity (you don't need to attach). In essence, ditch the first line, just do the ID check and if the returned ID is default, do a context.Places.Add(new Place{...}).
If you're looking for upsert, it's probably easiest to download the whole entity and then inspect if it was default or not; if it is, then make a new one otherwise edit the downloaded one.
If you're trying for "update without download" then skip the querying part entirely and attach an entity you declare as modified.
If you're after some hybrid upsets without download, I think you'll struggle, because you have to at least quiz the db as to whether it knows of an entity before you decide what to do.. or you run a raw MERGE
That entire loop makes no sense. You repeat the same twice. And as soon as you select one of the items, EF marks it as a tracked. And you can't update using another item, before the first one will be untracked or you can use the tracked item.
Try this code
foreach (Place item in ListOfPlaces)
{
var placesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
if(placesList!=null && placesList.Count ==1)
{
var existedPlace = placesList.FirstOrDefault();
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
// and maybe this
else context.Places.Add(item)
}
context.SaveChanges();
UPDATE
Thanks to #CaiusJard for a hint, in this case it is more efficient to use SingleOrDefault instead of ToList
.....
var existedPlace = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).SingleOrDefault();
if(existedPlace!=null)
{
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
.....

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

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.

Can entities be attached to an ISession that weren't previously attached?

I'm playing around with NHibernate 3.0. So far things are pretty cool. I'm trying to attach an entity that wasn't detached previously:
var post = new Post(){ Id = 2 };
session.Update(post); // Thought this would work but it doesn't.
post.Title = "New Title After Update";
session.Flush();
What I'm trying to write is the following:
var post = new Post(){ Id = 2 };
session.Attach(post);
post.Title = "New Title After Update";
session.Flush(); // Sql should be something like: UPDATE Post SET Title='New Title After Update' WHERE Id=2
Is this possible so that only Title gets updated? This is currently possible in EntityFramework. I'd like to not have to load Post from the database when I just need to update a few properties. Also, I'm trying to avoid a method call that would create the object... since it's moving away from an object oriented approach in my opinion.
EDIT: I know about using transactions, I just used Flush() to make the code simple. Ok so I think we're sort of getting on the right track for what I'm trying to achieve. I'd like to be able to create an entity with a known Id using the constructor, like I have in the 2nd code block above. I don't want to have to make a call to Get<T> or Load<T> since it feels rather wrong constructing objects like this that already exist in the database. For example, in Entity Framework I can write the 2nd code example and it will "just work". It only updates the Title property.
You can session.Save() or session.SaveOrUpdate()
update
Okay, I think I see now what you are trying to do. You are trying to update a single property on a Post that was previously persisted, not a new Post, and to do that you're instantiating a new Post and giving it the Id of one in the database.
I'm not sure what you mean when you say you're trying to avoid a method call that would create the object, but the way to do this with NHibernate is this:
var post = session.Load<Post>(2);
post.Title = "New Title";
session.SaveOrUpdate(post);
In general, you should not be calling Flush() on your sessions.
The important thing to note here is the use of session.Load. Calling Load with an id in and of itself does not load the entity from the database. The entity's property values will only be loaded when/if you access them.
Of course, in this scenario, I believe that NHibernate will load the properties for the Post, (but not collections unless you've specified an eager fetch mode), and that makes sense (frankly, I don't understand why EF would not load the entity). What if the setter for your Title property does something important, like check it against the existing title, validate the title's length, check your credentials, or update another property? Simply sending an UPDATE to the database isn't sufficient.
It's possible to only update changed properties by setting dynamic-update in the mapping. However, as far as I know, it is not possible (without reverting to SQL) to perform an update without retrieving the object from the database at some point.
Use the Merge method. You have to create a new instance variable to accept the attached entity = nhibernate will not do anything else with your detached instance.
var post = new Post(){ Id = 2 };
post.Title = "New Title After Update";
// Must create a new instance to hold final attached entity
var attachedPost = session.Merge(post);
session.Update(attachedPost);
session.Flush();
// Use attachedPost after this if still needed as in session entity
That covers the "attach" functionality you are looking for, but I don't see how you are going to be able to only update the one property. if the object instance has not been populated from the database, the properties will be different. Dynamic mapping will not solve this - NHibernate sees the properties as "updated" to a bunch of nulls, empty strings.
Gotta say, you are creating a new instance but what you are actually doing is updating an existing instance. You are working directly with IDs not objects. And you are setting a single property and now have an instance potentially hanging around and doing more things but it has not enforced any invariants and may in fact bear no resemblence to the real deal other than the id property...
It all feels pretty anti-object oriented to me personally.

Categories

Resources