DbEntityEntry.OriginalValues ​​does not populate complex properties

I am writing an audit trail of code fragments found on the Internet. When I call my SaveChanges function, I look through all the changed objects registered in the Context and recording the log entries from their changes.

foreach (DbEntityEntry modifiedEntity in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified)) { // For each changed record, get the audit record entries and add them foreach(AuditLog x in GetAuditRecordsForChange(modifiedEntity, userId)) { this.AuditLog.Add(x); } } 

When I then try to access the initial values ​​of the modified object, all scalar properties are populated, but there are no complex ones (the number of properties will be considered 6 instead of 8). Then I call ToObject() to build the object in its original state, but obviously all zeros are complex properties.

 modifiedEntity.OriginalValues.ToObject() 

This only happens with some of my domain objects, and these objects always appear as proxies after calling ToObject() , whereas (I'm not sure why), but those that do not have proxies created for them by the entity, their complex properties are well filled. When I use POCO proxies as usual throughout the application, lazy loading works fine for them.

I noticed that if I make changes to one of these complex properties that are not populated as part of the OriginalValues ​​data, the state of the object does not change to Modified, this makes sense, because change tracking compares the original value for the current to see if it has changed. What does not make sense is that the data is saved on SaveChanged ??

EDIT: I just noticed that the model object that fills its complex properties is a complex property (by convention), considered as a "complex type" by Entity i. There is no primary key.

Any ideas?

+5
entity-framework
Aug 11 '11 at 8:10
source share
3 answers

To get all the member names of the entity, and not just simple properties that you can use with an ObjectContext , not a DbContext , then access the list of elements through EntityType .

 ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members 

Then you can use the DbEntityEntry.Member (string propertyName) method to get the DbMemberEntry.

Returns an object representing a member of the object. The runtime type of the returned object depends on the type of request. The currently supported member types and their return types are the Reference navigation (DbReferenceEntry) property, the collection navigation property (DbCollectionEntry), the Primitive / scalar property (DbPropertyEntry), and the Complex property (DbComplexPropertyEntry).

In the code example below, this is used to register modifications to complex properties. Please note that when registering complex property changes, something more sexual is possible. I am currently registering an entire complex property (serialized in JSON), not just the internal properties that have changed, but it is doing its job.

 private IEnumerable<AuditLogEntry> GetAuditLogEntries(DbEntityEntry dbEntry) { if (dbEntry.State == EntityState.Added) { return new AuditLogEntry { ... }; } if (dbEntry.State == EntityState.Deleted) { return new AuditLogEntry { ... }; } if (dbEntry.State == EntityState.Modified) { // Create one AuditLogEntry per updated field. var list = new List<AuditLogEntry>(); // We need to object state entry to do deeper things. ObjectStateEntry objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry); // Iterate over the members (ie properties (including complex properties), references, collections) of the entity type foreach (EdmMember member in ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members) { var dbMemberEntry = dbEntry.Member(member.Name) as DbPropertyEntry; if (dbMemberEntry == null || Equals(dbMemberEntry.OriginalValue, dbMemberEntry.CurrentValue)) { // Member entry isn't a property entry or it isn't modified. continue; } string oldValue; string newValue; if (dbMemberEntry is DbComplexPropertyEntry) { // Bit a bit lazy here and just serialise the complex property to JSON rather than detect which inner properties have changed. var complexProperty = (DbComplexPropertyEntry)dbMemberEntry; oldValue = EntitySerialiser.Serialise(complexProperty.OriginalValue as IAuditableComplexType); newValue = EntitySerialiser.Serialise(complexProperty.CurrentValue as IAuditableComplexType); } else { // It just a plain property, get the old and new values. var property = dbMemberEntry; oldValue = property.OriginalValue.ToStringOrNull(); newValue = property.CurrentValue.ToStringOrNull(); } list.Add(new AuditLogEntry { ..., EventType = AuditEventType.Update, ColumnName = member.Name, OriginalValue = oldValue, NewValue = newValue }); } return list; } // Otherwise empty. return Enumerable.Empty<AuditLogEntry>(); } 

I look forward to other solutions.

+8
Nov 10 '11 at 11:43
source share

I believe this article may give you some insight. This is not EF 4.1, but many tips and examples apply.

Complex Types and the New Change Tracking API

This is a little earlier than in the middle of the tutorial with the section name being the link name. Basically, to access the original values ​​with a complex type, you add an extra function that defines the complex property.

 var original = modifiedEntity.ComplexProperty(u => u.Address).OriginalValues 
+4
Aug 11 2018-11-11T00:
source share

More digging, it seems that EF change tracking does not store any initial values ​​for reference or collection type properties for modified objects (someone please correct me if I am wrong)

I can find out, for example, that my Vehicle object was referencing one VehicleColour object and then added again, pointing to another VehicleColour instance. I cannot find out, for example, that it points to VehicleColour with the name "Stardust Silver" and now points to one with "Azure Blue".

+1
Aug 12 2018-11-11T00:
source share



All Articles