How can I create a dynamic select expression in LINQ? - c#

What I currently have looks a bit like this:
if(userLikesBananas)
{
return from fruit in basket
select new Fruit
{
AteBanana = Bowl.Any(b => b.OwnedBy == user && b.Contains(fruit) && fruit.Type == FruitType.Banana),
...
...
//lots of properties
...
}
}
else
{
return from fruit in basket
select new Fruit
{
AteBanana = Bowl.Any(b => b.Contains(fruit)),
...
...
//lots of properties
...
}
}
Admittedly the example makes absolutely no sense, but the principle is that I want to change the conditions of a properties selection based on arbitrary criteria. Right now the select statements are repeated.
Now the time has come that I need to add anoter dependent criteria. I don't want to have 4 different cases where the property conditions are slightly different.
What I want to do, is something like this:
Func<Fruit, bool> fruitFunc = f => false;
if(userLikesBananas)
{
fruitFunc = f => Bowl.Any(b => b.OwnedBy == user && b.Contains(f) && f.Type == FruitType.Banana);
}
else
{
fruitFunc = f => Bowl.Any(b => b.Contains(f));
}
return from fruit in basket
select new Fruit
{
AteBanana = fruitFunc(fruit)
...
...
//lots of properties
...
};
The trouble is that is the expression cannot be converted to sql, as it contains a dynamic invoke. I have tried wrapping the Func in an Expression, but the same problem seems to arise.
So the question is, how can I avoid the copy and paste?

...my english is not good, but I'll try to explain how to easily solve this problem :-)
Dynamic Linq is bad for type control - It's easy to use, but you can't browse the resulting object (x.Name, x.Surname, etc.)
There is (a litle noob-like) trick to solve this problem (I'm using it and its working fain):
Create enum with attributes of your object you're selecting from.
public enum MyAtrs{ID, FirstName, Surname}
create Dictionary<MyAtrs,bool> (and fill it) for conditions (set true if you want to get this property)
public Dictionary Dic = new Dictionary();
Dic.Add(MyAtrs.ID,true);
Dic.Add(MyAtrs.Firstname,false);
Dic.Add(MyAtrs.Surname,true);
Built your query:
var query = DBContext.MyDBTable.Where(predicate).Select(e=>new {
ID = Dic[MyAtrs.ID] ? e.dbID:0,
Firstname = Dic[MyAtrs.Firstname] ? e.dbFirstname:null,
Surname = Dic[MyAtrs.Surname] ? e.dbSurname:null,
});
In this case, there will be all 3 columns in the SQL Select statement, but it's just a few bytes (who matters...). SQL Server gives you back all 3 columns but (in this case) Firstname will be empty (something like {ID=123, Firstname=, Surname="Jobs"}).
It's not brilliant but it's easy way how to built "dynamic" select expression without loosing type control :)

I can suggest writing the Func in a different way (maybe something like this):
fruitFunc = f => Bowl.Any(b => ((f.Type == FruitType.Banana && b.OwnedBy == user && userLikesBananas) || !userLikesBananas) && b.Contains(f));
I haven't tested if it works but this may be a way to write the function to cover more cases (not only 2). The approach is somehow similar to boolean algebra as I remember...
Cheers...

Related

passing linq anonymous result to iqueryable object for another query

