linq expression for ExecuteUpdateAsync - linq

I have found ExecuteDeleteAsync and ExecuteUpdateAsync in EF Core 7 with great enthusiasm. They help to make my code much simpler and faster. There is no need to use self-made procedures for batch delete or update of 1-2 fields.
Anyway I have situations when the exact table and field of database for update should be selected in run time.
I can use the database table:
public static IQueryable<object> Set(this DbContext context, Type entity) =>
context.ClrQuery(context.ClrType(entity));
I have the method to make expression to filter rows:
public static IQueryable Where(this IQueryable source, string equalProperty, object value, [NotNull] Type EntityType)
{
PropertyInfo? property = EntityType.GetProperty(equalProperty);
if (property == null)
throw new NotImplementedException($"Type {EntityType.Name} does not contain property {equalProperty}");
ParameterExpression parameter = Expression.Parameter(EntityType, "r");
MemberExpression member = Expression.MakeMemberAccess(parameter, property);
LambdaExpression whereExpression = Expression.Lambda(Expression.Equal(member, Expression.Constant(value, property.PropertyType)), parameter);
MethodCallExpression resultExpression = WhereCall(source, whereExpression);
return source.Provider.CreateQuery(resultExpression);
}
So I can find the rows to make update using
IQueryable Source = db.Set(EntityType).Where(FieldName, FieldValue, EntityType);
I should make expression to update IQueryable ExecuteUpdateQuery = Source.ExecuteUpdateAsync(EntityType, FieldName, FieldValue);
What is the way to access to expression for SetProperty?

Try the following extensions. I have also corrected method signature:
var affected = anyQuery.ExecuteUpdate(FieldName, FieldValue);
var affected = await anyQuery.ExecuteUpdateAsync(FieldName, FieldValue, cancellationToken);
And implementation:
public static class DynamicRelationalExtensions
{
static MethodInfo UpdateMethodInfo =
typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdate));
static MethodInfo UpdateAsyncMethodInfo =
typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdateAsync));
public static int ExecuteUpdate(this IQueryable query, string fieldName, object? fieldValue)
{
var updateBody = BuildUpdateBody(query.ElementType, fieldName, fieldValue);
return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody });
}
public static Task<int> ExecuteUpdateAsync(this IQueryable query, string fieldName, object? fieldValue, CancellationToken cancellationToken = default)
{
var updateBody = BuildUpdateBody(query.ElementType, fieldName, fieldValue);
return (Task<int>)UpdateAsyncMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody, cancellationToken })!;
}
static LambdaExpression BuildUpdateBody(Type entityType, string fieldName, object? fieldValue)
{
var setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(entityType), "s");
var objParam = Expression.Parameter(entityType, "e");
var propExpression = Expression.PropertyOrField(objParam, fieldName);
var valueExpression = ValueForType(propExpression.Type, fieldValue);
// s.SetProperty(e => e.SomeField, value)
var setBody = Expression.Call(setParam, nameof(SetPropertyCalls<object>.SetProperty),
new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression);
// s => s.SetProperty(e => e.SomeField, value)
var updateBody = Expression.Lambda(setBody, setParam);
return updateBody;
}
static Expression ValueForType(Type desiredType, object? value)
{
if (value == null)
{
return Expression.Default(desiredType);
}
if (value.GetType() != desiredType)
{
value = Convert.ChangeType(value, desiredType);
}
return Expression.Constant(value);
}
}

Related

.NET Core Expression DbFunctions DiffDays

