如何将动态LINQ中的匿名类型转换为强类型

本文关键字:类型转换 强类型 动态 LINQ | 更新日期: 2023-09-27 18:12:33

这是一个有点多方面的问题,所以让我在下面深入研究代码并做一些解释。

示例数据示例:

List<Encounter> input
Id, Facility, HospitalService, Field3, Field4, Field5, ...
 1,  1,       A,               ...,    ...,    ...
 2,  2,       A,               ...,    ...,    ...
 3,  1,       B,               ...,    ...,    ...
 4,  2,       B,               ...,    ...,    ...
 5,  1,       A,               ...,    ...,    ...
 5,  2,       A,               ...,    ...,    ...

我想做的是查询我的数据对象,例如返回不同的字段,例如

distinct Facility is 1, 2
distinct HospitalService is A, B
distinct pair is 1A, 2A, 1B, 2B

然而,有一个问题是我想返回一个强类型对象,在这种情况下,与输入相同的对象,在这种情况下,Encounter对象,所有其他字段都为空或默认值,例如

List<Encounter> output, with only fields of interest populated
Id, Facility, HospitalService, Field3, Field4, Field5, ...
 0,  1,       A,               "",     "",     ""
 0,  2,       A,               "",     "",     ""
 0,  1,       B,               "",     "",     ""
 0,  2,       B,               "",     "",     ""

使用标准LINQ,我可以做到这一点,它工作。

    List<Encounter> sampleData = CreateSampleData();
    List<Encounter> rawResultFromStandardLinq =
        sampleData
            .GroupBy(e => new {e.Facility, e.HospitalService})
            .Select(e => new Encounter() { Facility = e.Key.Facility, HospitalService = e.Key.HospitalService})
            .ToList();

问题# 1:在上面的例子中,它不是动态的。我必须知道用new关键字创建哪个对象。此外,我必须知道要选择/项目哪些字段。我如何动态地做到这一点?如何将匿名类型投影为强类型?

。我想我可以这样做,使用json序列化。这是有效的,但我认为它会非常慢。

    var rawResultAsAnonymousType =
        sampleData
            .GroupBy(e => new { e.Facility, e.HospitalService })
            .Select(e => new { e.Key.Facility, e.Key.HospitalService }) 
            .ToList();
    string json = JsonConvert.SerializeObject(rawResultAsAnonymousType);
    var encountersFromJson = JsonConvert.DeserializeObject<List<Encounter>>(json);

问题# 2:下一个问题是我们希望查询是动态的。也就是说,我们想公开一个接口,让客户端查询数据以获得他们想要的任何东西。为此,我们转向了动态LINQ。

有人能帮我把这个弄起来吗?

[Update: I can now do this for multiple columns]
            var rawResultFromDynamicLinq4 =
                DynamicQueryable
                    .GroupBy(_sampleData.AsQueryable(), @"new (Facility, HospitalService)", "it")
                    .Select("new (it.Key.Facility, it.Key.HospitalService)")
                    ;
[Before, I was trying to do this]
        var rawResultFromDynamicLinq =
            sampleData
                .GroupByMany("Facility", "HospitalService")
                //.Select(.... how do I get my object back?)
            ;
一些解释:

我们为什么要这样做?这在很大程度上与技术问题无关,但如果你一定要知道,我在医疗保健行业工作,我们正在使用FHIR标准来查询数据,所以我们必须使用已定义的FHIR模型。这意味着我们不能返回一个包含特定字段的不同值的List(例如,让客户端创建下拉值来过滤数据)。

如何将动态LINQ中的匿名类型转换为强类型

如何将匿名类型投影为强类型?

嗯,匿名类型强类型——你只是在编译时不知道该类型的名称(因此"匿名")。所以,当然,你可以投射到一个不同的类型,并使用AutoMapper之类的工具将匿名类型映射到不同的类型,但是仍然必须在编译时知道字段。

可能可以这样做:

    sampleData
        .GroupBy(e => new {e.Facility, e.HospitalService})
        .Select(g => g.First())
        .ToList();

,但不清楚这是否正是您要寻找的(并且您仍然需要在编译时知道"分组"字段)。

我如何动态地做到这一点?

你说的"动态"是什么意思?你的意思是让它根据目标类型自动设置属性?

有人能帮我把这个弄起来吗?

同样,如果您只想要匹配每个分组条件的第一个项,您可以执行

var rawResultFromDynamicLinq =
    sampleData
        .GroupByMany("Facility", "HospitalService")
        .Select(g -> g.First())
    ;