I have two tables (tbPerson and tbDataLog) where I need to return Id from one table (tbPerson) after checking certain conditions on both. After this, this result should be passed to another query. My first query returns the Id (primary key of a table) successfully and I need to pass these ids to another query so that it return me data based upon these Id. I also has an IQueryable type base object to check certain conditions to fetch data.
IQueryable<tbPerson> dataset
and I cannot changes this from Iqueryable to other as it will break other part of the code)
My first linq statement:
public static IQueryable<LogResults> GetResultsForYes()
{
Databasename ents = new Databasename();
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new LogResults { _LogID = f.Id }).OrderBy(x => x._LogID);
return ids;
}
public class LogResults
{
public int _LogID { get; set; }
}
I access my result something like this where I can see in debugger all the Ids.
IQueryable<LogResults> log = GetResultsForYes();
Problem comes, when I tried to get records from tbPerson based upon these returned Id.
dataset=log.where(x=>x._LogID != 0);
I get this error:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Linq.IQueryable'. An explicit conversion exists(are you missing a cast)?
Any suggestions or some other good approach is welcome.
I love this thing about stackoverflow. when we write questions we force our brain to think more deeply and after 30 mins of posting this question, I solved it in a simple way. Sometimes we overcomplicated things!
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new { f.Id }).ToArray();
var allId = ids.Select(x => x.Id).ToArray();
dataset = dataset.Where(x => allId.Contains(x.Id));
#ankit_sharma : I have not tested yours but will give a try and come back to you. Thanks for giving time and effort.
IQueryable<tbPerson> dataset=log.where(x=>x._LogID != 0);
The result of log.where(x=>x._LogID != 0) is an IQueryable<LogResults>, and you are trying to assign this result to dataset of type IQueryable<tbPerson>, two diferent types.
EDIT:
I see you make a join to get the tbPerson ids, and then you do a second query to get the persons. You could get the persons in the first join.
I just modify your code:
IQueryable<tbPerson> persons = from person in ents.tbPerson
join g in ents.tbDataLog
on person.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select person;

How to create a custom store expression for my linq queries

Let me first explain what I'm trying to accomplish.
I'm working with a C# ASP.NET MVC 5 project using Entity Framework to communicate with a SQL Server database. Most of the queries utilizes linq for its queries. In various places on the frontend site I'm displaying lists of records and need to provide the means of searching these records via a search bar. The initial idea right now is to allow the user to enter a search phrase with keywords being separated by spaces, and those keywords are used to match any combination of fields in the records of a table.
For example, say my search is "John Doe" against a user table. Consider these being the records in this table:
uFirstName uLastName
---------- ----------
Johnny Doe
John Doe
Jane Doe
The first two records should be returned.
Here's an example method I would call to return the results I expect:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from u in dbcontext.Users
where
(
(terms == "") ||
(termlist.Any(_s => u.uLastName.Contains(_s))) ||
(termlist.Any(_s => u.uFirstName.Contains(_s)))
)
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel> ( u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName } );
}
}
In my project I'm utilizing this search bar in various places being used to search against a variety of tables that obviously have different fields. What I would like to do is create a helper method that allows me to pass in the "terms" string and have it matched against a list of field values within the linq statement generically. Here's an example pseudo method that shows what I would like to change the above method to:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var linqList = (from u in dbcontext.Users
where SearchTermMatch(terms, new List<string>() { u.uLastName, u.uFirstName }) == true
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
}
}
And this is what the helper method would look like:
public static bool SearchTermMatch(string terms, List<string> fieldvalues)
{
if (terms == "") return true;
else
{
var termlist = terms.Split(' ').ToList();
var foundlist = new List<bool>();
foreach (string value in fieldvalues)
foundlist.Add(termlist.Any(s => value.Contains(s)));
return foundlist.Any(f => f == true);
}
}
Even though this compiles fine, at runtime it produces the following error:
LINQ to Entities does not recognize the method 'Boolean SearchTermMatch(System.String, System.Collections.Generic.List`1[System.String])' method, and this method cannot be translated into a store expression.
From all my searching on how to get this working, it's clear I need to utilize Expressions, but I can't for the life of me understand how those work. What I do understand is that Entity Framework wants to convert the linq statements into a query that SQL can understand, and my helper method isn't equipped to do so.
Ultimately what I want to accomplish is to build a helper method that I can later expand upon with more advanced searching techniques. I figure if I start simple with a search on all relevant fields based on a keyword split, I can later add more complexity that I would only have to do to this helper method and all my search bars will benefit from those advancements.
So I guess what I'm looking for is your help on how I can create this helper method that I can use throughout my various linq statements in my project.
Ok, I found a solution to my question. It's not completely ideal, but it gets the job done.
Let me first give reference to the sources I'm using for my solution. I first referred to this answer as the starting point:
https://stackoverflow.com/a/27993416/4566281
This answer referred to a source that I ended up using in my project. If you're using Visual Studio, you can find the package in NuGet, just search for "neinlinq", or get it from this GitHub repository:
https://github.com/axelheer/nein-linq
The only reason I don't consider this my ideal solution is that I was hoping to stick completely to the libraries in .NET / MVC. There's nothing wrong with using a 3rd party library, and in this case, it got the job done for me. But I was hoping to accomplish this as native as possible, and within reason.
So on to my code solution, as I hope this will help someone else in some capacity.
My "helper" function(s) ended up being this (don't forget to include "using NeinLinq;")
[InjectLambda]
public static bool SearchTermMatch(List<string> termlist, List<string> fieldvalues)
{
throw new NotImplementedException();
}
public static Expression<Func<List<string>, List<string>, bool>> SearchTermMatch()
{
return (t,f) =>
(
(t.Count() == 0) ||
(t.Count(_t => f.Any(_f => _f.Contains(_t)) || _t == "") == t.Count())
);
}
And, my linq statement ended up being the following:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from u in dbcontext.Users
where SearchTermMatch(termlist, new List<string>() { u.uLastName, u.uFirstName })
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
}
}
I also didn't like that I have to construct the "termlist" before the linq statement in order to make the comparisons I wanted. Ideally I'd like to have the "SearchTermMatch" expression to construct the list through something similar to Split so all I had to do was pass in the string "terms", but I couldn't figure out how to accomplish that in the expression. If someone has an idea on how to do that please let me know. I could then have the flexibility to establish my own set of search rules in the expression instead of having the calling linq statement make the list.
So, to come full circle on how this accomplishes my sitution, I can now repurpose SearchTermMatch for all my search bar scenarios. Take for example this statement:
var linqList = (from p in Person
join a in Address on p.AddressID equals a.AddressID
select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });
I can now easily update it to the following to handle my search bar call:
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from p in Person
join a in Address on p.AddressID equals a.AddressID
where SearchTermMatch(termlist, new List<string>() { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode })
select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });

