英文:
How to conditionally SetProperty with Entity Framework Core ExecuteUpdate?
问题
在使用.ExecuteUpdate
时,我看不到如何有条件地链接.SetProperty
子句:
[HttpPatch("{id}")]
public async Task<IActionResult> UpdateEntityAsync(int id, Entity entity)
{
var entitiesUpdated = await _context.Entities
.Where(e => e.Id == id)
.ExecuteUpdateAsync(s => s
// 如何基于entity.Property1和entity.Property2有条件地链接SetProperty?
.SetProperty(e => e.Property1, entity.Property1)
.SetProperty(e => e.Property2, entity.Property2)
);
return entitiesUpdated == 1 ? NoContent() : NotFound();
}
英文:
When querying a database with EF Core, it's easy to conditionally add .Where
clauses to a query before executing the query, e.g.:
[HttpGet]
public async Task<List<Entity>> GetEntitiesAsync(string? property1, string? property2)
{
var query = _context.Entities.AsNoTracking();
if (property1 != null)
{
query = query.Where(e => e.Property1.Contains(property1));
}
if (property2 != null)
{
query = query.Where(e => e.Property2.Contains(property2));
}
return await query.ToListAsync();
}
However, when using .ExecuteUpdate
, I can't see how you would conditionally chain .SetProperty
clauses:
[HttpPatch("{id}")]
public async Task<IActionResult> UpdateEntityAsync(int id, Entity entity)
{
var entitiesUpdated = await _context.Entities
.Where(e => e.Id == id)
.ExecuteUpdateAsync(s => s
// How to conditionally chain SetProperty based on
// if entity.Property1 and entity.Property2 are null?
.SetProperty(e => e.Property1, entity.Property1)
.SetProperty(e => e.Property2, entity.Property2)
);
return entitiesUpdated == 1 ? NoContent() : NotFound();
}
You can't use if
statements inside the lambda. It needs to be a single expression that evaluates to a SetPropertyCalls<T>
. Maybe you could manually create an expression tree, but wouldn't you need to build it on top of the parameter passed into the lambda? Is there an easy way I'm not seeing?
答案1
得分: 4
SetProperty
允许传递表达式来计算值。要根据条件动态组合 SetProperty
调用,可以使用 三元条件运算符:
var entitiesUpdated = await _context.Entities
.Where(e => e.Id == id)
.ExecuteUpdateAsync(s => s
.SetProperty(e => e.Property1, e => entity.Property1 != null
? entity.Property1
: e.Property1)
.SetProperty(e => e.Property2, e => entity.Property2 != null
? entity.Property2
: e.Property2));
虽然这会生成类似于 SQL 的代码 "PropertyX" = "e"."PropertyX"
,用于源为 null
的情况。
更具挑战性但更“正确”的方法是执行一些 表达式树操作:
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> AppendSetProperty<TEntity>(
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> left,
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> right)
{
var replace = new ReplacingExpressionVisitor(right.Parameters, new []{left.Body});
var combined = replace.Visit(right.Body);
return Expression.Lambda<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>>(combined, left.Parameters);
}
Expression<Func<SetPropertyCalls<Author>, SetPropertyCalls<Author>>> set =
calls => calls;
if (entity.Property1 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property1, entity.Property1));
}
if (entity.Property2 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property2, entity.Property2));
}
英文:
SetProperty
allows passing expression to calculate the value. To dynamically combine SetProperty
calls based on condition you can use ternary conditional operator :
var entitiesUpdated = await _context.Entities
.Where(e => e.Id == id)
.ExecuteUpdateAsync(s => s
.SetProperty(e => e.Property1, e => entity.Property1 != null
? entity.Property1
: e.Property1)
.SetProperty(e => e.Property2, , e => entity.Property2 != null
? entity.Property2
: e.Property2));
Though this will generate SQL like "PropertyX" = "e"."PropertyX"
for cases when the source is null
.
A bit harder but more "correct" approach is to perform some expression trees manipulation:
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> AppendSetProperty<TEntity>(
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> left,
Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> right)
{
var replace = new ReplacingExpressionVisitor(right.Parameters, new []{left.Body});
var combined = replace.Visit(right.Body);
return Expression.Lambda<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>>(combined, left.Parameters);
}
Expression<Func<SetPropertyCalls<Author>, SetPropertyCalls<Author>>> set =
calls => calls;
if (entity.Property1 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property1, entity.Property1));
}
if (entity.Property2 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property2, entity.Property2));
}
答案2
得分: 1
这是我写的一个简单的辅助程序,它组合了表达式树并启用了这些情景,同时允许你保持相同的 API 结构,不同于 Guru Stron 的回答:
用法:
int affectedCount = await dbContext.Books
.Where(b => b.Id == someBookId)
.ExecutePatchUpdateAsync(b =>
{
if (someCondition)
b.SetProperty(b => b.Foo, "Foo");
else
b.SetProperty(b => b.Bar, "Bar");
});
实现:
public static class QueryableExtension
{
public static Task<int> ExecutePatchUpdateAsync<TSource>(
this IQueryable<TSource> source,
Action<SetPropertyBuilder<TSource>> setPropertyBuilder,
CancellationToken ct = default
)
{
var builder = new SetPropertyBuilder<TSource>();
setPropertyBuilder.Invoke(builder);
return source.ExecuteUpdateAsync(builder.SetPropertyCalls, ct);
}
public static int ExecutePatchUpdate<TSource>(
this IQueryable<TSource> source,
Action<SetPropertyBuilder<TSource>> setPropertyBuilder
)
{
var builder = new SetPropertyBuilder<TSource>();
setPropertyBuilder.Invoke(builder);
return source.ExecuteUpdate(builder.SetPropertyCalls);
}
}
public class SetPropertyBuilder<TSource>
{
public Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> SetPropertyCalls { get; private set; } = b => b;
public SetPropertyBuilder<TSource> SetProperty<TProperty>(
Expression<Func<TSource, TProperty>> propertyExpression,
TProperty value
) => SetProperty(propertyExpression, _ => value);
public SetPropertyBuilder<TSource> SetProperty<TProperty>(
Expression<Func<TSource, TProperty>> propertyExpression,
Expression<Func<TSource, TProperty>> valueExpression
)
{
SetPropertyCalls = SetPropertyCalls.Update(
body: Expression.Call(
instance: SetPropertyCalls.Body,
methodName: nameof(SetPropertyCalls<TSource>.SetProperty),
typeArguments: new[] { typeof(TProperty) },
arguments: new Expression[] {
propertyExpression,
valueExpression
}
),
parameters: SetPropertyCalls.Parameters
);
return this;
}
}
英文:
Here's a simple helper I wrote that composes the expression tree and enables these kinds of scenarios — while allowing you the same API shape, unlike Guru Stron's answer:
Usage:
int affectedCount = await dbContext.Books
.Where(b => b.Id == someBookId)
.ExecutePatchUpdateAsync(b =>
{
if (someCondition)
b.SetProperty(b => b.Foo, "Foo");
else
b.SetProperty(b => b.Bar, "Bar");
});
The implementation:
public static class QueryableExtension
{
public static Task<int> ExecutePatchUpdateAsync<TSource>(
this IQueryable<TSource> source,
Action<SetPropertyBuilder<TSource>> setPropertyBuilder,
CancellationToken ct = default
)
{
var builder = new SetPropertyBuilder<TSource>();
setPropertyBuilder.Invoke(builder);
return source.ExecuteUpdateAsync(builder.SetPropertyCalls, ct);
}
public static int ExecutePatchUpdate<TSource>(
this IQueryable<TSource> source,
Action<SetPropertyBuilder<TSource>> setPropertyBuilder,
)
{
var builder = new SetPropertyBuilder<TSource>();
setPropertyBuilder.Invoke(builder);
return source.ExecuteUpdate(builder.SetPropertyCalls);
}
}
public class SetPropertyBuilder<TSource>
{
public Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> SetPropertyCalls { get; private set; } = b => b;
public SetPropertyBuilder<TSource> SetProperty<TProperty>(
Expression<Func<TSource, TProperty>> propertyExpression,
TProperty value
) => SetProperty(propertyExpression, _ => value);
public SetPropertyBuilder<TSource> SetProperty<TProperty>(
Expression<Func<TSource, TProperty>> propertyExpression,
Expression<Func<TSource, TProperty>> valueExpression
)
{
SetPropertyCalls = SetPropertyCalls.Update(
body: Expression.Call(
instance: SetPropertyCalls.Body,
methodName: nameof(SetPropertyCalls<TSource>.SetProperty),
typeArguments: new[] { typeof(TProperty) },
arguments: new Expression[] {
propertyExpression,
valueExpression
}
),
parameters: SetPropertyCalls.Parameters
);
return this;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论