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?
UPD
EF Core 10 (with .net-10) introduces a breaking change:
ExecuteUpdateAsyncnow accepts a regular, non-expression lambda
Which allows to dynamically construct the column setters (based on conditions) out of the box (though should break previous approach):
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged)
{
s.SetProperty(b => b.Name, "foo");
}
});
Pre EF Core 10
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 to perform "conditional" batch update is to perform some expression trees manipulation:
// helper method to combine set expressions
static 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);
}
// empty set expression
Expression<Func<SetPropertyCalls<Author>, SetPropertyCalls<Author>>> set =
calls => calls;
// conditionally append set for Property1
if (entity.Property1 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property1, entity.Property1));
}
// conditionally append set for Property2
if (entity.Property2 is not null)
{
set = AppendSetProperty(set,
s => s.SetProperty(e => e.Property2, entity.Property2));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With