Best way to override SaveChanges()

This code will be better with :

    var added = this.ChangeTracker.Entries()
            .Where(t => t.Entity is ITrack && t.State == EntityState.Added)
            .Select(t => t.Entity)
            .ToArray();

The same for modified :

    var modified = this.ChangeTracker.Entries()
            .Where(t => t.Entity is ITrack && t.State == EntityState.Modified)
            .Select(t => t.Entity)
            .ToArray();

And remove if conditions in foreach loop...


you can do the following

1- create an Interface in your application that all the classes that has the following properties will implement this interface: Id, CreatedDate,CreatedBy, ModifiedDate,ModifiedBy

public interface ITrack
{
      int Id{get; set;}
      int CreatedBy{get; set;}
      DateTime CreatedDate{get; set;}
      int? ModifiedBy{get; set;} // int? because at first add, there is no modification
      DateTime? ModifiedBy {get; set;}
}

Best practices Define the CreatedBy and ModifiedBy as string which will be good for performance and maintenance

2- Add a class TrackableEntry which implements the interface ITrack

public abstract class TrackableEntry : ITrack
{
      public int Id{get; set;}
      public int CreatedBy{get; set;}
      public DateTime CreatedDate{get; set;}
      public int? ModifiedBy{get; set;} 
      public DateTime? ModifiedBy {get; set;}
}

3- remove the properties mentioned in the interface from all of your classes and let these classes to implement directly from TrackableEntry

public class A: TrackableEntry
{
    //public int Id{get; set;}
    //public int CreatedBy{get; set;}
    //public DateTime CreatedDate{get; set;}
    //public int? ModifiedBy{get; set;}
    //public DateTime? ModifiedBy {get; set;}
}

4- In your DbContext file override your SaveChanges and add property UserId or UserName if you followed the *Best practices* part

public int UserId{get; set;}

public override int SaveChanges()
{
    this.ChangeTracker.DetectChanges();
    var added = this.ChangeTracker.Entries()
                .Where(t => t.State == EntityState.Added)
                .Select(t => t.Entity)
                .ToArray();

    foreach (var entity in added)
    {
        if (entity is ITrack)
        {
            var track = entity as ITrack;
            track.CreatedDate = DateTime.Now;
            track.CreatedBy = UserId;
        }
    }

    var modified = this.ChangeTracker.Entries()
                .Where(t => t.State == EntityState.Modified)
                .Select(t => t.Entity)
                .ToArray();

    foreach (var entity in modified)
    {
        if (entity is ITrack)
        {
            var track = entity as ITrack;
            track.ModifiedDate = DateTime.Now;
            track.ModifiedBy = UserId;
        }
    }
    return base.SaveChanges();
}

finally in your forms when you want to call SaveChanges method, ensure you set the UserId or UserName value

var entities=new Entities(); // assuming that your DbContext file called Entities
// code for adding or deletion or modification here
entities.As.Add(new A(){...});

// ....

entities.UserId=MyUser;
entities.SaveChanges();

hope this will help you


To 'override' SaveChanges without needing to create all that extra code, I wrapped it in its own extension method. i.e.

public static int SaveChangesWrapped(this MyDbContext db)
{
    // do other things

    return db.SaveChanges();
}

or async...

public static Task<int> SaveChangesAsyncWrapped(this MyDbContext db)
{
    // do other things

    return await db.SaveChangesAsync();
}

You can tweak it however you need, i.e. add other parameters, make it generic, etc. I may be missing something but this was enough for me to 'override' SaveChanges and cut down a whole lot of repeated code.

Then anywhere in your code when you want to save, it's just:

db.SaveChangesWrapped();

//or

await db.SaveChangedAsyncWrapped();