【问题标题】:change value of a member of a list of type anonymous更改匿名类型列表成员的值
【发布时间】:2021-03-06 14:17:03
【问题描述】:

我有一个匿名对象列表,每个匿名对象都有相同的成员,我通过以下方式创建此列表:

var listWorthies = balanceWorthies.Select(w => new
                        {
                            OwnerName = w.OwnerOnInquery,
                            OwnerDocDate = w.OwnerDocDate,
                        }).ToList();

现在我只想转换每个动态对象的 OwnerDocDate 成员。 像这样:

    var listWorthies = balanceWorthies.Select(w => new
                        {
                            OwnerName = w.OwnerOnInquery,
                            OwnerDocDate = ConvertDate(w.OwnerDocDate),
                        }).ToList();

给我错误

LINQ to Entities 无法识别方法 'System.String ConvertDate(System.DateTime)' 方法,并且该方法无法转换为存储表达式。

创建列表后,我也尝试了类似的方法:

                  foreach (dynamic Worthy in listWorthies)
                        {
                         DateTime OwnerDocDate_2 = ConvertDate(Worthy.OwnerDocDate);
                         Worthy.AddProperty(OwnerDocDate_2);
                        }

但它给了我这个错误:

'f__AnonymousType8' 不包含“AddProperty”的定义

更新

函数ConvertDate的输出是String,我想把OwnerDocDate的类型从datetime改成string

我该如何解决这个问题?

【问题讨论】:

  • 原始字段“OwnerDocDate”的类型是什么?您能否复制/粘贴确切的异常消息?
  • 我很确定它应该与 stackoverflow.com/questions/17441420/… 重复...我强烈建议 edit 发布帖子以显示“动态”项目的作用 - 到目前为止代码仅显示匿名类型.
  • @Aliz,我已经更新了我的问题。
  • 没有理由在foreach 中使用dynamic 和有问题的AddProperty。您需要告诉我们您使用的是什么版本的 EF:LINQ to EF 6.x / EF 2.0 / 2.1 / EF Core 3.x ?
  • @NetMage,我使用的是 EF 6.0

标签: c# list linq object dynamic


【解决方案1】:

您忘了告诉我们 balanceWorthies 是 IQueryable<...>,而不是 IEnumerable<...>

要了解为什么这很重要,您必须了解 IQueryable<...>IEnumerable<...> 之间的区别。

IEnumerable

实现IEnumerable<...> 的类的对象表示一个序列。可以获取第一个元素,一旦有了元素,就可以获取下一个元素,只要有元素。

在最低级别,这是使用 GetEnumerator() / MoveNext() / Current 方法完成的:

IEnumerable<Cusotmer> customers = ...
IEnumerator<Customer> enumerator = customers.GetEnumerator();
while (enumerator.MoveNext())
{
    // There is still a Customer in the sequence:
    Customer customer = enumerator.Current;
    ProcessCustomer(customer);
}

foreach 会在内心深处这样做。

如果您查看 LINQ 方法,您会发现有两组方法:返回 IEnumerable&lt;...&gt; 的方法和其他方法。第一组不会枚举序列。我们说他们使用延迟执行或延迟执行。在这些 LINQ 方法的描述中,您会在备注部分找到该术语。

该组的连接方法并不昂贵:不执行查询,仅调整 Enumerator。

返回 IEnumerble&lt;...&gt; 以外的其他内容的 LINQ 方法将执行该序列。内部深处GetEnumerator / MoveNext / Current被调用,逐一访问源序列的元素。

可查询

实现IQueryable&lt;...&gt; 的类的对象不代表可枚举序列,它代表可能获取可枚举序列。

为此,该类拥有一个Expression 和一个Provider。表达式包含必须以某种通用格式获取的数据。 Provider 知道必须从哪里获取数据(通常是数据库管理系统)以及用于与该 DBMS 通信的语言(通常是 SQL)。

当您要求 IQueryable 获取枚举器时,Expression 被发送给 Provider,Provider 会将 Expression 转换为 SQL 并在 DBMS 中获取数据。获取的数据显示为IEnumerator&lt;...&gt;。调用者可以使用MoveNext() / Current 来一一访问获取到的元素。

回到你的问题

提供者不知道您自己的方法。因此它不知道如何将它们翻译成 SQL。事实上,有几种标准的 LINQ 方法不受实体框架的支持。见Supported and unsupported LINQ methods

你的编译器不知道你的 Provider 有多聪明,所以他不能抱怨。您会在运行时收到错误消息。

解决问题的最简单方法是将数据传输到本地进程,让本地进程像IEnumerable&lt;...&gt; 一样执行方法。

这是使用方法Enumerable.AsEnumerable 完成的。由于传输数据的成本很高,因此在调用 AsEnumerable 之前将传输的数据限制在最低限度是明智的。所以首先做你所有的Where, (Group-)Join等所有限制传输数据量的事情。

var listWorthies = balanceWorthies.Select(w => new
{
    OwnerName = w.OwnerOnInquery,
    OwnerDocDate = w.OwnerDocDate,
})

// OwnerDocDate has the type of balanceWorthy.OwnerDocDate
// move the data to local process, so you can ConvertDate
.AsEnumerable()

// now you can call your own methods:
.Select(fetchedItem => new
{
    OwnerName = fetchedItem.OwnderName,
    OwnerDocDate = ConvertDate(fetchedItem.OwnerDocDate),
});

数据库管理系统在选择数据方面得到了极大的优化。似乎 ConvertDate 只会将数据转换为不同的格式。所以你不会损失很多效率。

如果在其他情况下该方法会更改所选数据的数量,请尝试更改表达式以便 DBMS 可以处理它,特别是如果它在 Where 之前。如果 DBMS 必须执行您的代码,并且您无法将 LINQ 转换为您的提供程序支持的内容,则您必须编写一个更改表达式的扩展方法。如何做到这一点是另一个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-25
    相关资源
    最近更新 更多