I have an extension I have written for the date filter using the DiffDays method with .NET.
In .NET Core, EF.Functions was replaced by DbFunctions, so it could not execute it.
My .NET Code
private static (ParameterExpression param, MemberExpression prop) QueryExpressions<Entity>(IQueryable<Entity> query, string property)
{
var param = Expression.Parameter(query.ElementType, "x");
MemberExpression prop;
if (property.Contains('.'))
{
string[] childProperties = property.Split('.');
prop = Expression.Property(param, childProperties[0]);
for (int i = 1; i < childProperties.Length; i++)
prop = Expression.Property(prop, childProperties[i]);
}
else
prop = Expression.Property(param, property);
return (param, prop);
}
public static IQueryable<Entity> DiffDaysLessThan<Entity>(this IQueryable<Entity> query, string property, object value)
{
var methodInfo = typeof(DbFunctions).GetMethod("DiffDays", new Type[] { typeof(DateTime?), typeof(DateTime?) });
(ParameterExpression param, MemberExpression prop) = QueryExpressions(query, property);
var left = Expression.Call(
methodInfo,
Expression.Convert(Expression.Constant(value), typeof(DateTime?)), Expression.Convert(prop, typeof(DateTime?)));
var right = Expression.Convert(Expression.Constant(0), typeof(int?));
var body = Expression.LessThanOrEqual(left, right);
return query.Where(Expression.Lambda<Func<Entity, bool>>(body, param));
}
.NET CORE
public static IQueryable<Entity> DiffDaysLessThan<Entity>(this IQueryable<Entity> query, string property, object value)
{
var methodInfo = typeof(SqlServerDbFunctionsExtensions).GetMethod("DateDiffSecond", new Type[] { typeof(DbFunctions), typeof(DateTime?), typeof(DateTime?) });
(ParameterExpression param, MemberExpression prop) = QueryExpressions(query, property);
var left = Expression.Call(
methodInfo,
Expression.Convert(Expression.Constant(value), typeof(DateTime?)), Expression.Convert(prop, typeof(DateTime?)));
var right = Expression.Convert(Expression.Constant(0), typeof(int?));
var body = Expression.LessThanOrEqual(left, right);
return query.Where(Expression.Lambda<Func<Entity, bool>>(body, param));
}
I could not equals the typeof (DbFunctions) parameter on the Expression.Call side when calling the method.
ERROR
System.ArgumentException: Incorrect number of arguments supplied for call to method 'System.Nullable1[System.Int32] DateDiffSecond(Microsoft.EntityFrameworkCore.DbFunctions, System.Nullable1[System.DateTime], System.Nullable`1[System.DateTime])' (Parameter 'method')
EF Core functions are extension methods of DbFunctions class returned by EF.Functions property. So although unused, you have to pass the first argument of type DbFunctions when calling them, e.g.
var left = Expression.Call(
methodInfo,
Expression.Constant(EF.Functions, typeof(DbFunctions)), // <--
Expression.Convert(Expression.Constant(value), typeof(DateTime?)),
Expression.Convert(prop, typeof(DateTime?)));

Mock IDocumentQuery with ability to use query expressions

