I found this answer which (unless I'm missing something) gives a good solution to catch all changes made to all independent associations in a DbContext. I am trying to adapt that solution to something more specific, to create a function that can find the changes for a particular navigation property on a particular entity. The goal is for the user of this function to not have to worry about any of the internals of EntityFramework, and to only be concerned with his POCO models.
This is what I have so far, and it works. But to my eye it looks very messy and prone to break if internal implementation details of Entity Framework change--particularly there's a "magic string" "ClrPropertyInfo"
in there that may not be intended as part of the EF interface contract.
public partial class EntityFrameworkMaterializer
{
public IEnumerable<T2> GetAssociationChanges<T1,T2>(T1 parent, string propertyName, EntityState findState)
{
// this.context is a DbContext instance that is a property of the object.
ObjectContext ocontext = ((IObjectContextAdapter)this.context).ObjectContext;
MetadataWorkspace metadataWorkspace = ocontext.MetadataWorkspace;
// Find the AssociationType that matches the property traits given as input
AssociationType atype =
metadataWorkspace.GetItems<AssociationType>(DataSpace.CSpace)
.Where(a => a.AssociationEndMembers.Any(
ae => ae.MetadataProperties.Any(mp => mp.Name == "ClrPropertyInfo" // Magic string!!!
&& ((PropertyInfo)mp.Value).Name == propertyName
&& typeof(T1).IsAssignableFrom(((PropertyInfo)mp.Value).DeclaringType)
)
)
).First();
// Find added or deleted DbDataRecords from the above discovered type
ocontext.DetectChanges();
IEnumerable<DbDataRecord> dbDataRecords = ocontext.ObjectStateManager
.GetObjectStateEntries(findState)
.Where(e => e.IsRelationship)
// Oddly, this works, while doing the same thing below requires comparing .Name...?
.Where(e => e.EntitySet.ElementType == atype)
.Select(e => findState == EntityState.Deleted ? e.OriginalValues : e.CurrentValues);
// Get the actual entities using the EntityKeys in the DbDataRecord
IList<T2> relationChanges = new List<T2>();
foreach (System.Data.Common.DbDataRecord ddr in dbDataRecords)
{
EntityKey ek = (EntityKey)ddr[0];
// Comparing .ElementType to atype doesn't work, see above...?
if (!(ek.GetEntitySet(metadataWorkspace).ElementType.Name == atype.Name))
{
ek = (EntityKey)ddr[1];
}
relationChanges.Add((T2)ocontext.GetObjectByKey(ek));
}
return relationChanges;
}
}
Example of usage:
// Student <--> Course is many-to-many Independent Association
IEnumerable<Course> droppedCourses;
droppedCourses = GetAssociationChanges<Student, Course>(
student,
"EnrolledCourses",
EntityState.Deleted
);
This was the result of a LOT of reverse-engineering and digging around through objects in the debugger. Besides the magic string problem, I wonder if there is a more direct path to figuring out the type of the "relationship entries" representing the IAs based on the "end" entity type and the navigation property name.