1

i use EF v5/6. i want to know how could i build where clause dynamically when user will try to add search filter dynamically through UI.

see the UI first enter image description here

now end user will add as many as filter through the above UI and try to search database.

i try to search google for some solution as a result i can add multiple search to my db table with EF and i found few. here is few links

https://stackoverflow.com/a/5595591/6188148

https://stackoverflow.com/a/24824290/6188148

the above guide line is tough for me to understand. so i am looking something easy.

this link bit close to my requirement https://stackoverflow.com/a/16646241/6188148 but still i am not getting idea how to customize their code to use for my scenario.

so my request anyone can help with a small sample code which help me to get the things done.

Community
  • 1
  • 1
Monojit Sarkar
  • 2,353
  • 8
  • 43
  • 94
  • With performance in mind for advanced searches like this, I would recommend using a dedicated product such as [Elasticsearch](https://www.elastic.co/), I spent days trying to do something similar inside of EF only discover how poorly it performed on bigger data. – uk2k05 Jun 21 '17 at 13:36
  • Elasticsearch can fetch data from sql server using EF.....i have no idea. – Monojit Sarkar Jun 21 '17 at 14:02

2 Answers2

6

I assume you are able to build a sub-condition from each line in your GUI. For example, the first line would be similar to

string userInputName = "john";
Expression<Func<Person, bool>> condition1 = person => person.Name.Contains(userInputName);

And can be used as

var selection = db.Persons.Where(condition1).ToList();

For multiple sub-conditions, and is just a concatenation of Where conditions:

var conditions = new List<Expression<Func<Person, bool>>>() { condition1, condition2, ... };

IQueryable<Person> query = db.Persons;
foreach (var condition in conditions)
{
    query = query.Where(condition);
}
var selection = query.ToList();

The little trickier part comes with the or conditions. Suppose you have normalized your conditions into groups of alternative and conditions (disjunctive normal form), then you get multiple groups of valid results. For the sake of simplicity, I keep it at 2 groups here, but it can be generalized the same way as the and conditions:

The or can be represented by the Union operation on sub-queries.

var conditions1 = new List<Expression<Func<Person, bool>>>() { condition1, condition2, ... };
IQueryable<Person> query1 = // see above construction logic for 'and' conditions
var conditions2 = new List<Expression<Func<Person, bool>>>() { condition5, condition6, ... };
IQueryable<Person> query2 = // see above construction logic for 'and' conditions

IQueryable<Person> query = query1.Union(query2);
var selection = query.ToList();

A few last words: consider using some established filter/search frameworks instead. This approach is probably neither the prettiest nor the fastest you can find.


As requested, a small example with some in-memory data. Note this is not 100% equivalent to Linq-to-entities. For example, string comparison will deal with upper-lower case differently and maybe not every kind of condition is allowed for SQL.

public enum TypeOfContact
{
    Unknown,
    Email
}
public class Person
{
    public string Name { get; set; }

    public DateTime Birth { get; set; }

    public TypeOfContact ContactType { get; set; }

    public string ContactValue { get; set; }
}

public class Program
{
    static void Main(string[] args)
    {
        // test data to simulate a database table
        var Persons = new List<Person>
        {
            // + All conditions met
            new Person { Name = "john doe", Birth = new DateTime(2011, 1, 1), ContactType = TypeOfContact.Email, ContactValue = "john.doe@email.com" },
            // - Not in result
            new Person { Name = "danny doe", Birth = new DateTime(2012, 1, 1), ContactType = TypeOfContact.Email, ContactValue = "johndoe@hotmail.com" },
            // + Name contains john
            new Person { Name = "john doe", Birth = new DateTime(2013, 1, 1), ContactType = TypeOfContact.Unknown, ContactValue = "" },
            // + Birth, ContactType and ContactValue correct
            new Person { Name = "justin", Birth = new DateTime(2014, 1, 1), ContactType = TypeOfContact.Email, ContactValue = "slenderman@email.com" },
            // - Not in result because Name and Birth are wrong
            new Person { Name = "jonny", Birth = new DateTime(1979, 1, 1), ContactType = TypeOfContact.Email, ContactValue = "you-know-who@email.com" },
            // - Not in result
            new Person { Name = "jenny doe", Birth = new DateTime(2016, 1, 1), ContactType = TypeOfContact.Unknown, ContactValue = "" },
        }.AsQueryable();

        // single-line-conditions
        Expression<Func<Person, bool>> c1 = p => p.Name.Contains("john");
        Expression<Func<Person, bool>> c2 = p => p.Birth.Date >= new DateTime(1980, 1, 1);
        Expression<Func<Person, bool>> c3 = p => p.ContactType == TypeOfContact.Email;
        Expression<Func<Person, bool>> c4 = p => p.ContactValue.EndsWith("@email.com");

        // DNF groups: outer list = or; inner list = and
        // c1 or (c2 and c3 and c4)
        var conditionList = new List<List<Expression<Func<Person, bool>>>>
        {
            new List<Expression<Func<Person, bool>>>
            {
                c1,
            },
            new List<Expression<Func<Person, bool>>>
            {
                c2,
                c3,
                c4,
            },
        };

        var andSubResults = new List<IQueryable<Person>>();
        foreach (var andQueries in conditionList)
        {
            var subQuery = Persons;
            foreach (var andQuery in andQueries)
            {
                subQuery = subQuery.Where(andQuery);
            }
            andSubResults.Add(subQuery);
        }
        var query = andSubResults.FirstOrDefault();
        foreach (var subResult in andSubResults.Skip(1))
        {
            query = query.Union(subResult);
        }
        var selection = query.ToList();
        // just check the result in debugger
    }
}
grek40
  • 13,113
  • 1
  • 24
  • 50
  • i would like to just a small request can you give me small but full working example on this issue if possible. thanks – Monojit Sarkar Jun 21 '17 at 13:59
  • i would like to go for condition iteration loop highlighted in your example. say this one.......`var conditions = new List>>() { condition1, condition2, ... }; IQueryable query = db.Persons; foreach (var condition in conditions) { query = query.Where(condition); } var selection = query.ToList();` – Monojit Sarkar Jun 21 '17 at 14:01
  • 2
    @MonojitSarkar a *small but full working example* would include to set up an actual database, because otherwise it's just an in-memory example which works a bit different from linq-to-entities. I feel like this would exceed the scope of an answer. I'll still write a small in-memory example. – grek40 Jun 21 '17 at 14:55
0

@grek40's answer gave me the clues I needed for this approach. I only have a few anded conditions, not sure if it works with ored conditions.

using (var theDb = new project_dbEntities())
{
    IQueryable<TheEntityClass> query = theDbContext.TheEntityClass;

    if (!showInactive)
    {
        query = query.Where(x => x.active);
    }

    if (selectedYearId != years.ALL_YEARS)
    {
        var yearToFilter = years.GetById(selectedYearId);
        query = query.Where(x => x.first_effective_date >= yearToFilter.start &&
                                 x.first_effective_date <= yearToFilter.end);
    }

    var entities = query
        .Include(nameof(TheChildEntity))
        .OrderBy(x => x.name)
        .ToList();

    return View(new IndexViewModel(entities));
}
William T. Mallard
  • 1,562
  • 2
  • 25
  • 33