I need to be able to mock IDocumentQuery, to be able to test piece of code, that queries document collection and might use predicate to filter them:
IQueryable<T> documentQuery = client
.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null)
{
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults)
{
documents.AddRange(await list.ExecuteNextAsync<T>());
}
I've used answer from https://stackoverflow.com/a/49911733/212121 to write following method:
public static IDocumentClient Create<T>(params T[] collectionDocuments)
{
var query = Substitute.For<IFakeDocumentQuery<T>>();
var provider = Substitute.For<IQueryProvider>();
provider
.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => query);
query.Provider.Returns(provider);
query.ElementType.Returns(collectionDocuments.AsQueryable().ElementType);
query.Expression.Returns(collectionDocuments.AsQueryable().Expression);
query.GetEnumerator().Returns(collectionDocuments.AsQueryable().GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(collectionDocuments));
query.HasMoreResults.Returns(true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
Which works fine as long as there's no filtering using IQueryable.Where.
My question:
Is there any way to capture predicate, that was used to create documentQuery and apply that predicate on collectionDocuments parameter?
Access the expression from the query provider so that it will be passed on to the backing collection to apply the desired filter.
Review the following
public static IDocumentClient Create<T>(params T[] collectionDocuments) {
var query = Substitute.For<IFakeDocumentQuery<T>>();
var queryable = collectionDocuments.AsQueryable();
var provider = Substitute.For<IQueryProvider>();
provider.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => {
var expression = x.Arg<Expression>();
if (expression != null) {
queryable = queryable.Provider.CreateQuery<T>(expression);
}
return query;
});
query.Provider.Returns(_ => provider);
query.ElementType.Returns(_ => queryable.ElementType);
query.Expression.Returns(_ => queryable.Expression);
query.GetEnumerator().Returns(_ => queryable.GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(query));
query.HasMoreResults.Returns(true, true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
The important part is where the expression passed to the query is used to create another query on the backing data source (the array).
Using the following example subject under test for demonstration purposes.
public class SubjectUnderTest {
private readonly IDocumentClient client;
public SubjectUnderTest(IDocumentClient client) {
this.client = client;
}
public async Task<List<T>> Query<T>(Expression<Func<T, bool>> predicate = null) {
FeedOptions options = null; //for dummy purposes only
Uri collectionUri = null; //for dummy purposes only
IQueryable<T> documentQuery = client.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null) {
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults) {
documents.AddRange(await list.ExecuteNextAsync<T>());
}
return documents;
}
}
The following sample tests when an expression is passed to the query
[TestMethod]
public async Task Should_Filter_DocumentQuery() {
//Arrange
var dataSource = Enumerable.Range(0, 3)
.Select(_ => new Document() { Key = _ }).ToArray();
var client = Create(dataSource);
var subject = new SubjectUnderTest(client);
Expression<Func<Document, bool>> predicate = _ => _.Key == 1;
var expected = dataSource.Where(predicate.Compile());
//Act
var actual = await subject.Query<Document>(predicate);
//Assert
actual.Should().BeEquivalentTo(expected);
}
public class Document {
public int Key { get; set; }
}

How to map an int to its enum description using AutoMapper during a queryable projection?

Here is the enum extension method to get its description attribute.
public static string GetDescription(this Enum enumeration)
{
if (enumeration == null)
throw new ArgumentNullException();
var value = enumeration.ToString();
var type = enumeration.GetType();
var descriptionAttribute =
(DescriptionAttribute[]) type.GetField(value).GetCustomAttributes(typeof (DescriptionAttribute), false);
return descriptionAttribute.Length > 0 ? descriptionAttribute[0].Description : value;
}
Here is the source object:
public class Account {
public int AccountId {get;set;}
public int AccountStatusId {get;set;}
}
Here is the enum:
public enum AccountStatus {
[Description("N/A")]
None,
[Description("OPEN")]
Open,
[Description("CLOSED")]
Closed,
[Description("BAD CREDIT")
Problem
}
Here is the destination object:
public class GetAccountResponse {
public int AccountId {get;set;}
public string Status {get;set;}
}
Here is my attempt to map (using the latest non-static automapper version). Remember this is during an EF queryable projection.
_config = new MapperConfiguration(cfg => cfg.CreateMap<Account, GetAccountsResponse>()
.ForMember(dest => dest.Status,
opts => opts.MapFrom(src => ((AccountStatus) src.AccountStatusId).GetDescription())));
Here is the projection where query is an IQueryable<Account>:
query.ProjectToList<GetAccountResponse>(_config);
This is the exception I get:
Can't resolve this to Queryable Expression
If you check out the signature of the MapFrom method, you'll notice that one of the overloads takes a parameter of type Expression<Func<TSource, TMember>>.
This suggests that you could write a method which builds an expression tree from ternary expressions that can convert any possible value of your enum to its appropriate string. AutoMapper would then convert this into the appropriate SQL expression via LINQ.
Here's an example which just uses the Enum names themselves: you should be able to adapt it straightforwardly to use your Descriptions:
public static class EnumerableExpressionHelper
{
public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
Expression<Func<TSource, TMember>> memberAccess, string defaultValue = "")
{
var type = typeof(TMember);
if (!type.IsEnum)
{
throw new InvalidOperationException("TMember must be an Enum type");
}
var enumNames = Enum.GetNames(type);
var enumValues = (TMember[])Enum.GetValues(type);
var inner = (Expression)Expression.Constant(defaultValue);
var parameter = memberAccess.Parameters[0];
for (int i = 0; i < enumValues.Length; i++)
{
inner = Expression.Condition(
Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
Expression.Constant(enumNames[i]),
inner);
}
var expression = Expression.Lambda<Func<TSource,String>>(inner, parameter);
return expression;
}
}
You would use it as follows:
CreateMap<Entry, EntryListItem>()
.ForMember(e => e.ReviewStatus,
c => c.MapFrom(EnumerableExpressionHelper.CreateEnumToStringExpression((Entry e) => e.ReviewStatus)))

Call custom function in EF LINQ query Where clause

Env: EF6 + Code First
I want to be able to call a custom function in the Where clause of a LINQ query
So this line:
var activeStaff = Repo.Staff.Where(s => s.EndDate == null || s.EndDate.Value > DateTime.Today);
becomes:
var activeStaff = Repo.Staff.Where(s => MyEdmFunctions.IsCurrentStaff(s));
This is what I have tried,
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new MyCustomConvention());
}
}
public class MyCustomConvention : IConceptualModelConvention<EdmModel>
{
public void Apply(EdmModel item, DbModel model)
{
var boolType = PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Boolean);
var staffType = item.EntityTypes.Single(e => e.Name == "Staff");
var payLoad = new EdmFunctionPayload
{
ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion,
IsComposable = true,
IsNiladic = false,
IsBuiltIn = false,
IsAggregate = false,
IsFromProviderManifest = true,
Parameters = new[] { FunctionParameter.Create("Staff", staffType, ParameterMode.In) },
ReturnParameters = new[] { FunctionParameter.Create("ReturnType", boolType, ParameterMode.ReturnValue) }
};
var function = EdmFunction.Create("IsCurrentStaff", "My.Core.Data", DataSpace.CSpace, payLoad, null);
item.AddItem(function);
}
}
public static class MyEdmFunctions
{
[DbFunction("My.Core.Data", "IsCurrentStaff")]
public static bool IsCurrentStaff(Staff s)
{
return s.EndDate == null || s.EndDate > DateTime.Today;
}
}
But I'm getting "Specified method is not supported." error from EntityFramework's internal CTreeGenerator class (after decompilation)
public override DbExpression Visit(NewRecordOp op, Node n)
{
throw new NotSupportedException();
}
Can someone please confirm if there is really no way to call a custom function in the where clause?
I know it's possible to create a stored procedure and map it in the model. But is there a way to write it in C#?
Thanks.
Just to answer my own question:
I'm going to follow this thread to create a AndAlso expression to solve my problem.
Extension method in where clause in linq to Entities

