回答

收藏

使用LINQ插入/选择-解决查询中的实体构造

技术问答 技术问答 273 人阅读 | 0 人回复 | 2023-09-12

我在代码中使用它DBML自动生成的LINQ to/ ^/ z/ o9 Q' U: e- ?
SQL因此,适当选择和插入数据将是很好的。这是另一篇文章中建议的一种方法。在下面的例子中,e_activeSession是DataContext中表自动生成的表示形式:
, z" ^( ^- K7 Hvar statistics =    from record in startTimes    group record by record.startTime into g    select new e_activeSession                                                   workerId = wcopy,               startTime = g.Key.GetValueOrDefault()totalTasks = g.Count()totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault()minDwell = g.Min(o => o.record.dwellTime).GetValueOrDefault(),    percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80)))(                            g.Sum(o => Convert.ToInt16(o.record.correct80)   Convert.ToInt16(o.record.wrong80))            };               percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80))) /                            g.Sum(o => Convert.ToInt16(o.record.correct80)   Convert.ToInt16(o.record.wrong80))            };因此,我尝试了以下方法:
0 o! y# h' N7 ]* m8 Z3 J, V% j; Qvar groups =    from record in startTimes    group record by record.startTime    into g    select g;var statistics = groups.ToList().Select(    g => new e_activeSession                                              workerId = wcopy,                startTime = g.Key.GetValueOrDefault(),       totalTasks = g.Count(),       totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),       minDwell = g.Min(o => o.record.dwellTime).GetValueOrDefault(),    percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80)))(                              g.Sum(o => Convert.ToInt16(o.record.correct80)   Convert.ToInt16(o.record.wrong80))                               percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80))) /                             g.Sum(o => Convert.ToInt16(o.record.correct80)   Convert.ToInt16(o.record.wrong80))             });但是,这种方法ToList效率似乎很低,只会让我的代码长时间坐在那里。有更好的方法吗?
4 R# H8 l  t- f2 u: s& W# e                                                               
& q+ p" f8 Q0 v/ x7 V* A    解决方案:                                                               
$ Y# t, M$ F6 X, l. h! Q                                                                AsEnumerable()``ToList()介绍处理linq-to-( A% Z" S, ~+ x1 t
object它会做同样的事情,但它不会浪费时间和内存来存储所有这些对象。相反,当你遍历它时,它会一次创建一个对象。
+ }' h& f6 ?  M. }. h. {6 ]4 D应通常使用AsEnumerable()将操作从另一个源移动到内存的方法,而不是ToList()除非你真的想要一个列表(例如,如果你想多次击中相同的数据,它将被用作缓存)。& K/ E* X( \9 X- s8 ]3 J
到目前为止,我们有:9 j+ P% s' w/ v# v; J0 M; S9 r/ T& _
var statistics = (  from record in startTimes  group record by record.startTime  into g  select g;  ).AsEnumerable().Select(    g => new e_activeSession                                                                                                                                                                                                                                          workerId = wcopy,     startTime = g.Key.GetValueOrDefault(),       totalTasks = g.Count(),       totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),       /* ... */     };但还有一个更大的问题。你也应该小心groupby。当与聚合法一起使用时,通常是可以的,但否则最终可能会成为许多数据库调用(一次获取不同的键值,然后每个值一次)。# Q/ V% O4 z7 X6 Z6 I' z+ F
考虑到以上内容(我省略了不提及每一列的内容)。如果不使用AsEnumerable()(或不使用ToList()你拥有的),因为wcopy可能根本不在查询范围内(我看不到它的定义位置),是第一个生成的SQL将是(如果允许),类似:: D, v9 o. o# F/ Z0 q  ~
select startTime,count(id),max(timeInSession),/* ... */from tasksgroup by startTime数据库应该处理得很有效(如果没有,请检查索引并在生成的查询中操作数据库引擎优化顾问)。
) U6 j4 e* o# j4 s# H' O1 Q然而,在内存中分组时,它很可能首先执行:2 K. C& x# `* X6 _! M; r2 f
select distinct startTime from tasks接着
8 Y; n2 U. i( r" w, ?% Eselect timeInSession,/* ... */from taskswhere startTime = @p0对于startTime发现的每一个不同点都被用作传递@p0.无论其他代码的效率如何,都会很快造成灾难性的后果。6 B- s# d  F+ K% |6 B& }# b& f
我们有两个选择。哪个最好根据情况而异,所以我给两者,尽管这里的第二个最有效。
0 c) w, _* s. ?% `有时候,我们最好的办法就是加载所有相关行并在内存中分组:# ~8 _" o1 p7 d
var statistics =  from record in startTimes.AsEnumerable()  group record by record.startTime  into g  select new e_activeSession {     workerId = wcopy,   startTime = g.Key.GetValueOrDefault(),   totalTasks = g.Count(),   totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),   /* ... */  };我们可选择我们关心的列,我们可能会使它更有效率(如果上述内容仍然使用表中的每一列,则无关紧要)
8 Z. F) K& {" V8 I6 P6 Nvar statistics =  from record in  from dbRec in startTimes    select new {dbRec.startTime,dbRec.timeInSession,/*...*/}).AsEnumerable()    group record by record.startTime    into g    select new e_activeSession                                                                                                                                                                                                                                          workerId = wcopy,     startTime = g.Key.GetValueOrDefault(),       totalTasks = g.Count(),       totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),       /* ... */    };我认为这不是最好的情况。我会用它来列举组,然后列出每个组。如果你想汇总每个组,而不是列出它们,最好把汇总保留在数据库中。数据库擅长这一点,它将大大减少通过网络发送的总数。在这种情况下,我能想到的最好方法是强迫一个新对象,而不是镜像它的物理类型,但它不能被识别为物理类型。你可以为此创建一种类型(如果你想做多个变体,它是非常有用的),否则匿名类型:2 r" T% `2 o. C. C
var statistics = (  from record in startTimes  group record by record.startTime  into g  select new{    startTime = g.Key.GetValueOrDefault(),   totalTasks = g.Count(),   totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),   /* ... */  }).AsEnumerable().Select(    d => new e_activeSession                                                                                                                                                                                                                                          workerId = wcopy,     startTime = d.startTime,     totalTasks = d.totalTasks,     * ... */   };显而易见的缺点是冗长冗长。然而,它将使操作在db在不浪费时间和内存的情况下内存ToList(),不会像e_activeSession像往常一样反复击中db,并将创建的内容从linq2sql拖入linq2objects因此,应允许这样做。
  w/ `0 ?  y' _- {  l顺便说一句,.NET该协议以大写字母开始。没有技术原因,但这意味着你将匹配更多人的代码,包括BCL以及您使用的其他库的代码)。, P! S2 w) V+ o
编辑:顺便说一句;我刚看到你的其他问题。请注意,在某种程度上,AsEnumerable()这是导致这个问题确切原因的变体。清楚了,你会对不同的事情有所不同linq查询提供程序之间的界限令人困惑。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则