I am trying to log changes to the database so that the user can see who changed what. I am using the DbEntityEntry to go through and log the DbPropertyEntity that have been modified. I am running into a problem when I want to log changes to a navigation property. I use the Reference() method to get reference to the navigation property, however unlike DbPropertyEntity, DbReferenceEntry does not have a OriginalValue only a CurrentValue attribute. How do you get the OriginalValue of a navigation property?
//Get the field that hold the id of the foreign key
var field = entry.Property(x => x.field);
//Check to see if the user changed the value
if (field.IsModified)
{
//Get the reference property associated with the field
var fieldRef = entry.Reference(x => x.fieldRef);
//Log the id change
Log(field.Name, field.CurrentValue, field.OriginalValue);
//Can't get the OriginalValue
Log(fieldRef.Name, fieldRef.CurrentValue, ???);
}
What exactly do you expect to log for the reference?
If you want to log changes in relation (= changes in foreign key) you should map the foreign key as separate property and it will be logged as normal field. If you don't map the foreign key you have an independent association. Support for independent associations in DbContext API is limited. The full support is only in ObjectContext API (you can use it from DbContext API) but it will still not solve your problem - independent association cannot be in modified state. It can be either in unchanged, added or deleted state. If you change the reference the old association is marked as deleted and new is added = there are two separate association objects tracked by the context.
Related
I have a POCO class (OPERATION) that is used as an Entity Framework entity. This class has a navigation property (OP) and a foreign key into the same related entity (OP_ID).
In a method, I get an OPERATION and on this OPERATION the OP_ID and OP are both null. When I set the OP_ID to a valid value for this foreign key, the OP navigation property remains null. When I explicitly detect changes in the context, the OP navigation property is now assigned with the correct value.
Sample code
public bool UpdateOperation(operationID)
{
IQueryable<OPERATION> operations = from o in base.ctx.OPERATION
select o;
OPERATION operation = operations
.Where(o => o.OPERATION_ID == operationID)
.Include("OP")
.FirstOrDefault();
if (operation != null)
{
operation.OP_ID = opId;
}
// operation.OP is null here
operation.GetContext().ChangeTracker.DetectChanges();
// operation.OP is populated here
}
I have confirmed that the operation is, in fact, a dynamic proxy. For what it's worth, once I detect changes, operation.OP also becomes a dynamic proxy. However, even then, assigning a different value to operation.OP_ID still requires an explicit DetectChanges() call in order to update the value of operation.OP.
Update
In response to the comment from #ErikPhilips, the documentation here seems to imply that this should happen. Specifically:
The following examples show how to use the foreign key properties and navigation properties to associate the related objects. With foreign key associations, you can use either method to change, create, or modify relationships. With independent associations, you cannot use the foreign key property.
By assigning a new value to a foreign key property, as in the following example.
course.DepartmentID = newCourse.DepartmentID;
...
When you change the relationship of the objects attached to the context by using one of the methods described above, Entity Framework needs to keep foreign keys, references, and collections in sync. Entity Framework automatically manages this synchronization (also known as relationship fix-up) for the POCO entities with proxies.
If you are using POCO entities without proxies, you must make sure that the DetectChanges method is called to synchronize the related objects in the context.
Some additional context may be useful, as well. This is a legacy application that used to work directly with an ObjectContext instead of a DbContext, though even then using EF 6. We are now migrating to the DbContext API. This particular code, without any modifications, used to demonstrate the behavior I'm expecting. Specifically, when OP_ID is assigned, I can see in the debugger that the OP property is automatically populated to point to the correct OPERATION.
In the end, I was doing exactly what the documentation described. I was
assigning a new value to a foreign key property.
Yes, Entity Framework does manage this in fix-up. And yes, the documentation does state this.
It turns out, though, that the egg is ultimately on my face. I had checked the classes generated from my T4 template, and seen that all navigation properties were marked virtual. I had not checked thoroughly enough to note that the foreign key properties were not marked virtual, however. It appears that this is the default behavior of the EF-provided T4 template used when working model- or database-first. I've addressed this by changing this line in the CodeStringGenerator.Property() method in the T4 template
Accessibility.ForProperty(edmProperty)
to
AccessibilityAndVirtual(Accessibility.ForProperty(edmProperty))
In the end, as usual, following the documentation (here, the requirements for EF change tracking on POCOs) often results in dependent code behaving as it is documented. Shame on me.
I've searched... I promise. The closest I came to an answer is a post where EF self tracking entities are used and I don't understand how to make it apply to my situation.
In my case, I have an existing record where I want to change a value from an int to a NULL.
Table Pseudocode:
PICKLIST_VALUE
Picklist ID (int, PK, Identity, NOT NULL)
PicklistValue (String, NOT NULL)
PERSON
Person_ID (int, PK,Identity, NOT NULL)
Person_Name (varchar(100), NOT NULL)
Person_Prefix (int, FK, NULL)
Person_Suffix (int, FK, NULL)
FOREIGN KEY (Person_Prefix) REFERENCES PICKLIST_VALUES.Picklist_ID
FOREIGN KEY (Person_Suffix) REFERENCES PICKLIST_VALUES.Picklist_ID
I am using EF6, DB first... With a DAL based on Magnus Montin's post at https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
In my code I have POCO definitions for each entity (table). A Person can have a prefix (Mr., Mrs., etc.) and a Person can have a suffix (Jr., Sr., etc.) If I accidentally set a suffix and then realize that it's incorrect, I want to be able to remove the suffix:
someperson.Person_Suffix = null; // Set FK property to null
someperson.PICKLIST_VALUE = null; // Set navigation property to null
someperson.EntityState = EntityStates.Modified;
DAL.UpdatePerson(someperson);
The UpdatePerson method resolves to this bit based on the DAL article:
public virtual void Update(params T[] items)
{
using (var context = new Entities())
{
DbSet<T> dbSet = context.Set<T>();
foreach (T item in items)
{
dbSet.Add(item);
foreach (DbEntityEntry<IEntity> entry in context.ChangeTracker.Entries<IEntity>())
{
IEntity entity = entry.Entity;
entry.State = GetEntityState(entity.EntityState);
}
}
context.SaveChanges();
}
}
protected static System.Data.Entity.EntityState GetEntityState(CB.DomainModel.EntityState entityState)
{
switch (entityState)
{
case DomainModel.EntityState.Unchanged:
return System.Data.Entity.EntityState.Unchanged;
case DomainModel.EntityState.Added:
return System.Data.Entity.EntityState.Added;
case DomainModel.EntityState.Modified:
return System.Data.Entity.EntityState.Modified;
case DomainModel.EntityState.Deleted:
return System.Data.Entity.EntityState.Deleted;
default:
return System.Data.Entity.EntityState.Detached;
}
}
I don't know enough of what's going on under the covers of the EF stuff and what the .edmx and T4 template code does. If I did, I could probably figure this out. But if I change the values from one int to another, the changes are persisted to the database after this call. But if I null the values, as in this example, to "clear" the foreign key reference, the original value comes back.
It must have something to do with the way the object registers with the ChangeTracker once it's added to the dbSet. Prior to that, it's just a plain old object.
So my question is why is the ChangeTracker (or whatever bit of auto-generated code) allowing the FK to change from one int value to another, but it won't persist the null? And how do I "fix" it or work around the issue?
Thanks.
J
UPDATE:
The key is definitely nullable. Both in the DB and in the class definition. The link to the other post seems to be a different scenario... They want to automatically null the foreign key when the foreign entity is deleted. (I will take a closer look, just to be sure there isn't an answer there, but it seems to be a different issue.) Just for fun I created a new object and copied the properties over, with the exception of the nav properties and set the FK property I want to remove to null and that did work. The null FK was persisted, and both the nav prop and the foreign key prop were null. So it has something to do with the change tracker and how it is maintaining a reference to the entity object, despite all the POCO-ness of it. So, at the very least, I have a work-around. But I would like to understand what's actually happening and how to address it.
SOLUTION:
I just discovered, this morning, that a change I made to the T4 template must have been over-ridden, at some point, and I lost a line setting ProxyCreationEnabled to false for the DBContext... Once I realized that and put the fix in, I was able to persist the changes properly. So, in effect, I was fighting something in the change tracker since it was creating the dynamic proxies for the entities retrieved from the DB. (I think.)
I just discovered, this morning, that a change I made to the T4 template must have been over-ridden, at some point, and I lost a line setting ProxyCreationEnabled to false for the DBContext... Once I realized that and put the fix in, I was able to persist the changes properly. So, in effect, I was fighting something in the change tracker since it was creating the dynamic proxies for the entities retrieved from the DB. (I think.)
I am trying to update a foreign key using Entity Framework 7. But it is giving error: The property 'Y' could not be found in object 'X'. I have tried many different solution but still not working. The sample code:
class X
{
property Y {get; set;} -> property Y is a foreign key and also a complex type
}
In table 'X' we have a column 'Y_ID' which is the foreign key.
Note: I just want to update the foreign key. E.g. Initially class 'X' is pointing to 'NULL', I want to update class 'X' to point to 'Y1'
The Entity Framework 7 code:
var x = this.GetX();
this.mainContext.Xs.Attach(x);
var xEntry = this.mainContext.Entry(x);
xEntry.Property("Y").CurrentValue = "Y1"; // Error at this line
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
Detailed Error:
The property 'Y' on entity type 'X' could not be found. Ensure that the property exists and has been included in the model.
Edit
The approach Fabien suggested in his comment works fine. But the problem is we only know about which property to update is at runtime. If I use reflection to achieve this, the problem is entity framework treats the object as new and tries to create it (INSERT) and then throws Primary Key violation (No duplicate entries allowed)
So, is there a way where I can't still update an object property which acts like a foreign key in EF? (I don't know exact property at compile time).
If you get the entities "X" and "Y" from your context, then they're automatically tracked by the ChangeTracker. So if you assign "Y" property of the "X" object with an "Y" instance retrieved from your context and call SaveChanges or SaveChangesAsync, EntityFramework will automically do the stuff for you.
var x = this.GetX();
x.Y = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
By convention, your property "Y" on object "X" should be virtual to indicate that it's an foreign key.
Edit 1 :
If I understand correctly, you want to update properties of your object dynamically at runtime, with values that comme from a web api.
1st way :
Like you did, you can attach your "X" object to your context instance to begin tracking of the entity with EntityState.Unchanged, and then flag each property that need to be updated :
this.mainContext.Xs.Attach(x);
var entry = this.mainContext.entry(x);
entry.Property(p => p.Y).CurrentValue = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
When attaching an entity, you can specify the GraphBehavior, it tell EntityFramework if navigation properties should traversed or not.
2nd way :
Using the DbSet.Update() method :
this.mainContext.Xs.Update(x);
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
It's automatically begin tracking of the entity with the state EntityState.Modified, all properties will be marked as modified. You should watch out when using this method, because all properties will be updated, if some of them are not inititialized in your "X" object, you could lost some data. To prevent that case, you should always validate inputs.
If you want to keep your domain models de-coupled form any ORM, then you should think to separate entity types and domain types. You can use an object mapper like Automapper to map entity to domain type and vice versa. In that way you clearly separate what you do at data access layer and business logic layer.
I am modifiying the foreign key property on an entity in code, by modifiying the Id only:
ElementData.ServiceLevelId = parameter.ServiceLevelId;
I have found, after persisting, that this only works as expected, when the corresponding navigation property ServiceLevel was null by accident. If it still holds the "old" object, the change will not hit the database.
This means, I need to do
ElementData.ServiceLevelId = parameter.ServiceLevelId;
ElementData.ServiceLevel = null; //Force the update to the Database
Does that mean, that changing the object is "stronger" than changing the id only? Should I always set the related object to null in such situations?
Update (per Tim Copenhaver's comment): The entity in question is a copy (with the mentioned modification) of an existing one. It uses Automapper for copying, and maps everything except the primary key and one unrelated property. Automapper creates a shallow copy AFAIK. Thus, the situation for the copy will be that the updated Id and the untouched object reference will not match at the moment of adding it to the context. I guess, that EF then decides that the "object reference is stronger".
Changing either property will work as long as your data mapping is correct. EF is smart enough to see which of the properties has changed and ignore the other one. You have to be careful, though - if ElementData.ServiceLevel.Id does not equal ElementData.ServiceLevelId, you will get some obscure errors.
If you're having trouble with it not saving, your mapping layer is probably not correct. We can help troubleshoot if you can post the mapping for your ElementData class and some more code around how you're doing the save.
I'm trying to update an existing entity.
I have the following code:
public MamConfiguration_V1 Save(MamConfiguration_V1 item)
{
mMaMDBEntities.MamConfiguration_V1.Attach(item);
mMaMDBEntities.ObjectStateManager.ChangeObjectState(item, System.Data.EntityState.Modified);
mMaMDBEntities.SaveChanges();
return item;
}
But the Attach methods throws an exception:
A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
How can I fix this?
Seems like you have some relationship with foreign key field and a navigation property in the item, and those fields have conflicting values. This occurs when you load an entity and its related entities, change the relationship at one end, mark only that end as Modified and attempt to save. Make sure you modify relationship at both ends and mark all the affected entities as Modified before calling SaveChanges.
I encountered this exception under a different set of circumstances, and am posting here since this question comes up when the error message is searched.
The exception was thrown when calling IObjectContextAdapter.ObjectContext.AttachTo(entitySetName, entity) with a partially-loaded entity. The foreign keys on the entity were defined, but the navigational properties were not loaded. (That is, O.ItemID had a value, but O.Item was null). The specific circumstances did not allow O.Item to be loaded.
The problem turned out to be that the Object State Manager had loaded the object in a separate method and was already tracking the object defined with the same keys. Since the separate method did not need to track the object state, the issue was resolved by calling IQueryable.AsNoTracking() within that method.
What is the definition of the item object? It seems that in some of its collections that set the realionship with other entities exist some type of conflict. You could try to clear all the collections to see if the problem persists, but in this case you lost the foreign key assignment. But perhaps it could help you to locate the problem.
This could be a tip. When I try to attach an existing entity to the context, I use to do the following:
mMaMDBEntities.Entry<MamConfiguration>(item).State = System.Data.EntityState.Modified;
You can add the using of System.Data to avoid the needed to write it all the time.
This attach the entity in the state that you want, modified in this case and track the changes. This is one line instead of two.
The issue for me was that entity framework had loaded my object in multiple places, so when I updated a foreign key, there were now two references to the same object, one with a foreign key pointing to record a and one with a foreign key pointing to record b, which caused an error since my relationship is one to one. To resolve it, I used context.Entry(Object).State = EntityState.Detached, reloaded the object, made the foreign key change and then saved my changes
Lets say you have the following schema:
If you want to edit the CurrentLocationId in Person, you also need to edit the CurrentLocation object embedded in the Person object. EF will automatically populate the CurrentLocation object because CurrentLocationId has a foreign key in the CurrentLocation's table. When you edit the CurrentLocationId without updating the CurrentLocation object as well, they become out of sync. This is what causes the exception in this case.
So let's say you needed to update the Person object's CurrentLocationId. We'll assume you pre-fetched the Person data and the Location data.
public class DbData
{
List<Person> PersonList;
List<Location> LocationList;
public DbData()
{
using (var context = new MyContext())
{
PersonList = context.Persons.ToList();
LocationList = context.Locations.ToList();
}
}
public void UpdatePersonLocation(Person person, int newLocationId)
{
using (var context = new MyContext())
{
var location = LocationList.Where(l=>l.id==newLocationId).Single();
//you need to update both the id and the location for this to not throw the exception
person.CurrentLocationId == newLocationId;
person.CurrentLocation == location;
context.Entry(person).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
}
//or if you're giving it the location object...
public void UpdatePersonLocation(Person person, Location location)
{
using (var context = new MyContext())
{
//you need to update both the id and the location for this to not throw the exception
person.CurrentLocationId == location.id;
person.CurrentLocation == location;
context.Entry(person).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
}
}
This might be an old post but the following worked for me
set the SaveOptions option to SaveOptions.DetectChangesBeforeSave