every Parameter object property that is not null, to be added to expression predicate as a condition
Asked Answered
S

2

6

I am looking for a way to dynamically build an expression, based upon the method parameters.

This is the code snippet from my service method, where I would like to build a predicate expression.

    public async Task<Account> GetCustomerAccountsAsync(Parameters params)
    {
        var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
        ...
    }

GetWhereAsync is a method from the generic repository that looks like:

    public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await Context.Set<TEntity>().Where(predicate).ToListAsync();
    }

And Parameters class:

    public class Parameters
    {
        public string CustomerId { get; set; }
        public string AccountId { get; set; }
        public string ProductId { get; set; }
        public string CurrencyId { get; set; }
    }

What I would like to implement, is that every Parameter object property that is not null, to be added to expression predicate as a condition.

For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression that would have functionality same as the following predicate:

    var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);

Appreciate any help.

Saldivar answered 10/3, 2020 at 15:51 Comment(3)
None of the properties in your Parameters class can ever be null.Furore
@Furore Sorry, those were all strings, I made them integers by mistake, trying to simplify the code I am actually using. Editing the post, making properties as strings.Saldivar
Does this answer your question? Add two expressions to create a predicate in Entity Framework Core 3 does not work (look at my answer, about combining expressions)Barta
F
5

You don't need to use Expressions to build something dynamically here. You can do something like this:

_unitOfWork.Accounts.Where(a =>
    (params.CustomerId == null || a.CustomerId == params.CustomerId) &&
    (params.AccountId == null || a.AccountId == params.AccountId) &&
    (params.ProductId == null || a.ProductId == params.ProductId) &&
    (params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);

This is how I've done queries before for a search form with multiple optional search parameters.

Furore answered 10/3, 2020 at 16:3 Comment(0)
S
4

I'm currently working a lot with Expressions so I think I can help you.

I just built a custom code for you.

The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.

The code filters string Properties and use it to create the Predicate.

public Func<Account, bool> GetPredicate(Parameters parameters)
{
    var stringProperties = typeof(Parameters)
    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
    .Where(x => x.PropertyType == typeof(string));

    var parameterExpression = Expression.Parameter(typeof(Account));

    var notNullPropertyNameToValue = new Dictionary<string, string>();

    BinaryExpression conditionExpression = null;

    foreach (var stringProperty in stringProperties)
    {
        var propertyValue = (string)stringProperty.GetValue(parameters);
        if (propertyValue != null)
        {
            var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
            var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
            var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);

            if (conditionExpression == null)
            {
                conditionExpression = propertyTestExpression;
            }
            else
            {
                conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
            }
        }
    }

    //Just return a function that includes all members if no parameter is defined.
    if (conditionExpression == null)
    {
        return (x) => true;
    }

    var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
    return lambdaExpression.Compile();
}

It returns a typed predicate that you can use in Linq for example.

See this example :

void Main()
{
    var customers = new List<Account>()
    {
        new Account()
        {
            CustomerId = "1",
        },
        new Account()
        {
            CustomerId = "2",
        }
    };
    var predicate = GetPredicate(new Parameters() { CustomerId = "1" });

    customers.Where(predicate);
}

If any help is needed, feel free to ask !

Schneider answered 10/3, 2020 at 17:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.