Manipulate Expression Tree in DtoGenerator
I’m writing a simple Dto generator, and today I found a challenging problem. I supported dto composition like this:
I have a CustomerDto3 that have only CustomerId and ContactName properties, then I want to autogenerate a OrderTestDto that have a Customers property of type CustomerDto3. The syntax on My T4 generator is this one.
|
|
I’ve simplified dto creation, and supported generation of each dto in a different file. The main problem supporting this scenario is how to write the assembler of the container class. I’ve started generating this code.
|
|
It works perfectly, after all since all the Dto are generated by the same generator, when I have to generate code that transforms an original Orders object into an OrdersTestDto, I know that the creation of CustomerDto3 can be delegated to the assembler of CustomerDto3 object. This is good but now this code wont work.
|
|
If you read my older article, you find that one the most useful feature of dto generator, is the possibility to use expressions to make a projection in the database, but with previous generated Expression, Entity Framework give me this error
|
|
This is obvious, because when EF translate the ExpressionTree into SQL code, it finds a call to user function, and he cannot know how to proceed. This seems to me a really bad limitation, so I decided to fix it. The problem is that the expression should be generated this way.
|
|
With such an expression the EF provider is able to do the projection, but now a big problem arise. I’m working with T4 code generation, when the generator of OrdersTestDto has to generate the Expression, he does not know how the CustomerDto3 was generated, so he cannot generate such an expression. I really want to avoid the need to find a way to make different generators to share data.
After a brief thinking I realize that I already have the right expression built in CustomerDto3, so the only stuff I need is to compose the two expression, but this can be more complex than you can think. After lot of experiments, I came to this little trick.
|
|
The trick works in this way, I generate the ExpressionSelector in the usual way, with the call to FromOriginal that does not work in EF LINQ query, but in the static constructor I call a ParseSelector function that rewrite the expression tree making it works with LINQ query :D. Here is the full code of the DtoExpressionVisitor().
|
|
This is quite complicated code, and it works by replacing part of the original expression tree thus composing two expression tree into one. All initial conditions are needed to identify nodes that need correction, I need to correct all MemberBinding node that are MethodCallExpression, because those ones are the ones calling the FromOriginal function from the other dto. When I find a node to change, I otbain with reflection the original expression selector (a lambdaexpression), where the body is the one used to initialize the Dto. Now I have to regenerate a similar expression using the parameter of the original lambda. Basically the problem is the following, the OrdersTestDto expressionSelector has a parameter named obj of type orders, but the ExpressionSelector of CustomersDto3 has one parameter named obj but of type Customers. This means that I need to regenerate the whole ExpressionTree part, changing how parameters works.
The first step is to regenerate all MemberBinding expression part, with the original parameter (line 34-38) and storing them into a collection (line 39). The operation is the following, I need a MemberExpression that extract from the parameter the object needed to generate the Dto. In the above example accessing the Customers property of Orders to gets a Customer object. Then another MemberExpression to take the corresponding field from the Customers object, and finally a MemberBinding.
Then I need to recreate the BindingExpression (line 41-43) and returning in place of the original one. All the code is based on the original Tree Visitor by microsoft. With this quite complex trick I’m able to change the expression at runtime, thus keeping the generator simple.
alk.