Dynamic Order (SQL ORDERBY) in LINQ CompiledQuery

how can I create a dynamic ORDERBY in my LINQ CompiledQuery (e.g. supply Order Field and Direction as parameters for the compiled query)?
I would do it this way, first all you really need is a way to access the property value by string on an object. You could use reflection, but its slow. So use this helper class approach which is based on the tests of http://stefan.rusek.org/Posts/LINQ-Expressions-as-Fast-Reflection-Invoke/3/
public static class LINQHelper
{
public static IComparable OrderByProperty<TClass>(TClass item,
string propertyName)
{
var t = Expression.Parameter(typeof(TClass), "t");
var prop = Expression.Property(t, propertyName);
var exp = Expression.Lambda(prop, t).Compile();
return (IComparable)exp.DynamicInvoke(item);
}
}
The in your code where you want your order by string of property name, in this example col1, you just do the following.
var myQuery = from i in Items
select i;
myQuery.OrderBy(i=>LINQHelper.OrderByProperty(i,"col1"));
Hope this helps.
I think I found it:
Check out this link. It will point you to the VS2008 code samples which contains a Dynamic Linq Query Library which contains the extension method below. This will allow you to go:
Object.OrderBy("ColumnName");
Here is the extension methods, but you may want the whole library.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}
public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "") };
ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings) {
queryExpr = Expression.Call(
typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, o.Selector.Type },
queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}

Resources