查询动态数据

时间:2018-11-01 15:57:23

标签: c# linq dynamic

简而言之,我知道问题出在哪里,就是这个...

Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type

...我也知道默认情况下这是不可能的。

我的问题(见下文)在这里,我如何才能做到这一点(即使我必须动态调用例如Roslyn编译器以生成一个全新的具体程序集)?

有什么办法可以使以下查询在C#中成为可能?

所以假设我有以下示例数据...

var data = @"
{
    ""$type"":""System.Dynamic.ExpandoObject, System.Core"",
    ""Items"":[
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":1, ""BuyerRef"":1, ""SupplierRef"":2, ""FaceValue"":1.34 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":1, ""BuyerRef"":1, ""SupplierRef"":2, ""FaceValue"":2.12 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":2, ""BuyerRef"":1, ""SupplierRef"":3, ""FaceValue"":100.0 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":3, ""BuyerRef"":1, ""SupplierRef"":2, ""FaceValue"":1.0 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":4, ""BuyerRef"":3, ""SupplierRef"":2, ""FaceValue"":1.0 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":5, ""BuyerRef"":4, ""SupplierRef"":1, ""FaceValue"":1.0 },
        { ""$type"":""System.Dynamic.ExpandoObject, System.Core"", ""Ref"":5, ""BuyerRef"":4, ""SupplierRef"":1, ""FaceValue"":1.0 }
    ],
    ""Companies"":[
        { ""CompanyId"":1, ""CompanyName"":""Sample Company 1"" },
        { ""CompanyId"":2, ""CompanyName"":""Sample company 2"" },
        { ""CompanyId"":3, ""CompanyName"":""ACME"" },
        { ""CompanyId"":4, ""CompanyName"":""HSBC Bank UK"" },
        { ""CompanyId"":5, ""CompanyName"":""Basic Buyer UK Ltd"" },
        { ""CompanyId"":6, ""CompanyName"":""Test Global US-CA"" },
        { ""CompanyId"":7, ""CompanyName"":""Test Global US-TX"" },
        { ""CompanyId"":8, ""CompanyName"":""Test Global US-NH"" },
        { ""CompanyId"":9, ""CompanyName"":""Test Global UK"" },
        { ""CompanyId"":10, ""CompanyName"":""Test Global FR"" }
    ]
}
";

然后我使用Newtonsofts JsonConvert解析...

object source = JsonConvert.DeserializeObject<ExpandoObject>(data);

现在我想做类似...

var result = source.Items.Select(item => new
{
    Ref = item.Ref,
    FaceValue = item.FaceValue,
    BuyerId = item.BuyerRef,
    Buyer = Companies.Where(company => item.BuyerRef == company.CompanyId).Select(company => company).FirstOrDefault(),
    SupplierId = item.SupplierRef,
    Supplier = Companies.Where(company => item.SupplierRef == company.Ref).Select(company => company).FirstOrDefault()

})
.ToArray()
.GroupBy(i => new { i.Ref, i.Buyer, i.Supplier })
.Select(group => new
{
    Ref = group.Key.Ref,
    BuyerId = group.Key.Buyer.Ref,
    Buyer = group.Key.Buyer,
    SupplierId = group.Key.Supplier.Ref,
    Supplier = group.Key.Supplier,
    Lines = group.Select(i => new { Ref = i.Ref, FaceValue = i.FaceValue }).ToArray(),
    FaceValue = group.Sum(i => i.FaceValue)
})
.ToArray();

...当然,问题在于数据是完全动态的,而LINQ不能用于动态对象。

可以做的假设...

  1. 数据可以是任何数据结构,上面仅是说明我的问题的示例。
  2. 查询已经过验证和检查,并且一定会与我要执行此操作时给出的数据相对应,因此我很高兴避免编译器级检查(如果可能)。
  3. 我的结果被认为是一样动态的,这取决于使用代码来完成所需的工作。

这在C#中完全可行吗?

1 个答案:

答案 0 :(得分:2)

好的,我已经可以处理您的样本数据,但是我不得不更改大量代码:

首先,您需要告诉编译器将source.Itemssource.Companies视为IEnumerable<dynamic>值。只需将它们提取到局部变量并使用隐式转换就足够了:

IEnumerable<dynamic> items = source.Items;
IEnumerable<dynamic> companies = source.Companies;

接下来,由于您假设公司条目具有Ref属性,因此您的查询在各个地方都被打乱了-它们没有,只有CompanyId。这是我担心您的假设,即“查询已验证并检查,并且肯定会根据给定的数据进行工作”-如果您提供的示例不是这种情况,您将如何确保确实如此在现实中?

为简单起见,我还将您的...Where(...).Select(company => company).FirstOrDefault()部分转换为.FirstOrDefault(...)。此外,要使Sum正常工作,您实际上需要确保每个元素都具有相同的类型-强制转换对此最简单。

最后,我删除了中间的ToArray调用,该调用没有什么特别的用处,并使用了投影初始化方法,其中匿名类型中的属性名称是您要从中提取属性的名称。 (您已经在GroupBy中进行了此操作-我要做的更多。)

将代码保留为:

dynamic source = JsonConvert.DeserializeObject<ExpandoObject>(data);
IEnumerable<dynamic> items = source.Items;
IEnumerable<dynamic> companies = source.Companies;
var result = items
    .Select(item => new
    {
        item.Ref,
        item.FaceValue,
        BuyerId = item.BuyerRef,
        Buyer = companies.FirstOrDefault(company => item.BuyerRef == company.CompanyId),
        SupplierId = item.SupplierRef,
        Supplier = companies.FirstOrDefault(company => item.SupplierRef == company.CompanyId)
    })
    .GroupBy(i => new { i.Ref, i.Buyer, i.Supplier })
    .Select(group => new
    {
        group.Key.Ref,
        BuyerId = group.Key.Buyer.CompanyId,
        group.Key.Buyer,
        SupplierId = group.Key.Supplier.CompanyId,
        group.Key.Supplier,
        Lines = group.Select(i => new { i.Ref, i.FaceValue }).ToArray(),
        FaceValue = group.Sum(i => (decimal) i.FaceValue)
    })
    .ToArray();
foreach (var item in result)
{
    Console.WriteLine(item);
}

至少可以打印出值-我不知道它们是否是您想要的值,但希望它们是...