It allows you to write queries like the following:
var query = YourDB.Products.Where("CategoryID = 2 And Price < 10").OrderBy("StockCount");What can constitute the string is a simple specific DynamicLinq language which is well documented in the documents attached to the DynamicLinq package. The heart of DynamicLinq is actually an implementation of a parser for this microlanguage which parses the given string into Expression object.
The part of the language is an ability to instantiate new objects of anonymous classes, especially useful in Select and SelectMany directives. You can do e.g.:
cars.Select("new (name, year, engine.type as engine_type)");This will construct the object containing properties: name, year and engine_type.
In standard Linq as in e.g. C#, apart from dynamic "typeless" objects, you are able to construct the query in such a way that the objects of already existing type are returned. This is, however, not possible with the DynamicLinq as provided by Microsoft.
Nevertheless, with some knowledge of how compilers (or more precisely parsers) work, some understanding of lambda expression trees in .NET and after analyzing the source code of Dynamic.cs (file containing the implementation of the DLinq), it is relatively easy to extend it with the capabilities to name the existing types to be created in new clauses.
Currently, the grammar for new expression in DynamicLinq language is as following:
new (expr1 as name1, expr2 as name2, ..., exprn as namen)where name# is the name of the property in the resultant object which will hold the value evaluated from expr#. If the expression boils down to the property getter, the as name# part can be omitted and the property in the resultant object will be exact to the name of evaluated property. We can extend the grammar in the following manner without introducing ambiguity:
new Namespace.TypeName (expr1 as name1, expr2 as name2, ..., exprn as namen)Not providing the TypeName will still denote the instantiation of an anonymous object. If we take a look at ExpressionParser class, we quickly localize the method ParseNew. This is, indeed, a method responsible for parsing the new expression. Currently, the parse method looks more or less like the following:
- Consume "new" keyword.
- Consume opening parenthesis
- Loop doing the following:
- Parse expression
- If next token is "as" consume it and the following token as an identifier
- Store the dynamic property definition using the obtained name and expression
- If the next token is comma, consume and continue; otherwise break loop.
- Consume closing parenthesis
- Synthesize and instantiate the anonymous type based on the accumulated dynamic property definitions.
- Return the expression tree node for type instantiation parametrized by the obtained type.
With the flag on, instead of points 5 and 6, we will instantiate the existing type using Type.GetType and bind the expressions values to its already present properties.
Finally, the ParseNew method will look like the following:
Expression ParseNew() { NextToken(); bool anonymous = true; Type class_type = null; if (token.id == TokenId.Identifier) { anonymous = false; StringBuilder full_type_name = new StringBuilder(GetIdentifier()); NextToken(); while (token.id == TokenId.Dot) { NextToken(); ValidateToken(TokenId.Identifier, Res.IdentifierExpected); full_type_name.Append("."); full_type_name.Append(GetIdentifier()); NextToken(); } class_type = Type.GetType(full_type_name.ToString(), false); if (class_type == null) throw ParseError(Res.TypeNotFound, full_type_name.ToString()); } ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); NextToken(); List<DynamicProperty> properties = new List<DynamicProperty>(); List<Expression> expressions = new List<Expression>(); while (true) { int exprPos = token.pos; Expression expr = ParseExpression(); string propName; if (TokenIdentifierIs("as")) { NextToken(); propName = GetIdentifier(); NextToken(); } else { MemberExpression me = expr as MemberExpression; if (me == null) throw ParseError(exprPos, Res.MissingAsClause); propName = me.Member.Name; } expressions.Add(expr); properties.Add(new DynamicProperty(propName, expr.Type)); if (token.id != TokenId.Comma) break; NextToken(); } ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); NextToken(); Type type = anonymous ? DynamicExpression.CreateClass(properties) : class_type; MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); return Expression.MemberInit(Expression.New(type), bindings); }We will also have to add comment for the introduced exception to Res class:
public const string TypeNotFound = "Type {0} not found";Now, we are able to construct queries like following:
cars.Select("new MyApp.CarInfo(name as name, year as year, engine.type as engine_type)");You can also nest news:
cars.Select("new MyApp.CarInfo(name, year, new EngineInfo(engine.type as type, engine.info as my_info) as engine_info)");Of course, you can also, for example, nest typed object inside an anonymous one:
cars.Select("new (name, year, new EngineInfo(engine.type as type, engine.info as my_info) as engine_info)");Feel free to use the modified DynamicLinq, it is uploaded to Google Code.(3)
(1) Dynamic Linq is a part of a package available here.
(2) Dynamic Linq is described thoroughly in this blog post.
(3) The full modified Dynamic.cs is available here. Note: The version from HEAD has further updates not described here.
(4) Original StackOverflow answer where I presented the changes: Dynamic LINQ: Specifying class name in new clause