How do you create a query with AND/OR conditions in LINQ where the values to filter are known only at runtime?

Viewed 72

I am using a framework (AspNet Boilerplate) built on ASP.NET, with .NET 6 and written in C#. It's basically a template to have an MVC project without starting from scratch. The way it queries the database is by using LINQ queries and using Entity Framework, so I followed this same pattern and added a new controller, endpoints and methods.

I have a table in an SQL Server database where I want to select only some rows. To simplify the idea, let's say my table "examples" has this structure:

id name location info1 info2 info3 info4 status
1 'Example A' 'location_a' 'data_1a' 'info_1a' 'detail_1a' 'stat_1a' 0
2 'Example B' 'location_a' 'data_2a' 'info_2a' 'detail_2a' 'stat_2a' 0
3 'Example C' 'location_b' 'input_1b' 'doc_1b' 'series_1b' 'file_1b' 1
4 'Example D' 'location_c' 'report_1c' 'info_1c' 'file_1c' 'other_1c' 0

(The contents aren't important for the example).

One of the views of my application has a form that allows the user to filter the data to get from the database. Some fields, like entry_date and status in the example, apply to all rows regardless of the location. However, it is possible that the user might want to only display rows where the location is 'location_a' and info1 contains a '2', together with rows where the location is 'location_c' and info4 contains the substring 'file'. The user specifies these values through text inputs.

I want to create a query that would be equivalent to something like

SELECT * FROM examples WHERE status = 0 AND ((location = 'location_a' AND info1 LIKE '*2*') OR (location = 'location_c' AND info4 LIKE '*file*' AND info3 = 'data_2c') OR (... etc.))

I managed to get all the filters into an object with this structure when the form is submitted:

public class ExampleFiltersDto {
    public string name { get; set; }
    public string location { get; set; }
    public string info1 { get; set; }
    public string info2 { get; set; }
    public string info3 { get; set; }
    public string info4 { get; set; }
    public int? status { get; set; }
    public Dictionary<string, Dictionary<int, string>> locationsInfos { get; set; }
}

That last Dictionary represents the "info" filters based on the location column, like this:

{
    "location_a": {
        1: "2",  //info1 for location_a
        2: null, //info2 for location_a
        3: null, //info3 for location_a
        4: null  //info4 for location_a
    },
    "location_b": {
        1: "someFilter",   //info1 for location_b
        2: "otherFilter",  //info2 for location_b
        3: null,           //info3 for location_b
        4: "anotherFilter" //info4 for location_b
    },
    "location_c": {
        1: null,      //info1 for location_c
        2: null,      //info2 for location_c
        3: "data_2c", //info3 for location_c
        4: "file"     //info4 for location_c
    }
}

My LINQ (following what the framework does for other things) goes something like this:

private IQueryable<Example> FilterQuery(ExampleFiltersDto input) {
    var query = _exampleRepository.GetAll(); //This returns an IQueryable<Example>, where Example is a class I made that can be mapped from a row in the database table
    if (input == null){
        return query;
    }
    if (input.name != null){
        query = query.Where(ex => input.name.Contains(ex.name));
    }
    if (input.status != null){
        query = query.Where(ex => input.status == ex.status);
    }

    //...
    //I'm stuck here, building the query
    //...
    //As an example, the following causes an error at runtime:
    query = query.Where(ex => locationsInfos.Keys.Contains(ex.location) && ex.info1.Contains(locationsInfos[ex.location][1]));
    //Also, no, I haven't forgotten about the possible null values, but I'd like to get this working first

    return query;
}

So I basically need to take my dictionary and convert it into statements for the query. The main problems I have right now are that

  1. I don't know if I can split the query with logical OR statements. Every time i add a Where() clause, it's equivalent to an AND. I need the OR to group "info" filters according to the "location" key.
  2. I only know the list of locations and the contents of the filters at runtime, so I can't hardcode values of filters or locations. If I try to extract data from the dictionary, I get a runtime error saying the expression could not be translated
  3. Whenever I try to add more elaborate code when building the query, LINQ complains about not being able to convert it into an expression tree.

So, sorry if the question is a bit too long, but there's no way something like this has not been done before. There has to be a way that I'm not seeing. I'm also not experienced with LINQ or Entity Framework (also not that much with .NET in general). Any ideas or suggestions?

Thanks!

0 Answers
Related