Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally SetProperty with Entity Framework Core ExecuteUpdate?

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?

like image 440
Hanyou Hottie Avatar asked Mar 06 '26 19:03

Hanyou Hottie


1 Answers

UPD

EF Core 10 (with .net-10) introduces a breaking change:

ExecuteUpdateAsync now 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));
}
like image 63
Guru Stron Avatar answered Mar 09 '26 08:03

Guru Stron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!