动态LINQ:在新子句中指定类名

本文关键字:子句 LINQ 新子句 动态 | 更新日期: 2023-09-27 18:19:53

使用动态LINQ,需要做哪些更改才能拥有给定类的字段?

例如,以下C#查询如何在DLinq:中重现

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

我已经应用了这个答案中提到的更改https://stackoverflow.com/a/1468357/288747以允许返回类型为调用类型,而不是匿名类型。

类定义如下(如果有帮助的话):

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;
}

以下内容不起作用(但我认为很接近,但仍然不起作用,因为我没有对动态linq库进行必要的更改,这正是我所需要的):

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

但它在new maker() {失败(正如预期的那样)。

我确信我需要更改DynamicLibrary.cs来实现这一点,并且可以就如何更改它来实现这目标做一些指导。

动态LINQ:在新子句中指定类名

UPDATE:我已经把我的答案变成了一篇更广泛的博客文章。

我从来没有真正使用过DynamicLinq库,但我已经查看了DynamicLibrary.cs代码,以及您在问题中提供链接的另一个stackoverflow问题中提供的支持生成类型类的更改。分析所有这些,似乎嵌套的new-s应该在您的配置中开箱即用。

然而,您的查询似乎不是正确的DynamicLinq语言查询。请注意,DLinq的查询字符串不等价于C#,并且有自己的语法。

我认为,查询应该读出以下内容:

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

编辑:

仔细重读这个stackoverflow问题,我意识到,它实际上并没有扩展Dynamic Linq的语言,使其有可能创建新的强类型类。他们只是将结果放在指定为Select()的泛型参数的类中,而不是在查询字符串中指定它。

为了获得你需要的东西,你需要恢复他们的更改(获得通用的DLinq)并应用我的更改,我刚刚验证了这一点:

找到ExpressionParser类的ParseNew方法,并将其更改为以下内容:

    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);
    }

然后,找到类Res并添加以下错误消息:

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

等等,你将能够构建这样的查询:

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

确保包含完整的类型名称,包括整个名称空间+类路径。

为了解释我的更改,它只是检查new和左括号之间是否有一些标识符(请参阅乞讨时添加的"if")。如果是这样,我们解析以句点分隔的类名,并尝试通过Type.GetType获得其Type,而不是在匿名new的情况下构造自己的类。

如果我理解正确,您希望创建一个包含class carclass maker字段的普通匿名类。如果是这种情况,您可以在该类中提供新名称,如下所示:

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

甚至只为冲突字段提供名称:

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