Additional Operations as part of Entity Framework Save - c#

As part of the save operation in my DbContext, I need to serialize extended information to be stored in a binary field in the database.
I've written the following method to intercept all Client objects that have been added or modified to ensure the serialization takes place before they are saved. I'm wondering if there is a better way of doing this or if there will be problems created by this method.
public int Save()
{
foreach (Client client in this.Context.Local.Clients)
{
EntityState state = this.Context.Entry(client).State;
if (state == EntityState.Added || state == EntityState.Modified)
{
client.SerializeExtended();
}
}
return this.Context.SaveChanges();
}

override DbContext.SaveChanges method.
protected override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<Clients())
{
var entity = entry.Entity;
if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
{
entry.Entity.SerializeExtended();
}
}
base.SaveChanges();
}

Related

Populate Created and LastModified automagically in EF Core

Looking forward to build a framework, (No repository pattern to working with DbSets directly) to autopopulate Created and last modified automatically, rather than spitting out these codes through out code base.
Could you point me out in right direction how to achieve it.
In past I tried populating these in constructors, however that seems
like a nasty code and every time we pull up somting from database EF
change tracking will mark the entity as modified.
.ctor()
{
Created = DateTime.Now;
LastModified = DateTime.Now;
}
public interface IHasCreationLastModified
{
DateTime Created { get; set; }
DateTime? LastModified { get; set; }
}
public class Account : IEntity, IHasCreationLastModified
{
public long Id { get; set; }
public DateTime Created { get; set; }
public DateTime? LastModified { get; set; }
public virtual IdentityUser IdentityUser { get; set; }
}
Starting with v2.1, EF Core provides State change events:
New Tracked And StateChanged events on ChangeTracker can be used to write logic that reacts to entities entering the DbContext or changing their state.
You can subscribe to these events from inside your DbContext constructor
ChangeTracker.Tracked += OnEntityTracked;
ChangeTracker.StateChanged += OnEntityStateChanged;
and do something like this:
void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{
if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IHasCreationLastModified entity)
entity.Created = DateTime.Now;
}
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity)
entity.LastModified = DateTime.Now;
}
One possible solution (the one we're currently using where I work) is to override the SaveChanges() method in the DbContext and add a little bit of code to loop through changed entities setting the values
public override int SaveChanges()
{
var changedEntriesCopy = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.ToList();
var saveTime = DateTime.Now;
foreach (var entityEntry in changedEntriesCopy)
{
if (entityEntry.Metadata.FindProperty("Created") != null && entityEntry.Property("Created").CurrentValue == null)
{
entityEntry.Property("Created").CurrentValue = saveTime;
}
if (entityEntry.Metadata.FindProperty("Updated") != null)
{
entityEntry.Property("Updated").CurrentValue = saveTime;
}
}
return base.SaveChanges();
}
Our situation is basically set up similar to that - Created in our situation is nullable so that when a new entity is created the value is null in code up to creation (makes it easy to check if Created has been populated ever).
There may be other solutions, but this was the easiest to get set up for us, and hasnt had any noticeable performance impact

Entity Framework 5 Soft Delete

I am trying to prevent any deletes on my database tables. Currently using Entity Framework 5. Firstly here is my code,
public override int SaveChanges()
{
var Changed = ChangeTracker.Entries();
if (Changed != null)
{
foreach (var entry in Changed.Where(e => e.State == EntityState.Deleted))
{
entry.State = EntityState.Unchanged;
}
}
return base.SaveChanges();
}
I've managed to prevent it with this way. When i use Remove method of EF its not working anymore.However, what i am trying to achieve is, when i use the remove method for the given ID, i want to set isDeleted(which is a (bit) column in all my db tables) value to false. Currently, i am lost in the documents and shared codes around the internet.
Thanks
I would probably handle this by making entities that are soft deletable implement an interface, something like ISoftDeletable.
public interface ISoftDeletable
{
bool IsDeleted { get; set; }
}
Then extend your code above to check if the entity type implements the ISoftDeletable interface, if it does simply set IsDeleted to true.
public override int SaveChanges()
{
var Changed = ChangeTracker.Entries();
if (Changed != null)
{
foreach (var entry in Changed.Where(e => e.State == EntityState.Deleted))
{
entry.State = EntityState.Unchanged;
if (entry.Entity is ISoftDeletable)
{
// Set IsDeleted....
}
}
}
return base.SaveChanges();
}
You would then need to make sure queries for the Entities that implement ISoftDeletable filter out those that are soft deleted.
Building on #BenjaminPauls great answer, but using the generic Entries<TEntity>. In my opinion it cleans up the code and the nestling a bit.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ISoftDeletable>())
{
if (entry.State == EntityState.Deleted)
{
// Set deleted.
}
}
return base.SaveChanges();
}
Or even:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ISoftDeletable>()
.Where(x => x.State == EntityState.Deleted)
{
// Set deleted.
}
return base.SaveChanges();
}

