如何拦截和修改批量更新?
How to intercept and modify bulk updates?
在我的代码中的几个地方,我正在使用方便的花花公子 Z.EntityFramework.Plus
扩展程序进行批量更新,例如
await db.Foos
.Where(f => f.SomeCondition)
.UpdateAsync(f => new Foo { Field1 = "bar", Field2 = f.Field2 + 1 });
将更新所有 Foo
记录,其中 SomeCondition
为真,将 Field1
设置为 "bar" 并且 Field2
将递增 1。
现在出现了一个新要求,其中一些表(但不是全部)正在跟踪 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
,以及其他已更新的字段。
怎么做?
更新: 我不反对更改我的扩展名,以便它只接受 ITrackModifiedDate
类型的 T
,如果有帮助的话,即
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);
}
我用下面的代码让它工作:
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
class 遍历 updateFactory
表达式,当它找到 MemberInitExpression
并更新它时。最初它有 属性 分配列表,我们为 ModifiedDate
生成一个新分配,并使用现有分配加上生成的分配创建新的 MemberInitExpression
。
执行访问者代码后的结果 - updateFactory
会
c => new Customer() {Email = "4242", ModifiedDate = 5/16/2019 23:19:00}
在我的代码中的几个地方,我正在使用方便的花花公子 Z.EntityFramework.Plus
扩展程序进行批量更新,例如
await db.Foos
.Where(f => f.SomeCondition)
.UpdateAsync(f => new Foo { Field1 = "bar", Field2 = f.Field2 + 1 });
将更新所有 Foo
记录,其中 SomeCondition
为真,将 Field1
设置为 "bar" 并且 Field2
将递增 1。
现在出现了一个新要求,其中一些表(但不是全部)正在跟踪 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
,以及其他已更新的字段。
怎么做?
更新: 我不反对更改我的扩展名,以便它只接受 ITrackModifiedDate
类型的 T
,如果有帮助的话,即
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);
}
我用下面的代码让它工作:
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
class 遍历 updateFactory
表达式,当它找到 MemberInitExpression
并更新它时。最初它有 属性 分配列表,我们为 ModifiedDate
生成一个新分配,并使用现有分配加上生成的分配创建新的 MemberInitExpression
。
执行访问者代码后的结果 - updateFactory
会
c => new Customer() {Email = "4242", ModifiedDate = 5/16/2019 23:19:00}