How to deal with a stale cache in Entity Framework? - c#

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

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

Error in Entity Framework Seed Method Only When Not Printing Debugging Statements

In our Entity Framework 6 seed method, we're setting user preferences to default values. We had a lot of issues getting stuff to work correctly, so we started printing debugging statements to a file. Now, however, if we remove the debugging lines, we get an exception.
Here is the code:
// Get the preferences.
Preferences prefs = context.Preferences.FirstOrDefault(x => x.UserId == user.Id);
using (StreamWriter write = new StreamWriter(#"C:\myFile.txt"))
{
//foreach (PropertyInfo prop in prefs.GetType().GetProperties())
// write.WriteLine($"{prop.Name} = {prop.GetValue(prefs)}");
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
prefs.CategoriesOnYAxis = true;
prefs.TabHorizontal = true;
prefs.OnlyAssignedToUser = true;
context.SaveChanges();
}
If we uncomment that For loop, then the seed method runs fine. With the for loop commented out, we get the following exception:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
In this example, the User object is 1-1 to the Preferences object, with Preferences having a foreign key to the User table.
We can even take out the stream writer and loop through the properties to the console, and as long as that loop is there the seed method runs correctly. As soon as it's gone, we get the error.
I have a guess what might be happening here. I think this is your problem:
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
I assume these are backed by the same field in your database? So you're setting both the Column entities as well as the ColumnId primary keys? You don't need to set both (though it should work).
I bet if you remove the assignment to prefs.Columns (and leave out the debug code), your code will start to work. The issue is defaultColumns. What's in there? Column entities - but are those entities attached to the current DbContext? (Your code doesn't show how they come into being)
When you fetch your prefs with context.Preferences.FirstOrDefault(x => x.UserId == user.Id);, you're asking EF for a Preference entity, but by default EF won't eager-load navigation properties (like those Column entities). Conversely, if you have a bunch of Column entities but they haven't been explicitly attached to (or fetched using) the current DbContext, EF will think those Columns are new, and that relationship might not be mapped in a way that EF can insert new Columns (which you don't want to happen, anyway).
When your debug code runs, prefs.GetType().GetProperties() is enumerating all the properties in the Preference, which I think EF is then lazy loading. When you remove the loop, it no longer enumerates then, so all the Preference's navigation properties didn't get pre-loaded.
There are a few ways you can go about this:
If you're absolutely certain all those defaultColumnIds are already in the database when you run the Seed method, then set only ColumnIds and call it a day. It will match the entities in the database when it executes your query, and as long as the expected foreign keys are there you're fine.
Fetch the defaultColumns from the context as soon as you open the context, or, if you defined them in code, attach them to the ChangeTracker before you call SaveChanges.

Context.SubmitChanges() not updating despite having a PK

I am having an issue with the SubmitChanges function provided by the linq to DB implementation in C#. When I run the command, nothing throws an error but the record never gets updated. I have looked up the issue almost everyone says that it is in issue with the table nothing a primary key. However my table has a primary key assigned to it and yet SubmitChanges does not happen. To give you an overview of what I am executing, I here is a sample:
public void setApproval(string approvalCode, int ID)
{
using (DatabaseDataContext context = new DatabaseDataContext(DBConnection().getConnectionString()))
{
myRecord con = getRecord(ID); //Gets the record succesfully, PK field in tact
con.ApprovalStatus = approvalCode;
context.SubmitChanges();
}
}
As commented above, the record is successfully obtained with all the data in tact, including the PK field used to identify it. The database connection user is given the rights to update the table, though here I would expect it to break and complain.
Any ideas? Please let me know if I have not provided enough information. Any help is greatly appreciated!
You should get the object through context
public void setApproval(string approvalCode, int ID)
{
using (DatabaseDataContext context = new DatabaseDataContext(DBConnection().getConnectionString()))
{
myRecord con = context.TableName.First(item => item.ID == ID); //Gets the record succesfully, PK field in tact
con.ApprovalStatus = approvalCode;
context.SubmitChanges();
}
}
When you get the object via Context, it keep track of changes you make and then it save those changes on SubmitChanges
Where does getRecord(ID) get its context to return a record? It is not getting passed to the method, so I assume it is using a different context. SubmitChanges() would only see changes for the current context, not the context that getRecord(ID) used.
Are you checking to see if the data was updated with code or with an independent DB tool?
If in code, your read code is as suspect as the write code:
I was having similar issues when two applications with no common API were communicating through a database. The context is not a reflection of what is in the DB right now, and no amount of telling it to refresh is going to entirely fix the problem. If you need to inspect the database for something entered by another program or thread, you have to create a new database context object to inspect the database. The old database context object may still have the old data from before your most recent update.
Your getRecord function needs to create a new context or take the current context that you just edited as a parameter. If it uses a static or class level context it will not have the latest data.

