Discussion:
[nhusers] Using dynamically built expression trees with QueryOver
Ruben Vandeginste
2018-04-26 07:24:18 UTC
Permalink
Hello,

I use QueryOver with dynamically built expression trees and hit a
limitation. I have a workaround, but I wonder if there's a better way to do
it.

Suppose you have an Employee, an Employer and both have an Address.
Employer has a bidirectional on-to-many relation with Employee. Address is
a component with properties Street, City, ZipCode, Country. A search needs
to be implemented on Address and we want to find all employees who live or
work on an address specified by some of the properties of address.

So, we will get something like:

Employee employee = null;
Employer employer = null;
var result =
Session
.QueryOver(() => employee)
.JoinAlias(() => employee.Employer, () => employer)
.Where(() => employee.Address.ZipCode == givenZipCode || employer.
Address.ZipCode == givenZipCode)


The part inside the "where" is dependent on the specified search criteria,
it could also be something like this:

var result =
Session
.QueryOver(() => employee)
.JoinAlias(() => employee.Employer, () => employer)
.Where(() => (employee.Address.ZipCode == givenZipCode && employee.
Address.Country == givenCountry)
|| (employer.Address.ZipCode == givenZipCode &&
employer.Address.Country == givenCountry))


Since there are 4 available search fields on address, that gives us 16
possible combinations (specified or not specified).

So, I wrote helper code that generates an expression tree specifically for
the given conditions, so that we get something like this:

var result =
Session
.QueryOver(() => employee)
.JoinAlias(() => employee.Employer, () => employer)
.Where(generatedExpressionTree)


The generated expression tree is a LambdaExpression with a signature
Func<bool>. The problem is that I somehow need to reference the variables
"employee" and "employer". I initially did that using a
ParameterExpression, but the problem is that those get lost: the resulting
criteria is equivalent with the following:

var result =
Session
.QueryOver(() => employee)
.JoinAlias(() => employee.Employer, () => employer)
.Where(e => e.Address.ZipCode == givenZipCode || e.Address.ZipCode ==
givenZipCode)

In this example, the query would still execute, but would not be correct.
If I would rename the Address property on Employer to WorkAddress, the
translation to criteria would fail.

So, after debugging, I noticed that if I don't use the generated expression
tree (as in the first example), the C# compiler generates some kind of
context class with properties "employee" and "employer". It creates a
Constant expression for an instance of this class, and uses
MemberExpression to access the properties "employee" and "employer". This
gets translated correctly by the QueryOver ExpressionProcessor and
preserves the references (aliases) to employee and employer.

Knowing this, I can manually create a "context" class such as
EmployeeEmployerContext with 2 properties "employee" and "employer". I
create a dummy instance of this class, and add this in a
ConstantExpression. I use MemberExpressions to access the properties
"employee" and "employer". So, I'm basically constructing an expression
tree that is identical to the one that would be generated by the C#
compiler. When doing this, the QueryOver expression is translated
correctly. It works, but is convoluted because of the context class that
must be created.

Are there better alternatives to get the references to previously defined
aliases correct? Or a better approach to create the query (using QueryOver)?

Kind regards,
Ruben
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nhusers+***@googlegroups.com.
To post to this group, send email to ***@googlegroups.com.
Visit this group at https://groups.google.com/group/nhusers.
For more options, visit https://groups.google.com/d/optout.
Loading...