InsertOrUpdate Generic Repository

Please advise, I'm using generic repository with UOW, and I got this error when I'm testing my InsertOrUpdate method.(I'm new in both c# and EF)
Result Message:
Test method UnitTestProject1.ManifestUOW.ManifestUOWTest threw exception:
System.InvalidOperationException: Attaching an entity of type 'DomainClasses.ManifestDetail'
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.
My generic repository
public T FindById(object id)
{
return _set.Find(id);
}
public void Add(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
entry.State = EntityState.Added;
}
else
{
this._set.Add(entity);
}
}
public void Update(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
this._set.Attach(entity);
}
entry.State = EntityState.Modified;
}
public void InsertOrUpdate(T entity, object id)
{
var _Record = FindById(id);
if (_Record != null)
{
Update(entity);
}
else
{
Add(entity);
}
}
My test implementation
[TestMethod]
public void ManifestUOWTest()
{
ApplicationUOW appUOW = new ApplicationUOW();
ManifestDetail manD=new ManifestDetail();
for (var i = 20; i <= 22; i++)
{
manD = new ManifestDetail();
manD.ID = "kkke" + i;
manD.ManifestID = "kkke";
manD.ModifiedDate = DateTime.Now;
manD.PriorityID = 1;
manD.JobNo = "8888777";
manD.PartNo = "ppppp";
manD.OpSeq = "9000";
manD.QTY = 9;
manD.Comment = "";
manD.LitNO = "Lit no";
appUOW.ManifestDetails.InsertOrUpdate(manD, manD.ID);
}
var man = new Manifest();
man.ID = "kkke";
man.CreatedDate = DateTime.Now;
man.ManifestStateID = 2;
man.MFBldgID = 1;
man.MFDestBldgID = 2;
man.UserID = "X6344";
appUOW.Manifests.InsertOrUpdate(man, man.ID);
appUOW.SaveChanges();
}
My UOW
namespace DataLayer
{
public class ApplicationUOW:IDisposable
{
private AuditorStationDB _context = new AuditorStationDB();
private IRepository<Manifest> _manifests = null;
public IRepository<Manifest> Manifests
{
get
{
if (this._manifests == null)
{
this._manifests = new GenericRepository<Manifest>(this._context);
}
return this._manifests;
}
}
private IRepository<ManifestDetail> _manifestDetails = null;
public IRepository<ManifestDetail> ManifestDetails
{
get
{
if (this._manifestDetails == null)
{
this._manifestDetails = new GenericRepository<ManifestDetail>(this._context);
}
return this._manifestDetails;
}
}
public void SaveChanges()
{
this._context.SaveChanges();
}
public void Dispose()
{
if (this._context != null)
{
this._context.Dispose();
}
}
}
}
The problem you are having is by your InsertOrUpdate() method calling the Update() method.
You first asked EF to locate your entity by Id, so at this point, in EF it is tracking that entity.
Then you pass in the T entity into Update(), in which your Update() method ask it to Attach() it to the EF.
Now the EF already tracked a same entity at step 1, you are trying to ask it to Attach() same entity(both
entity has same primary key) at step 2, that is why it failed.
So to solve your problem, there is possible 2 ways (not tested):
don't use FindById() to determine InsertOrUpdate() - maybe if id = 0 will mean need insert else update.
before calling Update(), detach your entity FindById() - this._ctx.Entry(entity).State = EntityState.Detached
Although in this article (http://msdn.microsoft.com/en-us/data/jj592676.aspx), it is not talking about Generic repository, but the idea of how to implement InsertOrUpdate method in EF can be found at bottom.

Entity Framework not saving modified children

Frustrating, this. Here's a pair of related objects, as generated by database-first Entity Framework:
public partial class DevelopmentType
{
public DevelopmentType()
{
this.DefaultCharges = new HashSet<DefaultCharge>();
}
public System.Guid RowId { get; set; }
public string Type { get; set; }
public virtual ICollection<DefaultCharge> DefaultCharges { get; set; }
}
public partial class DefaultCharge
{
public System.Guid RowId { get; set; }
public decimal ChargeableRate { get; set; }
public Nullable<System.Guid> DevelopmentType_RowId { get; set; }
public virtual DevelopmentType DevelopmentType { get; set; }
}
Here's the code that I'm calling to save a DevelopmentType - it involves automapper since we differentiate entity objects from DTOs:
public void SaveDevelopmentType(DevelopmentType_dto dt)
{
Entities.DevelopmentType mappedDevType = Mapper.Map<DevelopmentType_dto, Entities.DevelopmentType>(dt);
_Context.Entry(mappedDevType).State = System.Data.EntityState.Modified;
_Context.DevelopmentTypes.Attach(mappedDevType);
_Context.SaveChanges();
}
In my user interface, the most common operation will be for a user to look at a list of DevelopmentTypes and update their DefaultCharge. So when I test this using the above code, it runs without error, but nothing actually changes.
If I pause in the debugger it's clear that the changed DefaultCharge is being passed into the function, and that it's attached to the DevelopmentType to be saved.
Stepping through it, if I change the value manually inside visual studio, it does save the updated value. Which is just even more confusing.
Monitoring the database with SQL Server Profiler reveals that update commands are issued only for the parent object and not for any attached objects.
I have other similar code elsewhere that functions as expected. What am I doing wrong here?
EDIT:
I have discovered that if you do this prior to the call to SaveDevelopmentType:
using (TransactionScope scope = new TransactionScope())
{
dt.Type = "Test1";
dt.DefaultCharges.First().ChargeableRate = 99;
_CILRepository.SaveDevelopmentType(dt);
scope.Complete();
}
The change to Type saves, but the change to ChargeableRate does not. I don't think it helps, massively, but thought I'd add it.
The problem is, that EF is not aware of the changed DefaultCharges.
By setting the State of the DevelopmentType to EntityState.Modified, EF only knows that the object DevelopmentType has been changed. However, this means that EF will only update DevelopmentType but not it's navigation properties.
A workaround - which isn't best practice - would be to iterate over all DefaultCharge of the current DevelopmentType and set the entity state to EntityState.Modified.
Additionally I would recommend to attach the entity to the context first, and change the state afterwards.
EDIT after comment
As you are using DTOs I suppose you are transfering these objects either through different layers or different machines.
In this case I would recommend to use self tracking entities, because it is not possible to share one context. These entities additionally holds their current state (ie. new, updated, deleted etc). There are many tutorials on the net about self tracking entities.
e.g. MSDN - Working with Self-Tracking Entities
As far as I know EF can save child entities only if the parent object was retrieved with the same Context that is trying to save it. That is attaching an object that was retrieved by one context to another context, will allow you to save changes to parent objects but not children. This was the result of a on old search based on which we switched to NHibernate. If memory serves correctly I was able to find a link where EF team member(s) confirmed this and that there WAS no plan to change this behavior. Unfortunately all links related to that search have been erased from my PC since.
As I am not aware of how you are retrieving the objects in your case, I am not sure this is relevant to your case, but put it out there just in case it helps.
Here is a link on attaching detached objects to a context.
http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and
Context.Entry() already "Attaches" the Entity internally in order to have the context change its EntityState.
By calling Attach() you're changing the EntityState back to Unchanged. Try to comment out this line.
The Graphdiff library was a great help for me to handle all of these complexities.
You only need to set up the navigation properties that you wish to insert/update/delete (using fluent syntax) and Graphdiff will take care of it
Note: It seems to be that the project is not updated anymore but i'm using it since more than a year and is quite stable
This is not a workaround for every case, but I did discover that you can get around this by updating foreign keys on an object instead of updating navigation property objects.
For example... instead of:
myObject.myProperty = anotherPropertyObject;
Try this:
myObject.myPropertyID = anotherPropertyObject.ID;
Make sure the object is flagged as modified in EF's mind (as mentioned in other posts) and then call your save method.
Worked for me at least! It'll be a no-go when working with nested properties, but perhaps you can break your contexts up into smaller chunks and work over objects in multiple parts to avoid context bloat.
Good luck! :)
If I understand the question correctly, you have problem updating child fields. I had problems with child collection fields. I tried this and it worked for me.
You should update all child collections after attaching the object to the database context change the modified state of the parent object and save changes to the context.
Database.Products.Attach(argProduct);
argProduct.Categories = Database.Categories.Where(x => ListCategories.Contains(x.CategoryId)).ToList();
Database.Entry(argProduct).State = EntityState.Modified;
Database.SaveChanges();
I created a helper method to solve this problem.
Consider this:
public abstract class BaseEntity
{
/// <summary>
/// The unique identifier for this BaseEntity.
/// </summary>
[Key]
public Guid Id { get; set; }
}
public class BaseEntityComparer : IEqualityComparer<BaseEntity>
{
public bool Equals(BaseEntity left, BaseEntity right)
{
if (ReferenceEquals(null, right)) { return false; }
return ReferenceEquals(left, right) || left.Id.Equals(right.Id);
}
public int GetHashCode(BaseEntity obj)
{
return obj.Id.GetHashCode();
}
}
public class Event : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public HashSet<Manager> Managers { get; set; }
}
public class Manager : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public Event Event{ get; set; }
}
DbContext with the helper method:
public class MyDataContext : DbContext
{
public MyDataContext() : base("ConnectionName") { }
//Tables
public DbSet<Event> Events { get; set; }
public DbSet<Manager> Managers { get; set; }
public async Task AddOrUpdate<T>(T entity, params string[] ignoreProperties) where T : BaseEntity
{
if (entity == null || Entry(entity).State == EntityState.Added || Entry(entity).State == EntityState.Modified) { return; }
var state = await Set<T>().AnyAsync(x => x.Id == entity.Id) ? EntityState.Modified : EntityState.Added;
Entry(entity).State = state;
var type = typeof(T);
RelationshipManager relationship;
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
if (stateManager.TryGetRelationshipManager(entity, out relationship))
{
foreach (var end in relationship.GetAllRelatedEnds())
{
var isForeignKey = end.GetType().GetProperty("IsForeignKey", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end) as bool?;
var navigationProperty = end.GetType().GetProperty("NavigationProperty", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end);
var propertyName = navigationProperty?.GetType().GetProperty("Identity", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(navigationProperty) as string;
if (string.IsNullOrWhiteSpace(propertyName) || ignoreProperties.Contains(propertyName)) { continue; }
var property = type.GetProperty(propertyName);
if (property == null) { continue; }
if (end is IEnumerable) { await UpdateChildrenInternal(entity, property, isForeignKey == true); }
else { await AddOrUpdateInternal(entity, property, ignoreProperties); }
}
}
if (state == EntityState.Modified)
{
Entry(entity).OriginalValues.SetValues(await Entry(entity).GetDatabaseValuesAsync());
Entry(entity).State = GetChangedProperties(Entry(entity)).Any() ? state : EntityState.Unchanged;
}
}
private async Task AddOrUpdateInternal<T>(T entity, PropertyInfo property, params string[] ignoreProperties)
{
var method = typeof(EasementDataContext).GetMethod("AddOrUpdate");
var generic = method.MakeGenericMethod(property.PropertyType);
await (Task)generic.Invoke(this, new[] { property.GetValue(entity), ignoreProperties });
}
private async Task UpdateChildrenInternal<T>(T entity, PropertyInfo property, bool isForeignKey)
{
var type = typeof(T);
var method = isForeignKey ? typeof(EasementDataContext).GetMethod("UpdateForeignChildren") : typeof(EasementDataContext).GetMethod("UpdateChildren");
var objType = property.PropertyType.GetGenericArguments()[0];
var enumerable = typeof(IEnumerable<>).MakeGenericType(objType);
var param = Expression.Parameter(type, "x");
var body = Expression.Property(param, property);
var lambda = Expression.Lambda(Expression.Convert(body, enumerable), property.Name, new[] { param });
var generic = method.MakeGenericMethod(type, objType);
await (Task)generic.Invoke(this, new object[] { entity, lambda, null });
}
public async Task UpdateForeignChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var children = (childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>()).ToList();
foreach (var child in children) { await AddOrUpdate(child); }
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
foreach (var child in existingChildren.Except(children, comparer)) { Entry(child).State = EntityState.Deleted; }
}
public async Task UpdateChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>();
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
var addedChildren = currentChildren.Except(existingChildren, comparer).AsEnumerable();
var deletedChildren = existingChildren.Except(currentChildren, comparer).AsEnumerable();
foreach (var child in currentChildren) { await AddOrUpdate(child); }
foreach (var child in addedChildren) { stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Added); }
foreach (var child in deletedChildren)
{
Entry(child).State = EntityState.Unchanged;
stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Deleted);
}
}
public static IEnumerable<string> GetChangedProperties(DbEntityEntry dbEntry)
{
var propertyNames = dbEntry.State == EntityState.Added ? dbEntry.CurrentValues.PropertyNames : dbEntry.OriginalValues.PropertyNames;
foreach (var propertyName in propertyNames)
{
if (IsValueChanged(dbEntry, propertyName))
{
yield return propertyName;
}
}
}
private static bool IsValueChanged(DbEntityEntry dbEntry, string propertyName)
{
return !Equals(OriginalValue(dbEntry, propertyName), CurrentValue(dbEntry, propertyName));
}
private static string OriginalValue(DbEntityEntry dbEntry, string propertyName)
{
string originalValue = null;
if (dbEntry.State == EntityState.Modified)
{
originalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null
? null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString();
}
return originalValue;
}
private static string CurrentValue(DbEntityEntry dbEntry, string propertyName)
{
string newValue;
try
{
newValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null
? null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString();
}
catch (InvalidOperationException) // It will be invalid operation when its in deleted state. in that case, new value should be null
{
newValue = null;
}
return newValue;
}
}
Then I call it like this
// POST: Admin/Events/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(Event #event)
{
if (!ModelState.IsValid) { return View(#event); }
await _db.AddOrUpdate(#event);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}

Updates on an entity object not firing before delete

So basically I'm trying to do this sequence of events in entity framework.
Create a new account
Get an existing account
Move all the data from the old account to the new account (transactions, users, etc)
Delete the old account
I'm doing this all in 'one go', inside a single ObjectContext.
It fails when I call SaveChanges on the context. I get an foreign key constraint error.
I checked this in SQL profiler and it turns out that entity framework isn't sending any of the updates, just the selected and then a delete.
I kinda understand WHY it is working like that but there must be some way to force it do work properly, without having to call SaveChanges() twice or something.
Hopefully.
My merge function basically looks like this
public void Merge(Account newAccount, Account oldAccount)
{
// ...
foreach (var user in oldAccount.Users.ToList())
{
oldAccount.Users.Remove(user);
newAccount.Users.Add(user);
}
// ...
_unitOfWork.Accounts.Delete(oldAccount);
}
The objects are POCO objects created by the E.F.4 POCO Entity Generator. To avoid pasting the entire class here's just one of the association properties with it's 'fixup' function.
public virtual ICollection<User> Users
{
get
{
if (_users == null)
{
var newCollection = new FixupCollection<User>();
newCollection.CollectionChanged += FixupUsers;
_users = newCollection;
}
return _users;
}
set
{
if (!ReferenceEquals(_users, value))
{
var previousValue = _users as FixupCollection<User>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupUsers;
}
_users = value;
var newValue = value as FixupCollection<User>;
if (newValue != null)
{
newValue.CollectionChanged += FixupUsers;
}
}
}
}
private void FixupUsers(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (User item in e.NewItems)
{
item.Account = this;
}
}
if (e.OldItems != null)
{
foreach (User item in e.OldItems)
{
if (ReferenceEquals(item.Account, this))
{
item.Account = null;
}
}
}
}
You use object as reference to add and remove inside the for loop , the best solution to get object by key instate of using an object when add.
oldAccount.Users.Remove(user);
newAccount.Users.Add(users.FirstOrDefault(t=>t.ID = user.Id));
Okay figured out the solution. #CodeGorilla's comment gave me a hint.
Essentially I just need to call
_context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
before trying to delete the account. This forces entity framework to do all the updates in the database. After that there is no problem with deleting it.

Categories