Translate

domingo, 25 de noviembre de 2018

How to set row creation info for your Entity Framework Core entities



There are many time where we need to have tracking where our data rows were originated from,
let's say we need this information:

  • Creation Date & Time
  • Source Application
  • Source Application IP Address
  • Originator User

Depending on how big our system is, and how good or bad it is designed, it could be a real pain to send this information, specially if our system is a set of multiple applications.
So, what we could do?

When using Entity Framework Core, we can take the advantage of it using Code First and Partial Classes, so what we can do is the following

  1. Create and interface with the columns representing the Source Information
  2. Create a Partial class for each Entity which requires the Origin information and make it implement the previously created interface.
  3. Create a Partial class for our DbContext
  4. Create a custom Validation method which would check if the entity uses the interface, and if the fields are not assigned, set them a value
  5. In the partial class override the Save methods, and invoke the previously created validation method

Sample code:

public partial class MainContext : DbContext
    {
        protected string ConnectionString { get; set; }
        public MainContext(string connectionString)
        {
            this.ConnectionString = connectionString;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(ConnectionString);
            }
        }

        public override int SaveChanges()
        {
            ValidateAndSetDefaults();
            return base.SaveChanges();
        }

        public override int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            ValidateAndSetDefaults();
            return base.SaveChanges(acceptAllChangesOnSuccess);
        }

        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
        {
            ValidateAndSetDefaults();
            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }

        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            ValidateAndSetDefaults();
            return base.SaveChangesAsync(cancellationToken);
        }

        private void ValidateAndSetDefaults()
        {
            //Check https://www.bricelam.net/2016/12/13/validation-in-efcore.html
            var entities = from e in ChangeTracker.Entries()
                           where e.State == EntityState.Added
                               || e.State == EntityState.Modified
                           select e.Entity;
            foreach (var entity in entities)
            {
                if (entity is IOriginatorInfo)
                {
                    IOriginatorInfo entityWithOriginator = entity as IOriginatorInfo;
                    if (String.IsNullOrWhiteSpace(entityWithOriginator.SourceApplication))
                    {
                        entityWithOriginator.SourceApplication = System.Reflection.Assembly.GetEntryAssembly().FullName;
                    }
                    if (String.IsNullOrWhiteSpace(entityWithOriginator.OriginatorIpaddress))
                    {
                        entityWithOriginator.OriginatorIpaddress = String.Join(",",
                            PTI.NetCore.CommonUtilities.NetworkUtilities.GetCurrentHostIPv4Addresses());
                    }
                    if (entityWithOriginator.RowCreationDateTime == DateTimeOffset.MinValue)
                    {
                        entityWithOriginator.RowCreationDateTime = DateTimeOffset.UtcNow;
                    }
                    if (String.IsNullOrWhiteSpace(entityWithOriginator.RowCreationUser))
                    {
                        try
                        {
                            entityWithOriginator.RowCreationUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
                        }
                        catch (Exception)
                        {

                        }
                    }
                }
                var validationContext = new ValidationContext(entity);
                Validator.ValidateObject(
                    entity,
                    validationContext,
                    validateAllProperties: true);
            }
        }
    }