Dynamic LINQ: Specifying class name in new clause
Asked Answered
H

2

4

With Dynamic LINQ, what changes need to be done to have fields of the given class?

For example, how can the following C# query be reproduced in DLinq:

var carsPartial = cars.Select(c => new {c.year, c.name, make = new maker() {name = c.make.name} }).ToList();

I have applied the changes mentioned in this answer https://mcmap.net/q/277132/-system-linq-dynamic-select-quot-new-quot-into-a-list-lt-t-gt-or-any-other-enumerable-collection-of-lt-t-gt to allow the return type to be the calling type rather than an anonymous type.

With the class definition is as follows (if it helps):

class car
{
    public int vid;
    public int odo;
    public int year;

    public string name;

    public maker make;
}

class maker
{
    public string name;
    public int firstYear;
}

The following doesn't work (but I think is close, but still doesn't work as I don't have the changes necessary to the dynamic linq library, which is what I need):

var carsPartial = cars.Select("new(name, year, new maker() {name = make.name})").ToList();

But it fails at the new maker() { (as expected).

I'm sure I need to change the DynamicLibrary.cs to get this working and could do with some direction on how to alter it to achieve this.

Hammack answered 8/1, 2012 at 8:17 Comment(0)
H
2

UPDATE: I have turned my answer into a little bit more extensive blog post.

I have not really ever used Dynamic Linq library, but I have taken a look at the DynamicLibrary.cs code and the change to support generating type classes provided in another stackoverflow question you provided link to in your question. Analyzing them all, it seems that the nested new-s should work out of the box in your configuration.

However, it seems your query is not the correct Dynamic Linq's language query. Note, that the query string for DLinq is not equivalent to C# and has its own grammar.

The query should read out, I believe, the following:

var carsPartial = cars.Select("new(name, year, new maker(make.name as name) as make)").ToList();

EDIT:

Rereading this stackoverflow question more carefully, I realizes, that it actually does not extend the Dynamic Linq's language with the possibility for creating new strong-typed classes. They just put the result to the class specified as a generic parameter of Select() instead of specifying it in the query string.

To obtain what you need you will need to revert their changes (get generic DLinq) and apply my changes, I have just verified to work:

Locate the ParseNew method of ExpressionParser class and change it to 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);
    }

Then, find the class Res and add the following error message:

public const string TypeNotFound = "Type {0} not found";

Et voilà, you will be able to construct queries like:

var carsPartial = cars.Select("new(name, year, (new your_namespace.maker(make.name as name)) as make)").ToList();

Make sure, you include the full type name including the whole namespace+class path.

To explain my change, it just checks if there is some identifier between new and opening parenthesis (see the added "if" at the begging). If so we parse full dot-separated class name and try to get its Type through Type.GetType instead of constructing own class in case of anonymous news.

Hazeghi answered 8/1, 2012 at 11:24 Comment(11)
It seems nested news are only half supported, it still throws exceptions because it expects a ( token after new and it also doesn't know the class maker and thinks it's a property of the car class.. but defiantly something to start with.Hammack
But have you applied the changes from https://mcmap.net/q/277132/-system-linq-dynamic-select-quot-new-quot-into-a-list-lt-t-gt-or-any-other-enumerable-collection-of-lt-t-gt properly? Because not having them will throw the exception you mentioned.Hazeghi
Well, another problem may be that it takes new - not maker - as the identifier to be a property of car class. What about trying "new(name, year, (new maker(make.name as name)))"?Hazeghi
And another thing ... it is not a direct property reference, thus we need to provide the name for the property in our anonymous object - try the query string from my updated answer.Hazeghi
I just reapplied the changes noted from the base DynamicLibrary.cs file that I have. It (still) throws '(' expected for new(name, year, make = new maker(make.name as name)) and No applicable method 'maker' exists in type 'car' for new(name, year, new(maker(make.name as name)))Hammack
Also, the '(' expected is after the new. So it seems to want new(Hammack
Have tried the simple not-nested testcase: new maker(make.name as name) to make sure recognizing classes works?Hazeghi
let us continue this discussion in chatHammack
Works like a charm, I integrated the code from the other question into my code afterwards and fixed up the defaulting of namespaces and looking across assemblies for the types. And the performance is great!Hammack
@Hammack - it would be interesting to know how you extended it further with "defaulting of namespaces".Hazeghi
I used the assumption that the if the type was not found as directly specified then it is likely to have the same namespace as that of the originating result type which is newResultType as mentioned in the other question that we both linked to. Then I expanded and checked all loaded assemblies for a match on this new type rather than just Type.GetType(full_type_name).Hammack
C
0

If I've understood you correctly you want to make a plain anonymous class that contains fields from both class car and class maker. If it's the case you can just provide new names in that class, something like the following:

var carsPartial = cars.Select(c => new { year = c.year, name = c.name, make_name = c.make.name });

Or even provide names only to conflicting fields:

var carsPartial = cars.Select(c => new { c.year, c.name, make_name = c.make.name });
Cassady answered 8/1, 2012 at 10:11 Comment(1)
Not really, what I want is do the first query ...(c => new {c.year, c.name, make = new maker() {name = c.make.name} }) in dynamic linq (ie: as a string input), I have updated the ending of the question to reflect this betterHammack

© 2022 - 2024 — McMap. All rights reserved.