Say changes have been made to a detached entity in EF 4. If we want to save theses changes when we re-attached the entity, is it possible to do this with ApplyCurrentValues without querying the DB to get the original entity? I don't think so, but I'd like somebody to confirm that.
using (var ctx = new BAEntities())
{
var firstCust = (from c in ctx.Contacts select c).First();
Console.WriteLine(firstCust.FirstName);
ctx.Contacts.Detach(firstCust);
firstCust.FirstName = "Modified Value";
ctx.Contacts.Attach(firstCust);
ctx.ApplyCurrentValues("Contacts", firstCust);//Does not work
//ctx.ObjectStateManager.ChangeObjectState(firstCust, EntityState.Modified); //Works with that line
ctx.SaveChanges( );
}
Thank you
I can confirm your guess. It doesn't work this way.
When you call Attach with an entity as parameter EF adds the entity to the context in state Unchanged. Basically you are telling EF with Attach that all property values the entity has at that time represent the current values in the database.
ApplyCurrentValues is kind of an "automapper" that just copies the property values of the object you pass into ApplyCurrentValues to the attached entity that has the same key. This copy happens based on the property name.
If a property value of the attached entity is different to the property value of the object you pass into ApplyCurrentValues EF marks the property as Modified. If not the state stays Unchanged. Obviously with your procedure all property states will stay unchanged and nothing gets written to the database.
In theory you could do crazy stuff to make it work like:
firstCust.FirstName = "Modified Value";
var dummyCust = new Contact { FirstName = "UnlikelyNameThatWillNeverOccur" };
ctx.Contacts.Attach(dummyCust);
ctx.ApplyCurrentValues("Contacts", firstCust);
Here the FirstName property would be marked as Modified. But you had to do this for every property and the result will be the same as setting the state of the whole entity to Modified as you did in the commented code line.
You can by the way set a single property to Modified:
ctx.Contacts.Attach(firstCust);
ctx.ObjectStateManager.GetObjectStateEntry(firstCust)
.SetModifiedProperty("FirstName");
This will send an UPDATE statement to the database that only sets the FirstName column value (while setting the state of the whole entity to Modified will create an UPDATE statement that sets all column values to the current property values).
Related
Is it possible in .NET 6 with Entity Framework Core 6 to populate the relationship navigation property by setting the foreign key value and then call SaveChanges?
I tried it but it doesn't seem to work. Although the other way around works perfectly (if I set the navigation property to the related entity).
Screenshots:
setting the foreign key
after save changes, "department" property still null
When trying this, student.department remains null after calling SaveChanges
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges();
While if I do this, the foreign key student.departmentId gets populated after calling SaveChanges:
var student = db.Students.Find(9);
student.department = db.Departments.Find(1);
db.SaveChanges();
When trying this student.department remains null after savechanges
Setting the foreign key value doesn't load the related department. The use case for setting the foreign key directly is typically to avoid actually loading the related entity.
If you want to load the related entity, you might as well just query it and assign it to the navigation property.
After setting the foreign key property on an entity, you can load the related entity if you want to using explicit loading. eg
db.Entry(student).Reference(b => b.Department).Load();
SaveChanges will not automatically load the relationship data unless context is already tracking the corresponding entity (Change Tracking in EF Core). In addition to using one of the options to load the related data (for example the one suggested by #David Browne in his answer), following things will do the trick:
db.Departments.Find(1);
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges(); // student.department will be filled here
Or even
var student = db.Students.Find(9);
student.departmentId = 1;
db.SaveChanges();
db.Departments.Find(1); // student.department will be filled here
I want to update a non-id field of an object's navigation property when the user is on the form for modifying that object. Some of my "Department" table's fields are like this...
Id
DirectorId (object of Employee table)
NoOfEmployees
Location
and Employee table's fields are as follows:
Id
Name
Post
Now when the user is presented with modification of Department table, I also show the core info about the employees (with post of "Director") with current director selected in its ComboBox and all the relevant information in corresponding TextBoxes etc. As soon as the user chooses a different Director, relevant fields also change accordingly.
Now what i want is that if user changes some of the Director's information (e.g. Name or Age) at this point, the changes should reflect in DB instead of compelling the user to go to employee modification form and do changes there.
I tried the simple approach of modifying few fields of a database object but that approach is not working when the object-to-be-modified is navigation property (as discussed above). Here is the code...
using (CompanyDBContext db = new CompanyDBContext() {
int id = ((Department)cbDept.SelectedItem).Id;
Department currentDept = db.Departments.Where(x => x.Id == id).First();
Department newDept = new Department();
newDept.Id = currentDept.Id;
newDept.Employee.Id = (int)cbDir.SelectedValue; // --> has no effect;
// raises NullRefException "Obj. ref. not set to an instance of an obj."
.....;
.....;
db.Entry(currentDept).CurrentValues.SetValues(newDept);
if (dirNameChanged) {
var director = new Employee() {
Id = currentDept.DirectorId,
Name = tbDirName.Text.Trim()
};
db.Employees.Attach(director); // --> raises exception
db.Entry(director).Property(x => x.Name).IsModified = true;
}
db.SaveChanges();
}
.Attach() methods throws InvalidOperationException exception saying
Attaching an entity of type 'CompanyDatabase.Employee' 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.
But then using Add() method also raises similar exception...
Any work around?
P.S. Dept object is changing alright. Changing the Director object is also fine i.e. if user chooses a new Director for the Dept. Only changing the non-id fields of Director (Employee) is giving problem.
db.Employees.Attach(director); // --> raises exception
The exception message indicates that the context already contains (is tracking) Employee object with the same PK (Id == director.Id). So instead of creating new object (new Employee()) you are expected to use the existing object (EF uses reference identity, i.e. does not allow two different entity instances with one and the same PK).
The standard way of doing that is to use Find method, which will return the currently tracked object with that PK or will load it from the database. Hence the code should be something like this:
if (dirNameChanged)
{
var director = db.Employess.Find(currentDept.DirectorId);
director.Name = tbDirName.Text.Trim();
}
Note that there is no need to Attach it or manipulating the entity/property states - the returned object is attached and tracked by the context, so any property value changes are determined automatically.
I am using entity framework 5.0. I am in a rocess od changing my app from ObjectContext to DbContext model. DbContext should be according to microsoft the recommended approach. I use database forst approach and I have generated model form database.
But, at a very first simple task there is a problem. Namely simple update of a record is broken.
Let's have a simple table Item, for ilustration only:
Item
(
ItemId int NOT NULL, -- Primary key
Name nvarchar(50) NOT NULL,
Description NVARCHAR(50)
)
I have noticed that using DbContext does not support updating a record not as ObjectContext does.
In my application I have a simple update method.
public void UpdateItem()
{
MyContext context = new MyContext();
Item item = new Item();
item.ItemId = 666;
context.Items.Attach(item);
// From this point onward EF tracks the changes I make to Item
Item.Description = "Some description";
context.SaveChanges();
}
Using ObjectContext this method correctly updates a record. Using SQL profiler I can see that it generates something like this (greatly simplified!!!)
UPDATE Item
SET Description = 'Some description'
WHERE ItemId = 666
If, however I try to do the same thing in DbContext I get the exception:
System.Exception: Items.aspx.cs - logged from CustomError() ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
And no database UPDATE is issued to Sql server.
I guess that DbContext validates all the properties and the property Name is null. This by design. I do not intend to modify it, I do not even know what is it and I do not need to know what is it.
Only the property Description was changed. Clearly ObjectContext does not track changes correctly.
How can this problem be resolved?
I have researched the issue and found the something on updating records.
For example this link: https://stackoverflow.com/a/15339512/4601078
db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
But this is horrible code. For every property on should add a line like:
entry.Property(e => e.Email).IsModified = true;
This produces ugly unreadable code, an I suspect lamda expression are not stelar in performance.
Even worse are those who propose to make a roundtrip to DB to fetch existing records with all properties populated, update it and the save changes. This is a no go with regard to performance.
So, how to tackle with simple entity updates or is this DbContext just another item in microsofts collection of dead ends which serve no real purpose?
DbContext doesn't really track changes by watching properties, it compares the values to previously known values. And validation always works on the entire entity so the only real choice if you want to do things this way is to disable validation during this operation. See Entity Framework validation with partial updates
If you know for sure that the changes you apply are valid, or you have custom code to validate them, you can turn off validation by EF altogether:
db.Configuration.ValidateOnSaveEnabled = false;
This works OK as long as you do it your way: attach a new entity with a known Id (aka a stub entity) and then modify its properties. EF will only update the properties it detects as having been modified (indeed by comparing original and current values, not, as ObjectContext did, by change notifications). You shouldn't mark the entity itself as modified.
If you don't want to turn off EF's validation, but neither want to mark individual properties as modified, I think this could be a useful alternative (brought to my attention by Alex's answer).
I have a little problem and I need your help with it. I'm using Entity Framework for database handling and I want to update a dataset in this database.
I have an EntityObject with all the changes and want to be able to update this Object with existing Object.
I'm using the following code to update the data:
IQueryable<Competitors> getCompetitor = DatabaseObject.Competitors.Where(SelectOnly => SelectOnly.competitorID == competitorObject.competitorID);
Competitors competitor = getCompetitor.First();
competitor = competitorObject;
DatabaseObject.SaveChanges();
But this deosn't work. How can I update the date in database?
Assuming, that your competitorObject is of Competitors type (competitor = competitorObject), you have to attach it to your context, mark it as modified, and then save changes:
DatabaseObject.Competitors.Attach(competitorObject);
DatabaseObject.Entry(competitorObject).State = EntityState.Modified;
DatabaseObject.SaveChanges();
There's no need to retrieve source object in your case, but without attaching, the context knows nothing about your updated object.
The piece of code, which is marking an object as modified, can be a little different, if you're using ObjectContext API instead of DbContext API:
DatabaseObject.ObjectStateManager.GetObjectStateEntry(competitorObject).SetModified();
The only change you need to make to your code to get it to work is update at least one property on the fetched entity. You are updating the reference, not the property values so like this:
IQueryable<Competitors> getCompetitor = DatabaseObject.Competitors.Where(SelectOnly => SelectOnly.competitorID == competitorObject.competitorID);
Competitors competitor = getCompetitor.First();
competitor.Name = competitorObject.Name;
competitor.Contact = competitorObject.Contact;
DatabaseObject.SaveChanges();
Or as Dennis has said you can attach the CompetitorObject to the context and mark it as modified. Doing it that way will override all the properties of the existing Competitors record with the values of CompetitorObject.
Here is some code along with my assumptions based on playing around in LINQPad. Can anyone confirm this is how the lazy loading is working, and perhaps provide any additional insight/links so I can understand how it's working on the back end? Thanks in advance!
// Step 1.
var record = context.MyTable.First();
// Step 2.
var foreignKey = ForeignKeyTable.Where(x => x.Id == record.ForeignKeyId).Single();
// Step 3.
var entry = context.Entry(record);
// Step 4.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);
// Step 5.
trace(record.ForeignKey.SomeProperty);
Retrieve some record (DB is queried).
Retrieve a record that happens to be a foreign key property of record without using lazy loading like record.ForeignKey to retrieve it (DB is queried).
Get the details of the record entity.
This is the part I'm unsure about. In my testing it outputs true. I'm guessing that IsLoaded doesn't know whether or not record.ForeignKey currently has a value, but knows that record.ForeignKey is already being tracked in the context based on it's knowledge of record.ForeignKeyId and the relationships that have been established.
The db doesn't seem to be hit here, and I assume it's for the same reason IsLoaded returns true in 4. It knows that it's tracking the foreignKey object already, so it knows it doesn't have to do the lazy loading.
Edit: The actual problem I'm trying to solve can be illustrated as such:
var record = context.MyTable.First();
var foreignKey = new ForeignKey() { Id = record.ForeignKeyId, SomeProperty = 5 };
context.ForeignKeyTable.Attach(foreignKey);
var entry = context.Entry(record);
// Returns false.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);
// Doesn't query for ForeignKey, so it must know it's `loaded` somehow, and
// gets SomeProperty from my new foreignKey object. What???
trace(record.ForeignKey.SomeProperty);
EF fixes relationships (navigation properties) automatically according to primary key and foreign key values when you load an entity from the database or when you attach it to the context.
In both code snippets you have loaded record which has a foreign key to your ForeignKeyTable. The context knows this value. (It doesn't matter btw if you have exposed the foreign key in your model. It will always be loaded, also without having a FK property in your model. You can see this when watching the SQL query.)
In both cases you attach afterwards a ForeignKey entity to the context which has as primary key the value of record.ForeignKeyId which the context already knows about. As a consequence EF will set the navigation property record.ForeignKey to this attached ForeignKey entity.
Obviously IsLoaded doesn't tell you if the entity is attached to the context because in both examples it is attached but one returns true and the other false. It also doesn't tell you if record.ForeignKeyId refers to an entity, because this is also the case in both examples.
It tells you apparently only that the entity has really been loaded from the database (and not only manually attached) (which also Intellisense says about IsLoaded). That's the only difference between your first and second example.
And it seems that lazy loading is not only controlled by the IsLoaded flag. If you attach an entity for the navigation property to the context, lazy loading doesn't happen anymore although IsLoaded is false.
What would happen if your last line in the second code snippet would actually trigger lazy loading? The ForeignKey object being loaded must have the same key as the ForeignKey object you have already attached (because record has this value as FK property ForeignKeyId). But because no two objects with same key can be attached to the context it must be the same object. But then there is no need to load it since such an object is already in memory and attached.
// Step 1.
var record = context.MyTable.First();
// Step 2.
var foreignKey = ForeignKeyTable.Where(x => x.Id == record.ForeignKeyId).Single();
// Step 3.
var entry = context.Entry(record);
// Step 4.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);
// Step 5.
trace(record.ForeignKey.SomeProperty);
Retrieve some record (DB is queried). yes, and the resulting record is attached to the DbContext.
Retrieve a record that happens to be a foreign key property of record without using lazy loading like record.ForeignKey to retrieve it (DB is queried). yes. If you had wanted to eager load the foreign key in #1, you would have used context.MyTable.Include(m => m.ForeignKey).First(); That would have retrieved the record along with the fk in 1 query.
Get the details of the record entity. Kind of... it is the details of the entity in relation to the DbContext (what is attached / deleted / loaded / etc)
This is the part I'm unsure about. In my testing it outputs true. I'm guessing that IsLoaded doesn't know whether or not record.ForeignKey currently has a value, but knows that record.ForeignKey is already being tracked in the context based on it's knowledge of record.ForeignKeyId and the relationships that have been established. This means that the DbContext does not need to run another query to load the data for the foreign key. If you execute record.ForeignKey, the data is already there, and no additional trip to the db is required.
The db doesn't seem to be hit here, and I assume it's for the same reason IsLoaded returns true in 4. It knows that it's tracking the foreignKey object already, so it knows it doesn't have to do the lazy loading. The entitiy has already been loaded in step #2, so there was no additional trip needed to get it from the db.
Update after question edit
According to EF, the .Attach method on IDbSet:
Attaches the given entity to the context underlying the set. That is, the entity is placed into the context in the Unchanged state, just as if it had been read from the database.