Dynamic LINQ: Specifying class name in new clause

邮差的信 提交于 2019-11-30 15:37:28
Krizz

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.

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 });
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!