LINQ query to search multiple conditions in a specific order?

I have a static set of categories, and an incoming list of items that are in various categories. I want to get the first item that matches the best category, and if none is found, get the first item that matches the next-best category, etc. until I get to a default category.
I tried putting my categories in an IEnumerable and doing a Contains() on it to see if my incoming items match a category, and stopping on the first match. But I can't control the match order: if the category list is ordered by priority [best, OK, default], the first input item to match anything in the category list wins. So if my first input item matches OK, and the second input item matches Best, then I'll stop before I get the Best match.
Right now I run Where() over the item list, and if the result is null I run a second Where(), etc. I'd like to know if there's a more concise (or more LINQ-idiomatic) way to write this:
public MyVM getVM( IEnumerable<Entities.Foo> foos )
{
Entities.Foo foo = null;
MyVM myVM = null;
if ( entity.IsBar )
{
foo = foos.Where( f => f.FooCatId == FooCategories.BestCat ).FirstOrDefault();
}
// no foos are BestCat, look for OkCat
if ( foo == null )
{
foo = foos.Where( f => f.FooCatId == FooCategories.OkCat ).FirstOrDefault();
}
// no foos are OkCat, look for DefaultCat
if ( foo == null )
{
foo = foos.Where( f => f.FooCatId == FooCategories.DefaultCat ).FirstOrDefault();
}
if ( foo != null )
{
myVM = new MyVM() { Name = foo.Name };
}
return myVM;
}
public enum FooCategories
{
DefaultCat,
SomeCat,
AnotherCat,
OkCat,
BestCat,
BadCat
}
There's certainly a more concise way of doing it, in two ways:
Use the overload of FirstOrDefault which takes a predicate
Use the null-coalescing operator
I'm going to ignore your IsBar check for now, because I don't understand how that fits in... but the rest would be:
var foo = foos.FirstOrDefault(f => f.FooCatId == FooCategories.BestCat)
?? foos.FirstOrDefault(f => f.FooCatId == FooCategories.OkCat)
?? foos.FirstOrDefault(f => f.FooCatId == FooCategories.DefaultCat);
Another option would be to change your enum order so that you could just find the cat with the best category - either by using OrderByDescending(f => f.FooCatId) or by using MaxBy from MoreLINQ. You'd also then need to check that the resulting cat isn't a bad cat, etc, so it might not be much of a win - but MaxBy would at least be more efficient, by only going through the list once.

what LINQ query will return results based on partial matches against arbitrary number of an entity's fields?

consider the following code, which represents an attempt to implement partial matching. the intended result is a row for any 1 or more fields that match between the query entity and the data store.
so if you supply person.email we want a match against that, if you supply person.email and person.FirstName we should filter the results further, and so on.
var results = from p in db.Persons
where p.CrookBookUserName.Trim().Contains(person.CrookBookUserName.Trim()) ||
p.email.Trim().Contains(person.email.Trim()) ||
p.FirstName.Trim().Contains(person.FirstName.Trim()) ||
p.LastName.Trim().Contains(person.LastName.Trim()) ||
p.phone.Trim().Contains(person.phone.Trim())
select p;
return results;
unfortunately, this code always returns all rows in the db. why, and what should be the fix?
thanks in advance.
Does person have all fields populated? A p.email.Trim().Contains("") from an empty email address will return true for everything.
An extra (!String.IsNullOrEmpty(..)) && before each parameter, while verbose, will fix it if this is the problem.
Edit re comments:
Yeah, a helper method or something could help here... something like
public static bool ContainsIfNotEmptyTrimmed(this string source, string param)
{
return !String.IsNullOrEmpty(param.Trim()) && source.Trim().Contains(param.Trim());
}
where p.CrookBookUserName.ContainsIfNotEmptyTrimmed(person.CrookBookUserName) || ...
You need to check if the user is providing empty strings.
var query = db.Persons;
if (!string.IsNullOrEmpty(person.CrookBookUserName.Trim()) {
query = query.Where(p => p.CrookBookUserName.Trim().Contains(person.CrookBookUserName.Trim()));
}
if (!string.IsNullOrEmpty(person.email.Trim()) {
query = query.Where(p => p.email.Trim().Contains(person.email.Trim()));
}
// etc...
return query;

How would you refactor this LINQ code?

I've got a lot of ugly code that looks like this:
if (!string.IsNullOrEmpty(ddlFileName.SelectedItem.Text))
results = results.Where(x => x.FileName.Contains(ddlFileName.SelectedValue));
if (chkFileName.Checked)
results = results.Where(x => x.FileName == null);
if (!string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text))
results = results.Where(x => x.IpAddress.Contains(ddlIPAddress.SelectedValue));
if (chkIPAddress.Checked)
results = results.Where(x => x.IpAddress == null);
...etc.
results is an IQueryable<MyObject>.
The idea is that for each of these innumerable dropdowns and checkboxes, if the dropdown has something selected, the user wants to match that item. If the checkbox is checked, the user wants specifically those records where that field is null or an empty string. (The UI doesn't let both be selected at the same time.) This all adds to the LINQ Expression which gets executed at the end, after we've added all the conditions.
It seems like there ought to be some way to pull out an Expression<Func<MyObject, bool>> or two so that I can put the repeated parts in a method and just pass in what changes. I've done this in other places, but this set of code has me stymied. (Also, I'd like to avoid "Dynamic LINQ", because I want to keep things type-safe if possible.) Any ideas?
I'd convert it into a single Linq statement:
var results =
//get your inital results
from x in GetInitialResults()
//either we don't need to check, or the check passes
where string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
x.FileName.Contains(ddlFileName.SelectedValue)
where !chkFileName.Checked ||
string.IsNullOrEmpty(x.FileName)
where string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text) ||
x.FileName.Contains(ddlIPAddress.SelectedValue)
where !chkIPAddress.Checked ||
string.IsNullOrEmpty(x. IpAddress)
select x;
It's no shorter, but I find this logic clearer.
In that case:
//list of predicate functions to check
var conditions = new List<Predicate<MyClass>>
{
x => string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
x.FileName.Contains(ddlFileName.SelectedValue),
x => !chkFileName.Checked ||
string.IsNullOrEmpty(x.FileName),
x => string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text) ||
x.IpAddress.Contains(ddlIPAddress.SelectedValue),
x => !chkIPAddress.Checked ||
string.IsNullOrEmpty(x.IpAddress)
}
//now get results
var results =
from x in GetInitialResults()
//all the condition functions need checking against x
where conditions.All( cond => cond(x) )
select x;
I've just explicitly declared the predicate list, but these could be generated, something like:
ListBoxControl lbc;
CheckBoxControl cbc;
foreach( Control c in this.Controls)
if( (lbc = c as ListBoxControl ) != null )
conditions.Add( ... );
else if ( (cbc = c as CheckBoxControl ) != null )
conditions.Add( ... );
You would need some way to check the property of MyClass that you needed to check, and for that you'd have to use reflection.
Have you seen the LINQKit? The AsExpandable sounds like what you're after (though you may want to read the post Calling functions in LINQ queries at TomasP.NET for more depth).
Don't use LINQ if it's impacting readability. Factor out the individual tests into boolean methods which can be used as your where expression.
IQueryable<MyObject> results = ...;
results = results
.Where(TestFileNameText)
.Where(TestFileNameChecked)
.Where(TestIPAddressText)
.Where(TestIPAddressChecked);
So the the individual tests are simple methods on the class. They're even individually unit testable.
bool TestFileNameText(MyObject x)
{
return string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
x.FileName.Contains(ddlFileName.SelectedValue);
}
bool TestIPAddressChecked(MyObject x)
{
return !chkIPAddress.Checked ||
x.IpAddress == null;
}
results = results.Where(x =>
(string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) || x.FileName.Contains(ddlFileName.SelectedValue))
&& (!chkFileName.Checked || string.IsNullOrEmpty(x.FileName))
&& ...);
Neither of these answers so far is quite what I'm looking for. To give an example of what I'm aiming at (I don't regard this as a complete answer either), I took the above code and created a couple of extension methods:
static public IQueryable<Activity> AddCondition(
this IQueryable<Activity> results,
DropDownList ddl,
Expression<Func<Activity, bool>> containsCondition)
{
if (!string.IsNullOrEmpty(ddl.SelectedItem.Text))
results = results.Where(containsCondition);
return results;
}
static public IQueryable<Activity> AddCondition(
this IQueryable<Activity> results,
CheckBox chk,
Expression<Func<Activity, bool>> emptyCondition)
{
if (chk.Checked)
results = results.Where(emptyCondition);
return results;
}
This allowed me to refactor the code above into this:
results = results.AddCondition(ddlFileName, x => x.FileName.Contains(ddlFileName.SelectedValue));
results = results.AddCondition(chkFileName, x => x.FileName == null || x.FileName.Equals(string.Empty));
results = results.AddCondition(ddlIPAddress, x => x.IpAddress.Contains(ddlIPAddress.SelectedValue));
results = results.AddCondition(chkIPAddress, x => x.IpAddress == null || x.IpAddress.Equals(string.Empty));
This isn't quite as ugly, but it's still longer than I'd prefer. The pairs of lambda expressions in each set are obviously very similar, but I can't figure out a way to condense them further...at least not without resorting to dynamic LINQ, which makes me sacrifice type safety.
Any other ideas?
#Kyralessa,
You can create extension method AddCondition for predicates that accepts parameter of type Control plus lambda expression and returns combined expression. Then you can combine conditions using fluent interface and reuse your predicates. To see example of how it can be implemented see my answer on this question:
How do I compose existing Linq Expressions
I'd be wary of the solutions of the form:
// from Keith
from x in GetInitialResults()
//either we don't need to check, or the check passes
where string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
x.FileName.Contains(ddlFileName.SelectedValue)
My reasoning is variable capture. If you're immediately execute just the once you probably won't notice a difference. However, in linq, evaluation isn't immediate but happens each time iterated occurs. Delegates can capture variables and use them outside the scope you intended.
It feels like you're querying too close to the UI. Querying is a layer down, and linq isn't the way for the UI to communicate down.
You may be better off doing the following. Decouple the searching logic from the presentation - it's more flexible and reusable - fundamentals of OO.
// my search parameters encapsulate all valid ways of searching.
public class MySearchParameter
{
public string FileName { get; private set; }
public bool FindNullFileNames { get; private set; }
public void ConditionallySearchFileName(bool getNullFileNames, string fileName)
{
FindNullFileNames = getNullFileNames;
FileName = null;
// enforce either/or and disallow empty string
if(!getNullFileNames && !string.IsNullOrEmpty(fileName) )
{
FileName = fileName;
}
}
// ...
}
// search method in a business logic layer.
public IQueryable<MyClass> Search(MySearchParameter searchParameter)
{
IQueryable<MyClass> result = ...; // something to get the initial list.
// search on Filename.
if (searchParameter.FindNullFileNames)
{
result = result.Where(o => o.FileName == null);
}
else if( searchParameter.FileName != null )
{ // intermixing a different style, just to show an alternative.
result = from o in result
where o.FileName.Contains(searchParameter.FileName)
select o;
}
// search on other stuff...
return result;
}
// code in the UI ...
MySearchParameter searchParameter = new MySearchParameter();
searchParameter.ConditionallySearchFileName(chkFileNames.Checked, drpFileNames.SelectedItem.Text);
searchParameter.ConditionallySearchIPAddress(chkIPAddress.Checked, drpIPAddress.SelectedItem.Text);
IQueryable<MyClass> result = Search(searchParameter);
// inform control to display results.
searchResults.Display( result );
Yes it's more typing, but you read code around 10x more than you write it. Your UI is clearer, the search parameters class takes care of itself and ensures mutually exclusive options don't collide, and the search code is abstracted away from any UI and doesn't even care if you use Linq at all.
Since you are wanting to repeatedly reduce the original results query with innumerable filters, you can use Aggregate(), (which corresponds to reduce() in functional languages).
The filters are of predictable form, consisting of two values for every member of MyObject - according to the information I gleaned from your post. If every member to be compared is a string, which may be null, then I recommend using an extension method, which allows for null references to be associated to an extension method of its intended type.
public static class MyObjectExtensions
{
public static bool IsMatchFor(this string property, string ddlText, bool chkValue)
{
if(ddlText!=null && ddlText!="")
{
return property!=null && property.Contains(ddlText);
}
else if(chkValue==true)
{
return property==null || property=="";
}
// no filtering selected
return true;
}
}
We now need to arrange the property filters in a collection, to allow for iterating over many. They are represented as Expressions for compatibility with IQueryable.
var filters = new List<Expression<Func<MyObject,bool>>>
{
x=>x.Filename.IsMatchFor(ddlFileName.SelectedItem.Text,chkFileName.Checked),
x=>x.IPAddress.IsMatchFor(ddlIPAddress.SelectedItem.Text,chkIPAddress.Checked),
x=>x.Other.IsMatchFor(ddlOther.SelectedItem.Text,chkOther.Checked),
// ... innumerable associations
};
Now we aggregate the innumerable filters onto the initial results query:
var filteredResults = filters.Aggregate(results, (r,f) => r.Where(f));
I ran this in a console app with simulated test values, and it worked as expected. I think this at least demonstrates the principle.
One thing you might consider is simplifying your UI by eliminating the checkboxes and using an "<empty>" or "<null>" item in your drop down list instead. This would reduce the number of controls taking up space on your window, remove the need for complex "enable X only if Y is not checked" logic, and would enable a nice one-control-per-query-field.
Moving on to your result query logic, I would start by creating a simple object to represent a filter on your domain object:
interface IDomainObjectFilter {
bool ShouldInclude( DomainObject o, string target );
}
You can associate an appropriate instance of the filter with each of your UI controls, and then retrieve that when the user initiates a query:
sealed class FileNameFilter : IDomainObjectFilter {
public bool ShouldInclude( DomainObject o, string target ) {
return string.IsNullOrEmpty( target )
|| o.FileName.Contains( target );
}
}
...
ddlFileName.Tag = new FileNameFilter( );
You can then generalize your result filtering by simply enumerating your controls and executing the associated filter (thanks to hurst for the Aggregate idea):
var finalResults = ddlControls.Aggregate( initialResults, ( c, r ) => {
var filter = c.Tag as IDomainObjectFilter;
var target = c.SelectedValue;
return r.Where( o => filter.ShouldInclude( o, target ) );
} );
Since your queries are so regular, you might be able to simplify the implementation even further by using a single filter class taking a member selector:
sealed class DomainObjectFilter {
private readonly Func<DomainObject,string> memberSelector_;
public DomainObjectFilter( Func<DomainObject,string> memberSelector ) {
this.memberSelector_ = memberSelector;
}
public bool ShouldInclude( DomainObject o, string target ) {
string member = this.memberSelector_( o );
return string.IsNullOrEmpty( target )
|| member.Contains( target );
}
}
...
ddlFileName.Tag = new DomainObjectFilter( o => o.FileName );

Categories