I have the following query:
IQueryable<Location> query = unitOfWork.LocationRepository.Query()
.Where(x => x.ID == "{some-id}");
I want to update the Location properties with values:
List<(Func<Location, object>, object)> parameters = new List<(Func<Location, object>, object)>
{
(x => x.Name, "New Name"), // string
(x => x.Date, DateTime.Now) // DateTime
};
How to build SetProperty dynamically from the list above?
query.ExecuteUpdate(sett => sett.SetProperty(... here??? ...));
I started with simple:
Func<Location, string> func = x => x.Name;
query.ExecuteUpdate(sett => sett.SetProperty(func, "abc123"));
But this fails and I get System.InvalidCastException: 'Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.LambdaExpression'.'
When I use lambda directly:
// This works
query.ExecuteUpdate(sett => sett.SetProperty(x => x.Name, "abc123"));
So how to pass multiple Func<> to the SetProperty?
Update 7/7: Context added:
I am creating an extension method to improve the performance of the app. Instead of loading entity, changing single property and saving it I want to set property value and save it quickly with EF7 ExecuteUpdate(). There is some logic implemented in DbContext.SaveChanges() which updates the entity when saving (setting current user name, last updated time, ...) and I want the same for ExecuteUpdate(). I expect several properties to be saved in the same time (max. 5 to have it efficient), that's why I need a List.
public static async Task<int> ExecuteUpdateCustom<T, V>(this IQueryable<T> source,
List<(Func<T, V> propertyExpression, V value)> propertiesAndValuesSet) where T : class
{
if (source is IHasUserDetails)
{
List<(Func<T, V>, V)> parameters = new List<(Func<T, V>, V)>
{
(x => x.UpdatedBy, getCurrentUser()),
(x => x.ModifiedTime, DateTime.UtcNow)
};
// Chain
propertiesAndValuesSet.AddRange(parameters);
}
return await source.ExecuteUpdateAsync(setters => setters.SetProperty(...propertiesAndValuesSet...));
}
You can declare the list like this
List<Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>> parameters = new()
{
{ sett => sett.SetProperty(x => x.Name, "New Name") }, // string
{ sett => sett.SetProperty(x => x.Date, DateTime.Now) }, // DateTime
};
Combining them is a little tricky. you essentially need to manufacture a new Expression
for each of those, which does return firstExpression(secondExpression);
I think the following code should work:
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> CombineSetters(
IEnumerable<Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>> setters
)
{
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> expr = sett => sett;
foreach (var expr2 in setters)
{
var call = (MethodCallExpression)expr2.Body;
expr = Expression.Lambda<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>(
Expression.Call(expr.Body, call.Method, call.Arguments),
expr2.Parameters
);
}
return expr;
}
You can then do
await source.ExecuteUpdateAsync(finalExpression);