Entity Framework 6.1.1 - How to check if property is ignored from an entity in SaveChanges method

c# dbcontext entity-framework entity-framework-6

Question

Scenario:

I have following set of entities:

public interface IAuditable
{
    DateTime? CreatedDate { get; set; }
    string CreatedBy {get;set;}
    string LastModifiedBy {get;set;}
    DateTime? LastModifiedDate {get;set;}
}

public abstract class BaseEntity: IAuditable
{
    public int ID { get; set; }
    public DateTime? CreatedDate { get; set; }
    public string CreatedBy {get;set;}
    public string LastModifiedBy {get;set;}
    public DateTime? LastModifiedDate {get;set;}
}

public class Product: BaseEntity
{
    public string Name { get; set; }
}

public class Order : BaseEntity
{
    public int ProductID { get; set; }
    public int Qty { get; set; }
}

and following DbContext:

public class AuditDbContext : DbContext
    {
        public AuditDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
        }

        public DbSet<Product> Products { get; set; }
        public DbSet<Order> Orders { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Products>()
            .Ignore(t => t.CreatedDate);

            modelBuilder.Entity<Order>()
            .Ignore(t => t.LastModifiedBy);
            modelBuilder.Entity<Order>()
            .Ignore(t => t.LastModifiedDate);
        }

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

        private void SetAuditableProperties()
        {
            foreach (var auditableEntity in ChangeTracker.Entries<IAuditable>())
            {
                if (auditableEntity.State == EntityState.Added ||
                    auditableEntity.State == EntityState.Modified)
                {
                    string userName = "ABC";
                    DateTime dateTimeNow = DateTime.Now;

                    auditableEntity.Entity.LastModifiedDate = dateTimeNow;
                    auditableEntity.Entity.LastModifiedBy = userName;

                    if (auditableEntity.State == EntityState.Added)
                    {
                        auditableEntity.Entity.CreatedDate = dateTimeNow;
                        auditableEntity.Entity.CreatedBy = userName;
                    }
                    else
                    {                      
                        auditableEntity.Property(p => p.CreatedDate).IsModified = false; //TODO: Set this property if mapped otherwise dont execute this line
                        auditableEntity.Property(p => p.CreatedBy).IsModified = false; //TODO: Set this property if mapped otherwise dont execute this line
                    }
                }
            }
        }
    }

Observe I'm Ignoring some properties form entities in OnModelCreating and setting Auditable properties inside SaveChanges method by overriding it. Now, when I try to create new entity it works as expected but when I try to update an existing entity it throws exception.

Problem:

Let's say I try to update Product entity it throws following exceptions:

"Member 'IsModified' cannot be called for property 'CreatedDate' on entity of type 'Products' because the property is not part of the Entity Data Model".

Same is true for 'Order' entity but for 'LastModifiedBy' and 'LastModifiedDate' properties.

How can I check if a property is ignored from entity inside 'SetAuditableProperties' method? So that I can set 'IsModified' accordingly.

P.S. I can find out [NotMapped] properties but when I use the ModelBuilder to ignore a column, I cannot detect these.

1
0
6/7/2017 5:30:54 PM

Accepted Answer

The EF6 is over encapsulated and most of the metadata is hidden inside internal classes/members.

I see two relatively easy ways to resolve your issue.

Generally you could use DbEntityEntry<TEntity>.CurrentValues property of type DbPropertyValues, which in turn has PropertyNames property of type IEnumerable<string>, which can be used to check for existence of a property name (the non mapped properties are not included in that list):

if (auditableEntity.CurrentValues.PropertyNames.Contains(nameof(IAuditable.CreatedDate)))
    auditableEntity.Property(p => p.CreatedDate).IsModified = false;
if (auditableEntity.CurrentValues.PropertyNames.Contains(nameof(IAuditable.CreatedBy)))
    auditableEntity.Property(p => p.CreatedBy).IsModified = false;

Another way for this particular case is to utilize the fact that the Property method returns DbPropertyEntry instance with IsModified == false for ignored properties (only the setter throws exception):

if (auditableEntity.Property(p => p.CreatedDate).IsModified)
    auditableEntity.Property(p => p.CreatedDate).IsModified = false;
if (auditableEntity.Property(p => p.CreatedBy).IsModified)
    auditableEntity.Property(p => p.CreatedBy).IsModified = false;

Of course you could encapsulate both logics in a custom (extension) methods.

4
6/7/2017 8:23:58 PM

Popular Answer

I'm unaware of any way to query the model builder for this outside of the scope of setting the configuration - but there is a little easier of a way to perform this task using attributes

On your properties to be ignored attribute them with [NotMapped], then you would perform this for each property you want to check before updating in your audit method:

foreach (var auditableEntity in ChangeTracker.Entries<IAuditable>())
{
    var ignore = Attribute.IsDefined(auditableEntity.Entity.GetType().GetProperty("CreatedDate"), typeof(NotMappedAttribute))
    if (!ignore) {
        auditableEntity.Entity.CreatedDate = dateTimeNow;
    }
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow