Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#

Runtime Configuration of Where Condition LINQ for Objects

Rate me:
Please Sign up or sign in to vote.
3.59/5 (3 votes)
6 Feb 2012CPOL3 min read 25.4K   118   6   13
Allow runtime alteration of where conditions for LINQ for Objects queries in response to user input

Update April 2015

There's a much better solution to the problem of flexible where conditions in LINQ queries than the one outlined in this article. Have a look here:

Introduction

A problem I've encountered when using LINQ for Objects is creating queries that can deal easily with varying numbers of parameters obtained from a search dialogue. This short article outlines a partial solution to the problem.

Background

The solution is based on the simple replacement of a hard coded condition with a function. A basic LINQ query looks much as below:

C#
var query = from TestClass t in TestData
where t.Alpha == 10
select {t.Name}

This is ideal if all you want to do is test the Alpha property of TestClass objects. We can get a bit more flexibility using one or more Lambda functions and referencing the function that meets our needs.

C#
Func<TestClass, bool> filter = delegate(TestClass t)
                                     { return (t.Alpha >= 10 && t.Gamma >300); };

var query = from TestClass t in TestData
where (filter)
select {t.Name};

However, each function we define still hard codes the name and number of properties tested. This is not what I wanted.

Using the Code

There are only three classes.

325787/ClassDiagrams.png

Class Comment
Condition<T> Defines a where condition instance with zero or more Condition.Test definitions for one or more named properties for an object of type T.
Condition.Test Associates a string (text) or numeric test with a named property.
Comparison Provides the methods to apply the tests defined for an instance of class [Condition]

Class: [Condition]

Allows the programmer to group together a number of property tests either as an AND group or as an OR group. This collection of tests can be added to or completely re-initialised without affecting the LINQ query. The query can be re-run with different configurations of the Condition objects that were referenced in the query's definition.

Property Type Comment
Logic enumeration: TestLogic Determines how the tests within a condition are applied. AND or OR
Test List<Condition.Test> The tests to be applied

 

 

Method Returns Comment
C#
Evaluate<T> (T Instance)
bool Evaluates all defined Condition.Tests in the Condition instance using the default TestLogic. Called from within the LINQ object query.
C#
Evaluate<T> (T Instance, TestLogic)
bool Evaluates all defined Condition.Tests using the specified TestLogic. Called from within the LINQ object query.

 

 

Class: [Condition.Test]

This allows the programmer to associate a property name with a test appropriate to the property's type and a value to test against.

Property Type Comment
Compare object - enumeration
  • Comparison.Number
  • Comparison.Text
The test to be applied to the property name: >, = < etc.
CompareTo numeric object or string The numeric or string value to test the property against. Numeric values are all cast to double when tested.

Class: [Comparison]

Uses reflection and some simple switch{} statements to carry out a test defined by a Condition.Test or an instance of an object.

Method Returns Comment
C#
Test<T>
			(T Instance,
			string propertyName,
			Comparison.Text comparison,
			string compareTo)
bool Applies a string test to a named property of an instance.
C#
Test<T>
			(T Instance,
			string propertyName,
			Comparison.Number comparison,
			object compareTo)
bool Applies a numeric test to a named property of an instance.

Setting up a Condition

Although the example has the property names and test values hard coded, I hope it is apparent that the tests added to each condition can be defined in response to user input taken from a search dialogue or the result of some other change in program state.

Tests can either be applied as an OR group or as an AND group. If not specified, the default behaviour is AND.

C#
// Create a condition containing multiple tests that apply to an object
// of type TestClass
// Tests within the condition will be applied using logical OR.
Condition<TestClass> whereCondition = new Condition<TestClass>() {Logic = TestLogic.Or} ;

whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Alpha",
                                                       Compare = Comparison.Number.GreaterEqual,
                                                       CompareTo = 10});
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Gamma",
                                                       Compare = Comparison.Number.Greater,
                                                       CompareTo = 300});
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Name",
                                                       Compare = Comparison.Text.Contains,
                                                       CompareTo = "D"});

Define the Query...

C#
var query = from TestClass t in TestClass.testdata()
where (whereCondition.Evaluate<TestClass>(t))
select new {t.Name};

...and Run It

C#
// Run the query with the where Condition in its initial state.
foreach (var t in query)
Console.WriteLine(t.Name);

Having separated the where logic setup from the query definition, we can now re-run the query as many times as we like altering the where condition as the fancy takes us...

Swapping to logical AND...

C#
whereCondition.Logic = TestLogic.And;
foreach (var t in query)
  Console.WriteLine(t.Name);

Completely Changing the Condition...

C#
whereCondition.Tests.Clear();
whereCondition.Logic = TestLogic.Or;
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Alpha",
                                                       Compare = Comparison.Number.GreaterEqual,
                                                       CompareTo = 100});
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Beta",
                                                       Compare = Comparison.Number.LessEqual,
                                                       CompareTo = 20});

foreach (var t in query)
  Console.WriteLine(t.Name);

Or have no tests at all...

C#
whereCondition.Tests.Clear();

foreach (var t in query)
  Console.WriteLine(t.Name);

Combining Multiple Conditions

C#
// Set up  (t.Alpha >= 100 || t.Beta <= 20) && t.Gamma >= 30

whereCondition.Logic = TestLogic.Or;
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Alpha",
                                                       Compare = Comparison.Number.GreaterEqual,
                                                       CompareTo = 100});
whereCondition.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Beta",
                                                       Compare = Comparison.Number.LessEqual,
                                                       CompareTo = 20});

// ..define an additional condition.
Condition<TestClass> supplementary = new Condition<TestClass>();
supplementary.Tests.Add(new Condition<TestClass>.Test() {PropertyName="Gamma",
                                                       Compare = Comparison.Number.GreaterEqual,
                                                       CompareTo = 30});


// ...and redefine the query to use both conditions.

query = from TestClass t in TestClass.testdata()
      where (whereCondition.Evaluate<TestClass>(t) &&
             supplementary.Evaluate<TestClass>(t))
      select new {t.Name};

foreach (var t in query)
  Console.WriteLine(t.Name);

Pros & Cons

Pros

  • Where condition definition independent of query definition
  • We open up the possibility of persisting and retrieving where condition definitions

Cons

  • Quite a bit more typing required to set up where conditions.
  • As written only suitable for properties representing string or numeric types. Date comparisons might be nice.
  • Code not so transparent and therefore may be more difficult to maintain.
  • There _must_ be a performance penalty.
  • There's probably a more elegant solution out there, I just haven't come across it. Yet.

History

  • February 2012 - First (and probably final) cut

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
Nothing interesting to report.

Comments and Discussions

 
QuestionOverengineered? Pin
Andreas Gieriet18-Apr-15 21:00
professionalAndreas Gieriet18-Apr-15 21:00 
AnswerRe: Overengineered? Pin
cigwork19-Apr-15 8:42
cigwork19-Apr-15 8:42 
SuggestionThere is a much better way! Pin
abdurahman ibn hattab8-Nov-14 7:29
abdurahman ibn hattab8-Nov-14 7:29 
GeneralEvaluating dataobjectproperties values as input at runtime Pin
faiznasi17-Jul-12 17:27
faiznasi17-Jul-12 17:27 
QuestionOr you could just use this Pin
Sacha Barber6-Feb-12 9:55
Sacha Barber6-Feb-12 9:55 
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx.

Only know cos we built our own query langauge which can be serialized and converted back to lambdas, wish I had this link then.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

AnswerRe: Or you could just use this Pin
cigwork6-Feb-12 20:34
cigwork6-Feb-12 20:34 
QuestionMissing .ZIP file Pin
10der6-Feb-12 7:32
10der6-Feb-12 7:32 
AnswerRe: Missing .ZIP file Pin
Peni7-Feb-12 2:13
Peni7-Feb-12 2:13 
QuestionMissing image? Pin
Ravi Bhavnani6-Feb-12 6:45
professionalRavi Bhavnani6-Feb-12 6:45 
AnswerRe: Missing image? Pin
DaveAuld6-Feb-12 7:02
professionalDaveAuld6-Feb-12 7:02 
QuestionThank you Pin
Sean Ewington6-Feb-12 6:04
staffSean Ewington6-Feb-12 6:04 
AnswerRe: Thank you Pin
DaveAuld6-Feb-12 7:00
professionalDaveAuld6-Feb-12 7:00 
GeneralRe: Thank you Pin
Sean Ewington6-Feb-12 7:02
staffSean Ewington6-Feb-12 7:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.