I have a parent table (from oracle dataBase) and some child tables, and I can successfully update the parent entity using the Update method below, but not the child ones.
By the way, can I use with generic entities here?
public void UpdateEnt<T>(T entity) where T : parent_table
{
if (entity == null)
{
throw new ArgumentException("Cannot add a null entity.");
}
using (var _context = (DataModelDetach)d.GetContextForRead(Module))
{
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
var set = _context.Set<T>();
T attachedEntity = set.Include(x => x.children_1).Include(x => x.children_2).SingleOrDefault(e => e.idSeq == entity.idSeq);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
}
Related
I am having an issue updating a record with a composite key using EF Core 5 and Net Core 5. I am able to get the record using both parts of the composite key (two ids in my case) but when I save the changes to the database I get the following error:
Database operation expected to affect 1 row(s) but actually affected 24 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
After looking in the database, it looks like entity framework is trying to update all the records in the table with same id (first part of the composite key) instead of updating the record that I got.
Has anyone else seen this behavior? Any ideas of how to get around it or what I might be doing wrong?
Here is a basic version of the table set up
Table1:
Id (PK)
Table2:
Id (PK),
Table1Id (PK, FK)
Scenario where it fails:
Table1:
Id
--
1
2
Table2:
Id, Table1Id
------------
1, 1
2, 1
1, 2
2, 2
If I try to update the first record in Table2 (1, 1), EF would try to update it and the third record (1, 2) and throw the above error.
Code:
// Getting record
var record = await context.Table2s
.FirstOrDefaultAsync(e => e.Id == 1 && e.Table1Id == 1, cancellationToken);
// Saving
await context.SaveChangesAsync(cancellationToken);
// I have also tried specifying which record to update before saving
context.Table2s.Update(record);
Configuration for Table2:
builder.ToTable("Table2");
builder.HasKey(e => new { e.Id, e.Table1Id });
builder.HasOne(e => e.Table1)
.WithMany(m => m.Table2s)
.HasForeignKey(e => e.Table1Id)
.OnDelete(DeleteBehavior.Cascade);
It works in my own projects, I hope it will be useful.
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<WebServisUser>().HasKey(x => new
{
x.Proje, /* Composite Keys */
x.KulAdi /* Composite Keys */
});
mb.Entity<WebServisUser>().Property(e => e.Proje).IsUnicode(false);
mb.Entity<WebServisUser>().Property(e => e.KulAdi).IsUnicode(false);
}
I have added the link other functions for better understanding.
public static class Helper
{
public static bool IsNull(this object value) => (value == null || Convert.IsDBNull(value) || Convert.GetTypeCode(value) == TypeCode.Empty);
public static bool IsNotNull(this object value) => !value.IsNull();
public static string ToPropertyName(this Expression value)
{
if (typeof(LambdaExpression).IsAssignableFrom(value.GetType())) { value = ((LambdaExpression)value).Body; }
if (value is MemberExpression e_mem) { return e_mem.Member.Name; }
else if (value is UnaryExpression e_una) { return ((MemberExpression)e_una.Operand).Member.Name; }
else { throw new InvalidCastException(String.Format("value type can be \"{0}\" or \"{1}\"", nameof(MemberExpression), nameof(UnaryExpression))); }
}
public static object ChangeType(object value, Type type)
{
var c = TryTypeIsNullable(type, out Type _outtype);
return (c && value.IsNull()) ? null : (_outtype.IsEnum ? Enum.ToObject(_outtype, value) : Convert.ChangeType(value, c ? Nullable.GetUnderlyingType(type) : type));
}
public static void SetPropertyValue<T>(this T value, string propertyname, object data) where T : class
{
var p = typeof(T).GetProperty(propertyname);
p.SetValue(value, data.IsNull() ? null : ChangeType(data, p.PropertyType));
}
public static bool TryTypeIsNullable(Type value, out Type outvalue)
{
var t = (value.IsGenericType && value.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
outvalue = (t ? value.GenericTypeArguments[0] : value); return t;
}
public static bool TryTypeIsAttribute<T, Y>(T value, out Y outvalue) where T : ICustomAttributeProvider where Y : Attribute
{
bool ret;
try
{
var v = value.GetType();
var y = typeof(Y);
if (typeof(Assembly).IsAssignableFrom(v) && Attribute.IsDefined((Assembly)((object)value), y)) { ret = true; }
else if (typeof(MemberInfo).IsAssignableFrom(v) && Attribute.IsDefined((MemberInfo)((object)value), y)) { ret = true; }
else if (typeof(Module).IsAssignableFrom(v) && Attribute.IsDefined((Module)((object)value), y)) { ret = true; }
else if (typeof(ParameterInfo).IsAssignableFrom(v) && Attribute.IsDefined((ParameterInfo)((object)value), y)) { ret = true; }
else { ret = false; }
outvalue = ret ? value.GetCustomAttributes(y, false).Cast<Y>().Take(1).FirstOrDefault() : default;
}
catch
{
outvalue = default;
ret = false;
}
return ret;
}
public static void SetCompositeKey<T, TKey>(this DbContext context, T entity, Expression<Func<T, TKey>> compositekey, TKey compositekeyvalue, Action<T> actionislem = null) where T : class, new()
{
var t = typeof(T);
var c = compositekey.ToPropertyName();
var ie_prop = typeof(T).GetProperties().Where(x => x.GetMethod.IsNotNull() && x.SetMethod.IsNotNull() && !x.GetMethod.IsVirtual && !x.SetMethod.IsVirtual && !Helper.TryTypeIsAttribute(x, out NotMappedAttribute _)).Select(x => new
{
name = x.Name,
iskey = x.Name == c,
iscompositekey = Helper.TryTypeIsAttribute(x, out KeyAttribute _outkeyvalue) && _outkeyvalue.IsNotNull() && Helper.TryTypeIsAttribute(x, out DatabaseGeneratedAttribute _outdatagenvalue) && _outdatagenvalue.DatabaseGeneratedOption == DatabaseGeneratedOption.None
});
if (ie_prop.Where(x => x.iscompositekey).Count() < 2) { throw new KeyNotFoundException("minimum of 2 composite keys exception message..."); }
else if (ie_prop.Any(x => x.iscompositekey && x.iskey))
{
var addentity = new T();
var e = context.Entry<T>(entity);
var dbset = context.Set<T>();
dbset.Attach(entity);
foreach (var item in ie_prop.Select(x => new
{
x.name,
x.iskey
})) { addentity.SetPropertyValue(item.name, item.iskey ? compositekeyvalue : e.Property(item.name).OriginalValue); }
dbset.Add(addentity); /* Insert record about new key value */
if (actionislem.IsNotNull()) { actionislem(addentity); } /* If there are other areas of connection, do the operations there with the action method. */
dbset.Remove(entity); /* delete old value */
}
else { throw new Exception("compositekey must be composite key exception message..."); }
}
}
and example
using (var mdl = new mdlBayUniOrtak())
{
using (var c = new TransactionScope())
{
var beforeentity = mdl.WebServisUsers.Where(x => x.Proje == "loremipsum").Take(1).FirstOrDefault();
var e = new Action<WebServisUser>((WebServisUser afterentity) =>
{
/* Other actions if necessary. Other foreign key operations can be done here! */
});
mdl.SetCompositeKey(beforeentity, x => x.KulAdi, "galatasaray", e);
mdl.SaveChanges();
c.Complete();
}
}
namespace LED.OM.Core.Base
{
public abstract class BaseContext : DbContext
{
public BaseContext(DbContextOptions options) : base(options)
{ }
public override int SaveChanges()
{
UpdateAuditEntities();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
UpdateAuditEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
UpdateAuditEntities();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}
}
}
user.cs
entity.cs
I am working with microservice. The entity is the common field in my entire table. I want to fill in the fields created and updated by those who want to log in. But I couldn't find how to fill this user info in BaseContext.
The problem is not about microservices or CQRS. The problem is about Entity Framework Core. So you need to detect the modified entities in UpdateAuditEntities() method then you can fill the UpdatedDate and
UpdatedBy properties.
Also, this method already fills the CreatedDate and CreatedBy properties in creating (EntityState.Added) operations.
You can get an exception when casting entry.Entity to LED.OM.Core.Base.Entities.Entity. You should use this code for retrieving modified entries: var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()...
You should handle the updated entities (EntityState.Modified) like below:
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}
I'm looking for a better way for implementing the following logic (HasAnyRelation and SetDeleteMarks) into ChangeTracker in order to avoid rewriting this logic for all my entities.
The problem is that once we call Remove() it will drop all relations with child/parent entities and I can't check the relations into ChangeTracker anymore.
In this post I got a suggestion to implement a copy of my previous entity to keep my collections intact after Remove(). However it works, I also would have to implement the logic many times.
Does anybody would have any other approach to achieve it?
Regards
CONTROLLER DELETE ACTION
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Sector sector)
{
if (ModelState.IsValid)
{
var currentEntity = db.Sector.Find(sector.SectorId);
var hasAnyRelation = EntityHelper.HasAnyRelation(currentEntity);
if (hasAnyRelation)
{
currentEntity = (Sector)EntityHelper.SetDeleteMarks(currentEntity);
db.Sector.Attach(currentEntity);
db.Entry(currentEntity).State = EntityState.Modified;
}
else
{
db.Sector.Attach(currentEntity);
db.Sector.Remove(currentEntity);
}
db.SaveChanges();
}
}
ENTITY HELPER
public static class EntityHelper
{
public static object SetDeleteMarks(object entityObj)
{
var deletedProperty = entityObj.GetType().GetProperties().Where(p => p.Name == "Deleted").FirstOrDefault();
var nameProperties = entityObj.GetType().GetProperties().Where(p => p.Name.Contains("Name"));
if (deletedProperty != null)
{
deletedProperty.SetValue(entityObj, true);
foreach (var nameProperty in nameProperties)
{
var deletedMark = "(*)";
var currentValue = nameProperty.GetValue(entityObj).ToStringNullSafe();
if (!currentValue.Contains(deletedMark) && !String.IsNullOrEmpty(currentValue))
{
nameProperty.SetValue(entityObj, String.Format("{0} {1}", currentValue, deletedMark).Trim());
}
}
}
return entityObj;
}
public static bool HasAnyRelation(object entityObj)
{
var collectionProps = GetManyRelatedEntityNavigatorProperties(entityObj);
foreach (var item in collectionProps)
{
var collectionValue = GetEntityFieldValue(entityObj, item.Name);
if (collectionValue != null && collectionValue is IEnumerable)
{
var col = collectionValue as IEnumerable;
if (col.GetEnumerator().MoveNext())
{
return true;
}
}
}
return false;
}
private static object GetEntityFieldValue(this object entityObj, string propertyName)
{
var pro = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).First(x => x.Name == propertyName);
return pro.GetValue(entityObj, null);
}
private static IEnumerable<PropertyInfo> GetManyRelatedEntityNavigatorProperties(object entityObj)
{
var props = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanWrite && x.GetGetMethod().IsVirtual && x.PropertyType.IsGenericType == true);
return props;
}
}
I am attempting to write a generic Entity Framework code first Copy routine.
This routine copies the source properties, creates new child entities, copies references to lookup entities and copies child collections.
It appears to work.
On inspection of the created entity all the child, lookups and collections are all present, but when I call the DbContext.ChangeTracker.HasChanges() I get the following error;
'The property 'ID' is part of the object's key information and cannot be modified. '
On investigation it appears that the error is being raised by the newly created child entities (not the newly created root entity/parent)
Here is the routine with the relevant section populated (if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildAttribute)));)
public static void CloneCopy<T>( T original, object destination) where T : class
{
var dest = destination as T;
if (dest == null)
throw new Exception("destination does not match source type");
PropertyInfo[] props = typeof(T).GetProperties();
foreach (var propertyInfo in props)
{
if (propertyInfo.PropertyType.Namespace == "System" || propertyInfo.PropertyType.IsEnum)
{
if (!propertyInfo.CanWrite) continue;
if (propertyInfo.Name.Equals("ID")) continue;
if (propertyInfo.Name.EndsWith("ID") && propertyInfo.Name.Length > 2) continue;
var pv = propertyInfo.GetValue(original, null);
propertyInfo.SetValue(destination, pv, null);
}
else
{
//dont need to copy parent entity
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityParentAttribute)))
...
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityLookupAttribute)))
...
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityInterfaceLookupAttribute)))
...
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildAttribute)))
{
dynamic source = propertyInfo.GetValue(original, null);
var target = propertyInfo.GetValue(dest, null);
if (source == null) return;
if (target == null)
{
var t = source.GetType();
target = Activator.CreateInstance(t);
}
source.CopyMeToProvidedEntity(target);
propertyInfo.SetValue(dest, target, null);
}
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildCollectionAttribute)))
...
}
}
}
So they can copy themselves, all of my entities have the method CopyMeToProvidedEntity(target) defined on their base class and overridden on their implementation. It looks like this and calls the above function;
public override void CopyMeToProvidedEntity(object destination)
{
CloneUtil.CloneCopy(this, destination);
}
I also further define the Associations on my entities with Additional Attributes (EntityParent, EntityLookup, EntityChild, EntityChildCollection) of my own creation.
I am kind of stuck. The copy routine ignores the ID's and never writes to them on the new entities. The ID is defined thusly;
[Browsable(false)]
[Key]
public int ID { get; set; }
so is always set to 0 on initialisation
Any help will be gratefully received
24/06/2017 - Added full CloneCopy routine
public static void CloneCopy<T>( T original, object destination) where T : class
{
var dest = destination as T;
if (dest == null)
throw new Exception("destination does not match source type");
//set cloning property so update triggers etc can be ignored
((IsCloneable)original).IsCloning = true;
((IsCloneable)dest).IsCloning = true;
PropertyInfo[] props = typeof(T).GetProperties();
foreach (var propertyInfo in props)
{
if (propertyInfo.PropertyType.Namespace == "System" || propertyInfo.PropertyType.IsEnum)
{
if (!propertyInfo.CanWrite) continue;
if (propertyInfo.Name.Equals("ID")) continue;
if (propertyInfo.Name.EndsWith("ID") && propertyInfo.Name.Length > 2) continue;
var pv = propertyInfo.GetValue(original, null);
propertyInfo.SetValue(destination, pv, null);
}
else
{
//Shouldn't need to do anything here as Entity Framework handles it
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityParentAttribute)))
{
//object pv = propertyInfo.GetValue(original, null);
//propertyInfo.SetValue(Destination, pv, null);
}
//Just put the entity here
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityLookupAttribute)))
{
var pv = propertyInfo.GetValue(original, null);
propertyInfo.SetValue(dest, pv, null);
}
//Just put the entity here
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityInterfaceLookupAttribute)))
{
var pv = propertyInfo.GetValue(original, null);
propertyInfo.SetValue(dest, pv, null);
}
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildAttribute)))
{
dynamic source = propertyInfo.GetValue(original, null);
var target = propertyInfo.GetValue(dest, null);
if (source == null) return;
if (target == null)
{
var t = source.GetType();
target = Activator.CreateInstance(t);
}
source.CopyMeToProvidedEntity(target);
propertyInfo.SetValue(dest, target, null);
}
if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildCollectionAttribute)))
{
var source = propertyInfo.GetValue(original, null) as IList;
var target = propertyInfo.GetValue(dest, null) as IList;
foreach (dynamic sourceEntity in source)
{
var found = false;
object targetEntity = null;
foreach (dynamic tEntity in target)
{
if (sourceEntity.IdentityGuid != tEntity.IdentityGuid) continue;
found = true;
targetEntity = tEntity;
break;
}
if (!found)
{
var b = propertyInfo.PropertyType.GetGenericArguments()[0];
targetEntity = Activator.CreateInstance(b);
}
sourceEntity.CopyMeToProvidedEntity(targetEntity);
if (!found)
{
target.Add(targetEntity);
}
}
}
}
}
((IsCloneable)original).IsCloning = false;
((IsCloneable)dest).IsCloning = false;
}
27/06/2017 - I've kind of located what the problem was
I have a property on the base class of my entities to aid with the pairing of cloned entities to their originators;
private string _identityGuid = Guid.NewGuid().ToString();
[Browsable(false)]
[NotMapped]
public virtual string IdentityGuid
{
get { return _identityGuid; }
set { CheckPropertyChanged(ref _identityGuid, value); }
}
If this property is copied I get the ID error in the Question Title...
I have no idea why this is so.
I've renamed it "Peter" just incase it's some EF automatic thing.
To fix I've conditionally excluded any properties named "IdentityGuid" and the copy routine now works and the copy can be saved to the database.
If anybody can explain what is wrong with this property I would be most grateful :)
I do not often see a need to use reflection for this task as it is not likely that you will want to clone just any entities. Usually what you want to clone is known and there is a business use case for it. If you later determine that is the case you could do the following.
Turn Lazy Loading off on the DbContext instance if it is not off already.
Retrieve the root entity that you want to clone
Include statements to get all compositions that you want to include in the clone as well.
Do not retrieve entities that you will not clone but have a relation to, instead rely that the FK will take care of that for you.
In the retrieval statement us AsNoTracking
Set the keys of the root entity and all its compositions to their default state as if the entity were new.
Add the root entity to the DbContext
Save changes.
Overly simplified sample code:
var root = dbContext.TypeA
.AsNoTracking()
.Where(x => someCondition)
.Include(x => composititions)
.SingleOrDefault();
root.Key = 0; // reset key
root.Comps.ForEach(comp => comp.Key = 0); // reset composition keys
dbContext.TypeA.Add(root);
dbContext.SaveChanges();
in my DbContext subclass I have overridden the SaveChanges() method so I can implement a sort of Trigger-like functionality (before the changes are actually saved).
Now, in some of these triggers it is necessary to detect whether certain relationships have changed, regardless of many-to-many, one-to-one/zero etc.
I have read a number of posts on the internet, including some on this site, that mention that the DbContext API doesn't expose any means of getting relationship info.
However, ObjectContext should be able to.
My SaveChanges method:
public override int SaveChanges()
{
IEntity entity;
ChangeTracker.DetectChanges();
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var added = stateManager.GetObjectStateEntries(EntityState.Added).ToList();
var updated = stateManager.GetObjectStateEntries(EntityState.Modified).ToList();
var deleted = stateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
var unchanged = stateManager.GetObjectStateEntries(EntityState.Unchanged).ToList();
while ((entity = _entitiesRequiringTriggering.FirstOrDefault(x => x.Value).Key) != null)
{
_entitiesRequiringTriggering[entity] = false;
var entry = ChangeTracker.Entries<IEntity>().SingleOrDefault(x => x.State != EntityState.Unchanged && x.Entity == entity);
if (entry == null) continue;
var trigger = Triggers.Triggers.GetTriggerForEntity(entry.Entity, this);
if (trigger == null) continue;
trigger.BeforeSave(entry.Entity);
switch (entry.State)
{
case EntityState.Added:
trigger.BeforeAdd(entry.Entity);
break;
case EntityState.Modified:
trigger.BeforeUpdate(entry.Entity);
break;
case EntityState.Deleted:
trigger.BeforeDelete(entry.Entity);
break;
}
}
return base.SaveChanges();
}
Note the four variables added, updated, deleted and unchanged.
According to what I've found so far, the GetObjectStateEntries is supposed to return a collection of ObjectStateEntry, which has a property IsRelationship.
I run the following code in a test application:
using (var db = container.Resolve<IDatabaseContext>())
{
var cus = db.Query<Customer>().Single(x => x.Id == 1);
var newAddress = db.Query<Address>().Single(x => x.Id == 5);
cus.Address = newAddress; //also sets the foreign key property Customer.AddressId to its new corresponding value
db.SaveChanges();
}
When I inspect the code in SaveChanges after that call, I get what was expected:
one result in the updated list, the Customer object.
But at no point do I ever get an ObjectStateEntry for the relationship (one-to-one) Customer_Address.
I need to be able to detect as previously described when the relationship has changed.
For normal scalar properties you would do this:
var changed = DbEntry.Property(x => x.Name).OriginalValue == DbEntry.Property(x => x.Name).CurrentValue;
But for reference properties that doesn't work obviously.
Any ideas?
You can use this ExtensionMethod to get a list of relationships that changed
public static class DbContextExtensions
{
public static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context)
{
return GetAddedRelationships(context)
.Union(GetDeletedRelationships(context));
}
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context,
EntityState relationshipState,
Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}