I have read quite a few posts about this, and I can't see how my situation is different, but it must be because it still doesn't update.
Basically, my method receives a detached entity in a message. I check it's key to see if it already exists. If it does not exist I add it to the database. (this works fine) If it exists I would like to update its values.
Here is my code:
InteropObject clientObject = (InteropObject)message.ItemToAddUpdate;
bool exists = context.InteropObjects.Any(o => o.GUID == clientObject.GUID);
if (!exists)
{
context.InteropObjects.AddObject(clientObject);
}
else
{
context.Attach(clientObject);
context.ObjectStateManager.GetObjectStateEntry(clientObject).SetModified();
}
context.SaveChanges();
thanks for the help!
The problem is that in the old ObjectContext API, setting the state to Modified does not set the properties of the entity to Modified. In the DbContext API this is fixed internally by a call that does do that.
In the ObjectContext API, you can get the same effect by setting the properties of the attached entity:
context.Attach(clientObject);
var ose = context.ObjectStateManager.GetObjectStateEntry(clientObject);
// Obtain an object array containing current values
var values = new object[ose.CurrentValues.FieldCount];
ose.CurrentValues.GetValues(values);
// "Overwrite" CurrentValues by these values:
ose.CurrentValues.SetValues(values);
So you set the properties by the values they already have, but this triggers the state manager to mark the property as Modified.
Related
When we pass our DbContext an object whose values have not changed, and try to perform an Update we get a 500 internal server error.
A user may open a dialog box to edit a record, change a value, change it back and then send the record to the database. Also we provide a Backup and Restore function and when the records are restored, some of them will not have changed since the backup was performed.
I was under the impression that a PUT would delete and re-create the record so I didn't feel there would be a problem.
For example, having checked that the Activity exists my ActivityController is as follows:
var activityEntityFromRepo = _activityRepository.GetActivity(id);
// Map(source object (Dto), destination object (Entity))
_mapper.Map(activityForUpdateDto, activityEntityFromRepo);
_activityRepository.UpdateActivity(activityEntityFromRepo);
// Save the updated Activity entity, added to the DbContext, to the SQL database.
if (await _activityRepository.SaveChangesAsync())
{
var activityFromRepo = _activityRepository.GetActivity(id);
if (activityFromRepo == null)
{
return NotFound("Updated Activity could not be found");
}
var activity = _mapper.Map<ActivityDto>(activityFromRepo);
return Ok(activity);
}
else
{
// The save failed.
var message = $"Could not update Activity {id} in the database.";
_logger.LogWarning(message);
throw new Exception(message);
};
My ActivityRepository is as follows:
public void UpdateActivity(Activity activity)
{
_context.Activities.Update(activity);
}
If any of the fields have changed then we don't get the error. Do I have to check every record for equality before the PUT? It seems unnecessary.
Perhaps I have missed something obvious. Any suggestions very welcome.
There is a lot of code missing here.
In your code you call your SaveChangesAsync (not the EF SaveChangesAsync).
Probably (but there is not the code to be sure) your SaveChangesAsync is something that returns false if there is an exception (and is not a good pattern because you "loose" the exception info) or if DbSet.SaveChangesAsync returns 0.
I think (but there is a lot of missing code) that this is your case. If you don't make any changes, SaveChangesAsync returns 0.
EDIT
The System.Exception is raised by your code (last line). EF never throws System.Exception.
I just changed our database, adding a new column to a table and setting it as a foreign key. We're using EF4 Database first, so I updated the model from the DB, added my new field to the DTOs and everything looked great until I tried to save data to it.
The new column is called DiaryEventId, and because it's a FK there's also a virtual property on the object called DiaryEvent. Here's what the code looks like:
public void SaveDocument(Guid CaseId, string diaryText, string ActivityType)
{
Guid eventTypeId = RepositoryHelper.GetDiaryEventFromCache("Document Uploaded", _commonQueryContext);
//this wanders off and created a diary event object, adds it to
//the context and returns its ID
Guid diaryId = RepositoryHelper.AuditEvent(CaseId, diaryText, commonUpdateContext);
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();,
ActivityType = activityType,
DiaryEventId = diaryId
};
_commonUpdateContext.DocumentMetadatas.Add(docData);
_commonUpdateContext.SaveChanges();
}
This compiles and runs fine, and when you step through it it appears to function as expected - diaryId is generated and set on the object. But in the DB, it always appears as null.
I've tried calling SaveChanges after adding the diary event (to make sure the key has something to connect to) and I've tried adding the actual DiaryEvent object to the virtual property rather than just the ID - both have the same effect.
When I watch what's going on in SQL Profiler, I can see that the INSERT statement simply ignores my new column completely - it's not listed in the insert columns at all.
Really got no idea what's going on. Any ideas?
Try modifying the object after adding it to the context, just as a test.
_commonUpdateContext.DocumentMetadatas.Add(docData);
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();,
ActivityType = activityType,
DiaryEventId = diaryId
};
_commonUpdateContext.SaveChanges();
Or is this perhaps a typo?
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();, <-------------typo?
Beyond that, it will require a bit of info on how you have your entities mapped out. Is there a navigation property between DiaryEvent and DocumentMetadata? Is the key defined as nullable (I'm assuming it is)? Is ActivityType getting persisted correctly?
I'm a bit stumped. From what I've read setting the DbContext.AutoDetectChangesEnabled to false should disable change tracking requiring one to call DbContext.DetectChanges in order to identify changes to be sent to the database.
However, it is clear from my logs below that the changes are being registered by dbContexts change tracker, even with the setting set to false.
Am I missing something?
Entity Framework Version: 5.0.0.0
DbContext class
public class ProjectContext : DbContext {
public DbSet<Project> Projects {get;set;}
}
Controller class
private ProjectContext db = new ProjectContext();
public method(){
Project p = new Project("uniqueName");
db.Configuration.AutoDetectChangesEnabled = false;
db.Projects.Add(p);
DebugChangeTracker();
db.SaveChanges();
db.Projects.First().ProjectName = "a differentName!";
DebugChangeTracker();
db.SaveChanges();
}
Logging method
private void DebugChangeTracker()
{
var path = "C:\\mypath\\";
path = path + Util.GetMsSinceEpoch().ToString() + "changeTracker.log";
using (StreamWriter sw = new StreamWriter(path))
{
var changeTracker = db.ChangeTracker;
var entries = changeTracker.Entries();
foreach (var x in entries)
{
var name = x.Entity.ToString();
var state = x.State;
sw.WriteLine("");
sw.WriteLine("***Entity Name: " + name +
"is in a state of " + state);
var currentValues = x.CurrentValues;
sw.WriteLine("***CurrentValues***");
PrintPropertyValues(currentValues,sw);
if (state != EntityState.Added)
{
sw.WriteLine("***Original Values***");
PrintPropertyValues(x.OriginalValues,sw);
}
}
}
}
First log
***Entity Name: Models.Projectis in a state of Added
***CurrentValues***
ProjectId:0
ProjectName:uniqueName
Second Log
***Entity Name: Models.Projectis in a state of Modified
***CurrentValues***
ProjectId:1
ProjectName:uniqueName
***Original Values***
ProjectId:1
ProjectName:a differentName!
Setting AutoDetectChangesEnabled to false doesn't disable change tracking. (That's what the AsNoTracking() extension method would do.) It just disables the automatic call of DetectChanges that would otherwise occur in many DbContext API methods.
But DetectChanges isn't the only method that participates in change tracking. However, if you don't call it manually at the right places where it is needed the tracked entity states are incomplete or wrong leading to incorrectly saved data.
In your case the state Added in the first part of your method is expected, even with AutoDetectChangesEnabled set to false because you only call db.Projects.Add(p). (The line is missing in your code btw, but I guess it's just a copy and paste error.) Calling a method from the DbContext API tracks changes correctly and the states in the tracker will be correct if the state was correct before the call to Add.
Or in other words: Calling an API method doesn't turn a correct state into a wrong state. But: If AutoDetectChangesEnabled is false it also won't turn a wrong state into a correct state which would be the case if AutoDetectChangesEnabled is true.
However, in the second part of your method you are just changing a POCO property value. After this point the change tracker state is wrong (Unchanged) and without a call to DetectChanges (manually or - if AutoDetectChangesEnabled is true - automatically in ChangeTracker.Entries or SaveChanges) it will never be adjusted. The effect is that the changed property value is not saved to the database.
In the last section mentioning the state Unchanged I'm refering to my own test (and also to what I would expect). I don't know and can't reproduce why you have state Modified.
Sorry, if this sounds all a bit confusing. Arthur Vickers can explain it better.
I find automatic change detection and the behaviour when disabling it rather difficult to understand and to master and I usually don't touch the default (AutoDetectChangesEnabled = true) for any tracked changes that are more complex than the simplest things (like bulk adding entities in a loop, etc.).
If someone looking for AutoDetectChangesEnabled in Entity Framework Core you can find it under ChangeTracker insted of Configuration
Usage like:
context.ChangeTracker.AutoDetectChangesEnabled = false;
//Do something here
context.PriceRecords.Add(newPriceRecord);
context.ChangeTracker.AutoDetectChangesEnabled = true;
according to Entity Framework Automatic Detect Changes's Article
they said:
you may get significant performance improvements by turning it off in some cases
look at this example from that article
using (var context = new BloggingContext())
{
try
{
context.Configuration.AutoDetectChangesEnabled = false;
// Make many calls in a loop
foreach (var blog in aLotOfBlogs)
{
context.Blogs.Add(blog);
}
}
finally
{
context.Configuration.AutoDetectChangesEnabled = true;
}
}
This code avoids unnecessary calls to DetectChanges that would have occurred while calling the DbSet.Add and SaveChanges methods.
I have an entity with self reference (generated by Entity Designer):
public MyEntity: EntityObject
{
// only relevant stuff here
public int Id { get...; set...; }
public MyEntity Parent { get...; set...; }
public EntityCollection<MyEntity> Children { get...; set...; }
...
}
I've written a stored procedure that returns a subtree of nodes (not just immediate children) from the table and returns a list of MyEntity objects. I'm using a stored proc to avoid lazy loading of an arbitrary deep tree. This way I get relevant subtree nodes back from the DB in a single call.
List<MyEntity> nodes = context.GetSubtree(rootId).ToList();
All fine. But when I check nodes[0].Children, its Count equals to 0. But if I debug and check context.MyEntities.Results view, Children enumerations get populated. Checking my result reveals children under my node[0].
How can I programaticaly force my entity context to do in-memory magic and put correct references on Parent and Children properties?
UPDATE 1
I've tried calling
context.Refresh(ClientWins, nodes);
after my GetSubtree() call which does set relations properly, but fetches same nodes again from the DB. It's still just a workaround. But better than getting the whole set with context.MyEntities().ToList().
UPDATE 2
I've reliably solved this by using EF Extensions project. Check my answer below.
You need to assign one end of the relationship. First, divide the collection:
var root = nodes.Where(n => n.Id == rootId).First();
var children = nodes.Where(n => n.Id != rootId);
Now, fix up the relationship.
In your case, you'd do either:
foreach (var c in children)
{
c.Parent = root;
}
...or:
foreach (var c in children)
{
root.Children.Add(c);
}
It doesn't matter which.
Note that this marks the entities as modfied. You'll need to change that if you intend to call SaveChanges on the context and don't want this saved.
The REAL solution
Based on this article (read text under The problem), navigation properties are obviously not populated/updated when one uses stored procedures to return data.
But there's a nice manual solution to this. Use EF Extensions project and write your own entity Materilizer<EntityType> where you can correctly set navigation properties like this:
...
ParentReference = {
EntityKey = new EntityKey(
"EntityContextName.ParentEntitySetname",
new[] {
new EntityKeyMember(
"ParentEntityIdPropertyName",
reader.Field<int>("FKNameFromSP")
)
})
}
...
And that's it. Calling stored procedure will return correct data, and entity object instances will be correctly related to eachother. I advise you check EF Extensions' samples, where you will find lots of nice things.
I have the following code snippet.
// Here I get an entity out of an EntityCollection<TEntity> i.e ContactSet. Entity is obtained and not null.
ProjectContact obj = ((Project)projectDataGrid.SelectedItem).ContactSet
.Where(projectContact => projectContact.ProjectId == item.ProjectId &&
projectContact.ContactId == item.ContactId).First();
// And the next line I just check whether ContactSet contains the queried entity that i.e. obj.
bool found = ((Project)projectDataGrid.SelectedItem).ContactSet.Contains(obj);
but found is always false. How can that be?
edit: Matt thank you for your guidance but let me make it a bit more clear since I haven't given out the full source code.
I have three tables in my database:
Project, Contact and ProjectContact and there's a many-to-many relationship between Project and Contact table through the ProjectContact table, although ProjectContact table has some extra columns other than Project and Contact table keys, and that's why I get an extra entity called ProjectContact if I use ADO.NET entity framework's entity designer generated code.
Now at some point I get a Project instance within my code by using a linq to entities query i.e:
var item = (from project in myObjectContext.Project.Include("ContactSet.Contact")
orderby project.Name select project).FirstOrDefault();
Note that ContactSet is the navigational property of Project to ProjectContact table and Contact is the navigational property of ProjectContact to Contact table.
Moreover the queried Project in question i.e. "item" has already some ProjectContacts in its item.ContactSet entity collection, and ContactSet is a standard EntityCollection implementation generated by entity designer.
On the other hand, ProjectContact overrides Equals() and GetHashCode() etc. but if I use that overriden implementation within an EqualityComparer then Project.ContactSet.Contains returns true so I'm guessing there's no problem with that but now the tricky part comes along. Assume that I have the following snippet:
using(SomeObjectContext myObjectContext = new SomeObjectContext())
{
var projectQueryable = from project in myObjectContext.Project.Include("ContactSet.Contact") orderby project.Name select project;
ObservableCollection<Project> projects = new ObservableCollection<Project>(projectQueryable.ToList());
var contactQueryable = from contact in myObjectContext.Contact select contact;
ObservableCollection<Contact> contacts = new ObservableCollection<Contact>(contactQueryable.ToList());
Project p = projects[0];
Contact c = contacts[0];
//Now if I execute the code below it fails.
ProjectContact projectContact = new ProjectContact();
projectContact.Contact = c;
projectContact.Project = p;
projectContact.ContactId = c.Id;
projectContact.ProjectId = p.Id;
projectContact.Role = ContactRole.Administrator; // This corresponds to the column in ProjectContact table and I do manual conversion within the partial class since EF doesn't support enums yet.
p.ContactSet.Add(projectContact); // This line might be unnecessary but just to be on the safe side.
// So now p.ContactSet does indeed contain the projectContact and projectContact's EntityState is Added as expected. But when I execute the line below without saving changes it fails.
bool result = p.ContactSet.Remove(projectContact); // result == false and projectContact is still in the p.ContactSet EntityCollection.
//Now if I execute the line below
myObjectContext.Delete(projectContact);
//Now projectContact's EntityState becomes Detached but it's still in p.ContactSet.
// Also note that if I test
bool exists = p.ContactSet.Contains(projectContact);
// It also returns false even if I query the item with p.ProjectContact.Where(...) it returns false.
}
Since everything occurs within the same ObjectContext I think I am missing something about EntityCollection.Remove(). But it seems still odd that ContactSet.Contains() returns false for an item obtained via direct Where query over ContactSet. In the end the question becomes:
How do you really Remove an item from an EntityCollection without persisting to the database first. Since a Remove() call after Add() apparently fails.
This looks like it should work, Some ideas:
Does ProjectContact override Object.Equals()? Or perhaps the ContactSet implements ICollection, and there may be a bug in the implementation of ICollection.Contains()?