0

I have a linq query which is as follows:

var query = _appDbContext.Persons
                .Where(p => p.Id == 123)
                .Select(lambda)
                .ToList();

I use lambda because i have a variable in my select statement. The lambda:

var wantedData = "Adress";

var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);

The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.

I tried the following, which obviously does not work.

var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);

Is there a way to do this?

6
  • Why are you not just querying like this? var addresses= _appDbContext.Persons.Where(p => p.Id == 123).Select(p => p.Address).ToList(); Commented Feb 23, 2021 at 13:03
  • @MarianSimonca Because I want my query to get either adresses or subscribtions, based on a variable. With a query like yours I would have to build two queries for this. Commented Feb 23, 2021 at 13:11
  • But you could have this query inside a method that has an Expression parameter. When calling that function you basically pass what properties you want to get trough that query Commented Feb 23, 2021 at 13:13
  • @MarianSimonca I'm new to querying with linq so I could be wrong but is that not what I'm already doing with assigning a value to var x? Commented Feb 23, 2021 at 13:16
  • Yes, it is something similar, but a bit more complicated, I am trying to put up an example for you to try Commented Feb 23, 2021 at 13:18

2 Answers 2

2

Ok, I managed to create a generic query method that takes an Expression as parameter.

EDIT: This will receive a string value and will try to match it with an existing property on your Person class.

private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
    return _dbContext.Persons
            // .Where(filter)
        .Select(selectExpression)
        .ToList();
}

public IEnumerable<object> GetData(string dataPropertyName)
{
    switch(dataPropertyName) {
         case nameof(Person.Address): return GetPersonData(p => p.Address);
         case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
         // other cases
         default: throw new InvalidArgumentException("Invalid property name");
    }
}

note that this code is just an example written on the spot and it might not work directly with copy-paste

Sign up to request clarification or add additional context in comments.

7 Comments

This works, but doesn't really solve the problem. I still have to make a function for each call. I would like to give the data I want to receive as a parameter like this: GetPersonData("Adress") Is that possible?
You can call this method with any navigation property for Person. I created two methods just to exemplify how to call GetPersonData in different scenarios
I understand, but it is not possible to this: var wantedData = "Adress" and then call the function like this: GetPersonData(p => p.wantedData)
As you can see, GetPersonData(p => p.Address) is specifying what you want to receive and this version is safer since you are not passing any magic string as parameter and if you change the name of Address property it will work as well
Well, you don't need the wantedData variable anymore because you don't need to create a lambda variable anymore
|
0

I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type. You can create an extension method with the following code:

public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
    var prop = typeof(T).GetProperties()
        .FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
    ParameterExpression pe = Expression.Parameter(typeof(T), "x");
    MemberExpression body = Expression.Property(pe, prop.Name);

    LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });

    MethodCallExpression selectCallExpression = Expression.Call(
        typeof(Queryable),
        "Select",
        new Type[] { source.ElementType, prop.PropertyType },
        source.Expression,
        lambda);
    return source.Provider.CreateQuery<TResult>(selectCallExpression);
}

That way, you can use it like:

var query = _appDbContext.Persons
                .Where(p => p.Id == 123)
                .Select<Person,string>(wantedData)
                .ToList();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.