回答#2:真的没有所谓的动态链接;然而,有一些第三方库可以做到这一点。同样内置于。net中的还有2个选项。首先是执行动态SQL的能力(这不是一个好主意)。第二个选择是表达式树。这可以用于问题1,但类型不能是动态的。

     // Add a using directive for System.Linq.Expressions. 
        string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
                           "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
                           "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
                           "Blue Yonder Airlines", "Trey Research", "The Phone Company",
                           "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };
        // The IQueryable data to query.
        IQueryable<String> queryableData = companies.AsQueryable<string>();
        // Compose the expression tree that represents the parameter to the predicate.
        ParameterExpression pe = Expression.Parameter(typeof(string), "company");
        // ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
        // Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
        Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
        Expression right = Expression.Constant("coho winery");
        Expression e1 = Expression.Equal(left, right);
        // Create an expression tree that represents the expression 'company.Length > 16'.
        left = Expression.Property(pe, typeof(string).GetProperty("Length"));
        right = Expression.Constant(16, typeof(int));
        Expression e2 = Expression.GreaterThan(left, right);
        // Combine the expression trees to create an expression tree that represents the 
        // expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
        Expression predicateBody = Expression.OrElse(e1, e2);
        // Create an expression tree that represents the expression 
        // 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
        MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryableData.ElementType },
            queryableData.Expression,
            Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
        // ***** End Where ***** 
        // ***** OrderBy(company => company) ***** 
        // Create an expression tree that represents the expression 
        // 'whereCallExpression.OrderBy(company => company)'
        MethodCallExpression orderByCallExpression = Expression.Call(
            typeof(Queryable),
            "OrderBy",
            new Type[] { queryableData.ElementType, queryableData.ElementType },
            whereCallExpression,
            Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
        // ***** End OrderBy ***** 
        // Create an executable query from the expression tree.
        IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);
        // Enumerate the results. 
        foreach (string company in results)
            Console.WriteLine(company);
        /*  This code produces the following output:
            Blue Yonder Airlines
            City Power & Light
            Coho Winery
            Consolidated Messenger
            Graphic Design Institute
            Humongous Insurance
            Lucerne Publishing
            Northwind Traders
            The Phone Company
            Wide World Importers
        */
https://msdn.microsoft.com/en-us/library/bb882637.aspx

通过对System.Linq.Dynamic.Library进行一些修改可以实现

查看更改后的代码:https://gist.github.com/de1e6c5e758e15cc9154.git和https://gist.github.com/d166f17cd672b696b916.git

你现在可以这样使用:

var sampleData = new List<Encounter>
{
    new Encounter {Id = "1", Language = "1", VersionId = "A"},
    new Encounter {Id = "2", Language = "2", VersionId = "A"},
    new Encounter {Id = "3", Language = "1", VersionId = "B"},
    new Encounter {Id = "4", Language = "2", VersionId = "B"},
    new Encounter {Id = "5", Language = "1", VersionId = "A"},
    new Encounter {Id = "6", Language = "2", VersionId = "A"}
};
List<Encounter> fromStandardLinq = sampleData
        .GroupBy(e => new { e.Language, e.VersionId })
        .Select(e => new Encounter { Id = "0", Language = e.Key.Language, VersionId = e.Key.VersionId })
        .ToList();
Console.WriteLine("fromStandardLinq:");
foreach (var en in fromStandardLinq)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}
var fromDynamicLinq1a = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select<Encounter>("new ('"0'" as Id, it.Key.Language, it.Key.VersionId)")
        ;
Console.WriteLine("fromDynamicLinq1a:");
foreach (Encounter en in fromDynamicLinq1a)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}
var fromDynamicLinq1b = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(new { Id = "9", Language = "9", VersionId = "9" }, "new ('"0'" as Id, it.Key.Language, it.Key.VersionId)")
        .Select(x => x)
        ;
Console.WriteLine("fromDynamicLinq1b:");
foreach (dynamic en in fromDynamicLinq1b)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}
Console.WriteLine("fromDynamicLinq2a:");
var rawResultFromDynamicLinq2a = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(typeof(Encounter), "new ('"0'" as Id,it.Key.Language, it.Key.VersionId)")
        ;
foreach (Encounter en in rawResultFromDynamicLinq2a)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}
Console.WriteLine("fromDynamicLinq2b:");
var rawResultFromDynamicLinq2b = sampleData.AsQueryable()
        .GroupBy(@"new (Language, VersionId)", "it")
        .Select(typeof(Encounter), new { Id = "9", Language = "9", VersionId = "9" }, "new ('"0'" as Id,it.Key.Language, it.Key.VersionId)")
        ;
foreach (Encounter en in rawResultFromDynamicLinq2b)
{
    Console.WriteLine("{0} {1} {2}", en.Id, en.Language, en.VersionId);
}