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

时间:2015-09-11 15:48:15

标签: c# linq anonymous-types dynamic-linq

这是一个多方面的问题,所以让我深入研究代码,并在底部进行一些解释。

示例示例数据:

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关键字创建哪个对象。此外,我必须知道要选择/投射哪些字段。我怎么能动态地这样做?如何将匿名类型投影为强类型?

e.g。我以为我可以做这样的事情来使用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(例如,客户端创建用于过滤数据的下拉值)。

3 个答案:

答案 0 :(得分:3)

  

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

好吧,匿名类型 是一种强类型 - 你只是在编译时不知道该类型的名称(因此&#34;匿名& #34)。当然,您可以投影到不同的类型,并使用AutoMapper等工具将匿名类型映射到其他类型,但仍然必须在编译时知道这些字段

可能能够执行以下操作:

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

但不清楚这是否正是您正在寻找的(并且您仍然需要在编译时知道&#34;分组&#34;字段)。< / p>

  

我该如何动态执行此操作?

你是什么意思&#34;动态&#34;?你的意思是有一些东西可以根据目的地类型自动设置属性吗?

  

有人可以帮助我让这个工作吗?

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

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

答案 1 :(得分:1)

回答#2:真的没有动态linq这样的东西;但是,有一些第三方库可以做到这一点。同样内置于.Net有2个选项。第一,有能力执行动态SQL(这是一个坏主意)。第二个选项是Expression Trees。这可能适用于问题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

答案 2 :(得分:0)

通过对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);
}