Dan
Dan

Reputation: 788

Entity Framework Interceptor set and populate custom value back after insert

I have an entity class that has a DateTime property, LastModified, that I would like to set its value using the Interceptor feature in Entity framework 6.1

I have created an EntityFramework interceptor that populates the insert command and correct sets the value in the INSERT statement.

My pseudo code looks like this

Simple entity class with a generated id and the LastModified field

public class Item {
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id{get;set;}
    public DateTime LastModified{get;set;}
}

The interceptor (which is added using a DbConfiguration, not covered here)

public class TestInterceptor : IDbCommandTreeInterceptor
{
    void IDbCommandTreeInterceptor.TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    {
        if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
            return;

        var insertCommand = interceptionContext.Result as DbInsertCommandTree;
        if (insertCommand != null)
        {
            //get setClauses of insert command
            var setClauses = insertCommand.SetClauses.ToList();

            //this index is hardcoded to simplify the example
            //that we change the value of one of the setClauses to a custom value.
            var clause = setClauses[0];
            clause = DbExpressionBuilder.SetClause(clause.Property(),DbExpression.FromDateTime(DateTime.UtcNow));               
            setClauses[0] = clause;

            interceptionContext.Result = DbInsertCommandTree(
                insertCommand.MetadataWorkspace,
                insertCommand.DataSpace,
                insertCommand.Target,
                setClauses.AsReadOnly(),
                insertCommand.Returning);
        }

    }
}

The code to create an instance object

using(var ctx = new MyDbContext()){
    var item = new Item();
    ctx.Items.Add(item);
    ctx.SaveChanges();
}

Problem is that the database has the correct value for the LastModified column, but the item instance has not. It has the Id correctly set. I guess that I need to modify the insertCommand.Returning but how?

UPDATE: Like to clarify that I am aware that there are much simpler ways to do this but the purpose of this post is to use Interceptors, the example above is trimmed down for clarity. The end result will use attributes to mark the entity properties that should be affected by this.

Upvotes: 1

Views: 3526

Answers (3)

Oana
Oana

Reputation: 657

As explained here http://marisks.net/2016/02/27/entity-framework-soft-delete-and-automatic-created-modified-dates/

public class LastChangeInterceptor : IDbCommandTreeInterceptor
{
    public const string LastChangeColumnName = "LastChange";
    public const string LastChangeByColumnName = "LastChangeBy";
    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    {
        if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
        {
            return;
        }

        var lastChange = DateTime.Now;
        var lastChangeBy = HttpContext.Current.User.Identity.Name;

        var insertCommand = interceptionContext.Result as DbInsertCommandTree;
        var updateCommand = interceptionContext.OriginalResult as DbUpdateCommandTree;

        if (insertCommand != null)
        {
            var setClauses = insertCommand.SetClauses
                .Select(clause => clause.UpdateIfMatch(LastChangeColumnName, DbExpression.FromDateTime(lastChange)))
                .Select(clause => clause.UpdateIfMatch(LastChangeByColumnName, DbExpression.FromString(lastChangeBy)))
                .ToList();

            interceptionContext.Result = new DbInsertCommandTree(insertCommand.MetadataWorkspace, insertCommand.DataSpace, insertCommand.Target, setClauses.AsReadOnly(), insertCommand.Returning);
        }

        else if (updateCommand != null)
        {
            var setClauses = updateCommand.SetClauses
                .Select(clause => clause.UpdateIfMatch(LastChangeColumnName, DbExpression.FromDateTime(lastChange)))
                .Select(clause => clause.UpdateIfMatch(LastChangeByColumnName, DbExpression.FromString(lastChangeBy)))
                .ToList();

            interceptionContext.Result = new DbUpdateCommandTree(updateCommand.MetadataWorkspace, updateCommand.DataSpace, updateCommand.Target, updateCommand.Predicate, setClauses.AsReadOnly(), null);
        }
    }
}



public static class Extensions
{
    public static DbModificationClause UpdateIfMatch(this DbModificationClause clause, string property, DbExpression value)
    {
        var propertyExpression = (DbPropertyExpression)((DbSetClause)clause).Property;

        if (propertyExpression.Property.Name == property)
        {
            return DbExpressionBuilder.SetClause(propertyExpression, value);
        }
        return clause;
    }

}

Upvotes: 1

Ilya Sulimanov
Ilya Sulimanov

Reputation: 7846

You can use quite good library for management your Interceptors. EntityHooks

This is example how you can easy achieve your goal:

public class MyDbContext : DbContext
{
    public MyDbContext()
    {
        this.CreateHook()
            .OnSave<Item>()
            .Do(i=> i.LastUpdated = DateTime.Now)
     }
 }

Upvotes: 0

Daprpaz
Daprpaz

Reputation: 106

I have an entity class that has a DateTime property, LastModified, that I would like to set its value using the Interceptor feature in Entity framework 6.1

If you want to set value for LastModified property in one place of your application, you can do this before SaveChanges() using ChangeTracker:

var entities = ChangeTracker.Entries().Where(x => x.Entity is Item && (x.State == EntityState.Added || x.State == EntityState.Modified));
foreach (var entity in entities)
{
    if (entity.State == EntityState.Added)
    {
        ((BaseEntity)entity.Entity).LastModified = DateTime.UtcNow;
    }
}

If you want to set value for LastModified property by your database engine (using DEFAULT or TRIGGER ), you can mark your property with this:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? LastUpdated { get; set; }

Using IDbCommandTreeInterceptor for this kind of scenarios looks like an overengineering to me.

Upvotes: 1

Related Questions