0

I have been searching and I have not seen an example like this.

I want to query a Model table to retrieve all Models from German manufacturers.

My data structure is Model Table has a Navigation Property called "Manufacturer" and an FK called "IdManufacturer"

A Manufacturer Has a Navigation Property called "Origin" and an FK called IdOrigin.

I would like to have a query that includes a where clause something like this:

(m => m.Manufacturer.Origin.Name == "German");

I would like this to be dynamically created at run time. Not only the lookup value might change, but the next time even the fields may change to, for example: (m.Type.Name == "SUV");

Finally, there will not necessarily be a UI associated with this request, it may be generated in code.

Please don't get too hung up on the business of it, I am hoping this made up example will be simple to understand.

Any suggestions on how to handle this would be greatly appreciated. Any general thoughts on performance would be created.

Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
Prv
  • 137
  • 10
  • 1
    What you have tried and where do you stuck? SO is full of such answers. – Svyatoslav Danyliv Nov 03 '22 at 18:50
  • Take a look on [this](https://stackoverflow.com/questions/74274206/c-sharp-ef-core-reflection-order) and [this](https://stackoverflow.com/questions/65849281/order-by-dynamic-parameter/65850085#65850085) – Magnetron Nov 03 '22 at 18:51
  • 1
    Consider whether you really need to build the expression tree dynamically, or if simply building the `IQueryable` dynamically would be sufficient, as in `if (request.Origin is not null) query = query.Where(m.Manufacturer.Origin.Name == request.Origin);` Dynamically building expression trees requires that you trust the input *completely* or put limits on what can be accessed. – madreflection Nov 03 '22 at 18:57
  • 1
    Thank you both for your quick responses. This is the code I am working with so far. It is not the ultimate solution, but it is designed to educate me a bit on how this should work: IList modelCollection; query = query.Where("Manufacturer.Origin.Name=@0", "German"); //Doesn't Work query = query.Where(a => a.Manufacturer.Origin.Name == "German"); //Doesnt Work modelCollection= await query.ToListAsync(); And, by doesn't work, provides 0 results when the data is out there. This is a time where I know this and examples have to be very common, just not finding them. – Prv Nov 03 '22 at 19:09

2 Answers2

2

My solution:
I have a model RouteList, in this model I created Expression:

public static Expression<Func<RouteList, bool>> FilterByDateOrShiftCode(RoutesFilterViewModel filter)
{
    Expression<Func<RouteList, bool>> result = rl => true;

    if (string.IsNullOrEmpty(filter.ShiftCode))
    {
        result = rl => rl.RouteListDate.Date >= filter.From.Date && rl.RouteListDate.Date <= filter.To.Date && rl.User.Id == filter.User.Id;
    }
    else
    {
        result = rl => rl.ShiftCode == filter.ShiftCode && rl.User.Id == filter.User.Id;
    }  

Place where I use it:

model.RouteLists = await _context.RouteLists
    .Include(rl => rl.Status)
    .Include(rl => rl.User)
    .Include(rl => rl.RouteListRows).ThenInclude(rlr => rlr.Address)
    .Where(RouteList.FilterByDateOrShiftCode(filter))
    .ToListAsync();

Speed of executing grow in several times.

  • Thank you for your help. A lot of this is new to me and I am reviewing it right now. – Prv Nov 04 '22 at 19:54
0

Try the following simple extension method. Note that this realization will work only with sting properties.

Usage:

query = query.Where("Manufacturer.Origin.Name", "German");

Realization:

public static class QueryableExtensions
{
    public static IQueryable<T> Where<T>(this IQueryable<T> query, string propPath, string value)
    {
        var param = Expression.Parameter(typeof(T), "e");

        // e.Prop1.Prop2 == value
        var body = Expression.Equal(MakePropPath(param, propPath), Expression.Constant(value));
        // e => e.Prop1.Prop2 == value
        var predicateLambda = Expression.Lambda<Func<T, bool>>(body, param);

        return query.Where(predicateLambda);
    }

    static Expression MakePropPath(Expression objExpression, string path)
    {
        return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
    }
}
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
  • Thank you for your help. A lot of this is new to me and I am reviewing it right now. – Prv Nov 04 '22 at 19:54
  • I am working with this suggestion, please know that this is my first extension. I am getting a compile error for each "" saying that "Type or Namespace T could not be found". I attempted changing each to "" and is compiling, however I am getting a run-time 500 error with this message: "No generic method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. '" – Prv Nov 04 '22 at 20:54
  • I am sure this is simple, just not what I have done so far. Any guidance is appreciated. – Prv Nov 04 '22 at 21:01
  • After days of overlooking something that was so obvious, I think I found the problem -- something that I just couldn't see. However, I do appreciate the suggestions. I am very likely going to incorporate these into my design. Once I have confirmed that this works, I will answer my question. – Prv Nov 04 '22 at 21:42
  • Corrected answer. Missed generic argument. Written from memory. – Svyatoslav Danyliv Nov 04 '22 at 22:00
  • Svyatoslav Danyliv: Thank you for your follow up. This seems to be working and is beyond helpful. – Prv Nov 05 '22 at 15:16
  • Svyatoslav Danyliv: As I said, this has been helpful. I am looking to make a change and am requesting any suggestions you might have, including even if you think this is a good idea. The change is to send a list of PropertyPath/Values and to have the code convert that into a "and" or and "or". Thanks again. – Prv Nov 10 '22 at 17:19