0

I'm trying to filter the posts that are displayed in my application. Everything goes like expected, but I have a small issue. A user can choose the education(s) and profession(s) he follows. How can I filter based on the arrays I get? I tried something like this, but it feels ugly. If I set more arrays in my Filter class like Language[].. It wil get more messy. Can I do something easier?

public class Filter
{
    public string[] Education { get; set; }

    public string[] Profession { get; set; }
    public int PageIndex { get; set; }

}

Example request: An example

public PaginatedResults<Post> FilterPosts(Filter filter)
{

// Both Education and Profession arrays are empty, we just return all the posts
    if(filter.Profession.Any(prof => prof == null) && filter.Education.Any(study => study == null)) {
        var posts1 = _dbContext.Posts.AsEnumerable();
        return _searchService.Pagination<Post>(posts1, filter.PageIndex);
    }
    else 
    {
        // Can this be simplified? Sometimes the Education array is empty and sometimes Profession array. User can choose
        IEnumerable<Post> posts = null;
        if(filter.Profession.Any(prof => prof == null)) 
        {
            posts = _dbContext.Posts.Where(post => filter.Education.Contains(post.Education)).AsEnumerable();
        }
        else if(filter.Education.Any(study => study == null)) 
        {
            posts = _dbContext.Posts.Where(post => filter.Profession.Contains(post.Profession)).AsEnumerable();
        }
        else 
        {
            posts = _dbContext.Posts.Where(post => filter.Profession.Contains(post.Profession) && filter.Education.Contains(post.Education)).AsEnumerable();
        }
        return _searchService.Pagination<Post>(posts, filter.PageIndex);
    }
}

1 Answer 1

1

There probably quite a few ways you could approach this problem. Assuming you want to keep your approach (which I think is perfectly valid), you could try the following steps:

Leverage IQueryable

Assuming you use entity framework, I believe _dbContext.Posts implements IQueryable already. Since LINQ does not get executed immediately, we can build filtering conditions sequentially before enumerating the collection:

posts = _dbContext.Posts.Where(post => filter.Education.Contains(post.Education) && filter.Education.Contains(post.Profession)).AsEnumerable();

// since you are implementing `AND` semantics for your filters, is easy to break down into series of `.Where()` calls
posts = _dbContext.Posts.Where(post => filter.Education.Contains(post.Education))
                        .Where(post => filter.Education.Contains(post.Profession))
                        .AsEnumerable(); // this should filter Posts by Education AND Profession as well as represent the result as IEnumerable. Should be functionally identical to the first statement

Invert boolean conditions and check if filters have values

This will allow you to add a .Where filter only when it's needed:

if (filter.Profession.Any()) // if Profession has elements
{
    posts = posts.Where(post => filter.Profession.Contains(post.Profession)); // apply respective filter to posts, you may want to ensure you only compare against meaningful search terms by appplying `.Where(i => !string.IsNullOrWhiteSpace(i))` to it
}
if (filter.Education.Any()) // if Education has elements
{
    posts = posts.Where(post => filter.Education.Contains(post.Education)).AsEnumerable(); // apply respective filter to posts          
}

Then, to put it all together

public PaginatedResults<Post> FilterPosts(Filter filter)
{
    IQueryable<Post> posts = _dbContext.Posts;
    if (filter.Profession.Any()) posts = posts.Where(post => filter.Profession.Contains(post.Profession));

    if (filter.Education.Any()) posts = posts.Where(post => filter.Education.Contains(post.Education));

    return _searchService.Pagination<Post>(posts.AsEnumerable(), filter.PageIndex); 
}
Sign up to request clarification or add additional context in comments.

4 Comments

This seems right in principle and advice but I'm suspicious of the use of IEnumerable<Post> posts = _dbContext.Posts;, this can have unforeseen implications.
@AluanHaddad oh, it's a copy-paste artifact. Should probably be IQueryable or var. I'll update it when I get a chance
I really like the approach, but there is one problem which I didn't explain properly. When I call my api like: api/posts/filter?profession=ICT&education=&pageIndex=1 The education or profession can be empty. Thats why I posted the picture above. For example education is empty: filter.Education[0] becomes null and filter.Education.Any() becomes true. .Where(post => filter.Education.Contains(post.Education)) becomes 0 posts because I dont have any Education with null. How can I solve this problem?
``` if(filter.Profession.Any(prof => prof != null)) { posts = posts.Where(post => filter.Profession.Contains(post.Profession)); }``` Oh I did this and it worked. Thank you for the provided help. I now understand how to build queries without executing it immediately :)

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.