Как перехватить и изменить массовые обновления? - PullRequest
0 голосов
/ 15 мая 2019

В нескольких местах в моем коде я делаю массовые обновления с использованием удобных расширений денди Z.EntityFramework.Plus, например,

await db.Foos
        .Where(f => f.SomeCondition)
        .UpdateAsync(f => new Foo { Field1 = "bar", Field2 = f.Field2 + 1 });

, которые обновят все Foo записи, где SomeCondition - true, настройка Field1 до "бара" и Field2 будут увеличены на единицу.

Теперь появилось новое требование, согласно которому некоторые таблицы (но не все) отслеживают ModifiedDate.Это включает записи, где я делаю массовые обновления.

Так что мой подход такой.У меня есть интерфейс:

public interface ITrackModifiedDate
{
    DateTime ModifiedDate { get; set; }
}

Так что все мои классы, которые отслеживают ModifiedDate, могут реализовать ITrackModifiedDate.Затем я пишу расширение среднего уровня для перехвата вызовов .UpdateAsync():

public static async Task<int> UpdateAsync<T>(this IQueryable<T> queryable, Expression<Func<T, T>> updateFactory)
        where T : class
{
    if (typeof(ITrackModifiedDate).IsAssignableFrom(typeof(T)))
    {
        // TODO Now what?
    }

    return await BatchUpdateExtensions.UpdateAsync(queryable, updateFactory);
}

Как видите, я не совсем уверен, как изменить updateFactory для установки ModifiedDate в DateTime.UtcNow, поверх других полей, которые уже обновляются.

Как это сделать?

ОБНОВЛЕНИЕ: Я не против изменить свое расширение так, чтобыон принимает только T типа ITrackModifiedDate, если это помогает, то есть

public static async Task<int> UpdateAsync<T>(this IQueryable<T> queryable, Expression<Func<T, T>> updateFactory)
    where T : class, ITrackModifiedDate
{
        // TODO what now?

    return await BatchUpdateExtensions.UpdateAsync(queryable, updateFactory);
}

1 Ответ

1 голос
/ 16 мая 2019

Я получил его, работая со следующим кодом:

using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Z.EntityFramework.Plus; 

class Program
{
    static async Task Main(string[] args)
    {
        using (var context = new SomeContext())
        {
            await context
                .Customers
                .Where(c => c.Email.Contains("42"))
                .CustomUpdateAsync((c) => new Customer()
                {
                    Email = "4242"
                });
        }
    }

}

public static class Helper
{
    public static async Task<int> CustomUpdateAsync<T>(this IQueryable<T> queryable, Expression<Func<T, T>> updateFactory)
        where T : class
    {
        var targetType = typeof(T);
        if (typeof(ITrackModifiedDate).IsAssignableFrom(targetType))
        {
            updateFactory = (Expression<Func<T, T>>)new TrackModifiedDateVisitor().Modify(updateFactory);
        }

        return await BatchUpdateExtensions.UpdateAsync(queryable, updateFactory);
    }
}


public class TrackModifiedDateVisitor : ExpressionVisitor
{
    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }

    public override Expression Visit(Expression node)
    {
        if (node is MemberInitExpression initExpression)
        {
            var existingBindings = initExpression.Bindings.ToList();
            var modifiedProperty = initExpression.NewExpression.Type.GetProperty(nameof(ITrackModifiedDate.ModifiedDate));

            // it will be `some.ModifiedDate = currentDate`
            var modifiedExpression = Expression.Bind(
                modifiedProperty,
                Expression.Constant(DateTime.Now, typeof(DateTime))
                );

            existingBindings.Add(modifiedExpression);

            // and then we just generate new MemberInit expression but with additional property assigment
            return base.Visit(Expression.MemberInit(initExpression.NewExpression, existingBindings));
        }

        return base.Visit(node);
    }
}


public class SomeContext: DbContext
{
    public SomeContext()
        : base("Data Source=.;Initial Catalog=TestDb;Integrated Security=SSPI;")
    {
        Database.SetInitializer(new CreateDatabaseIfNotExists<SomeContext>());
    }

    public DbSet<Customer> Customers { get; set; }
}

public class Customer: ITrackModifiedDate
{
    public int ID { get; set; }
    public string Email { get; set; }
    public DateTime ModifiedDate { get; set; }
}

public interface ITrackModifiedDate
{
    DateTime ModifiedDate { get; set; }
}

Необходимая часть - это класс TrackModifiedDateVisitor, который пересекает выражение updateFactory и когда находит MemberInitExpression и обновляет его.Изначально у него есть список присвоений свойств, и мы генерируем новый для ModifiedDate и создаем новый MemberInitExpression с существующими назначениями плюс сгенерированный.

В результате после выполнения кода посетителя - updateFactory будет иметь

c => new Customer() {Email = "4242", ModifiedDate = 5/16/2019 23:19:00}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...