NHibernate Update Not working

I can't get my update to work. The test fails and I do not see any update statements being sent to the database. Can someone tell me what I'm doing wrong?
This is my repository update procedure:
public void UpdateProject(Project proj)
{
Session.Update(proj);
}
This is the unit test I am trying:
[Test]
public void Can_Update_A_Project()
{
var project = _projects[0];
project.Name = "test project";
repository.UpdateProject(project);
var fromDb = repository.GetAProject(_projects[0].ID);
Assert.AreEqual(project.Name, fromDb.Name);
}
The test always fails. I see the test data being inserted and I see the select for the test.I don't see the update being performed. What am I missing?
Thanks!
There are a couple of things that may be happening.
1) The update is failing and NHibernate is raising an exception that is being swallowed somewhere - that can happen depending on how you've configured things. So in VS make sure all exceptions will cause a break.
2) The update is being cached and not written directly to the DB - you can force data to be written using Repository.Flush();
3) Are you sure _projects[0] has been read from the DB - I'm assuming that's happening in a TestSetup? If not NHibernate won't be aware of that as an object which is under its 'control'.
BTW - It's good practise to read the data you are going to change within the test itself, and then undo that change, resetting the DB to it's original state. In that way your test DB won't be altered by your testing.
BTW2 - In the above test, if project.Name has already been updated once, ie the test has run succesfully. Then next time around the test will suceed even if the update itself fails. A way to avoid this - append a DateTime to the project.Name, don't set it to a fixed value.
Another thing is this: when you save an entity using a session and you load the same entity from the same session using the entity's ID, you will get the same instance that you saved - regardless of whether any inserts or updates have been issued to the database.
That's because of NHibernate's 1st level cache, which is an identity map that belongs to the session.
If you want your test to check what was actually written to the database, you may do it like so:
session.Save(someEntity);
session.Flush(); // forces the entity to be inserted
session.Clear(); // clears the session's identity map, thus
// detaching someEntity from the session
var loadedEntity = session.Get<EntityType>(someEntity.Id);
// now you may compare the fields of someEntity and loadedEntity
// to verify that they were actually persisted
ISession.Update in NHibernate does not commit changes to the database. It is used to update transient instances in a different session from the one that was used to retrieve the instance (see here for details). Changes are sent to the database when a session is flushed. By default, sessions operate in FlushOnCommit mode, which means the changes to the objects will be sent to the database when the NHibernate transaction is committed (see here for details on the different flush modes).
try this may be i am wrong but its works
public void UpdateProject(Project proj)
{
ISessionFactory SessionFactory;
ISession session = SessionFactory.OpenSession();
using (ITransaction transaction = session.BeginTransaction())
{
session.Update(proj);
transaction.Commit();
}
}
FlushMode!
I'm maintaining an app I did not write and found out the hard way that even if you use a transaction and call txn.Commit() you still may not see the changes if your NHibernate Session has session.FlushMode = FlushMode.Never.
Very easy to detect. Set a breakpoint and look at session.FlushMode. Or just search all *.cs files for FlushMode.
I recently hit this same issue.
Inserts Worked
Updates Did Not
The issue was a flaw in the coding. Data was checked if the row was changed, then a new entity was mapped, and an update was sent, but nothing happened.
Solution: The actual row had to be queried from the database, then changes in C# applied to that row that was pulled back. Now NHibernate knows the row changed, run save and all worked.
public void SaveChanges()
{
_session.Flush();
_session.Transaction.Commit();
_session.BeginTransaction();
}

Categories

Resources