LINQ/Lambda 表达式:加入列表并使用给定公式查找数据的平均计数

Posted

技术标签:

【中文标题】LINQ/Lambda 表达式:加入列表并使用给定公式查找数据的平均计数【英文标题】:LINQ/Lambda expression: Join lists and find the average counts of data with the given formula 【发布时间】:2021-01-02 15:20:28 【问题描述】:

我有 500 多条 EventData 记录的列表。 EventData 的模型如下所示

public class EventData

    public int preEventId  get; set; 
    public int empNum  get; set; 
    public int EventId  get; set; 
    public DateTime CreateDate  get; set; 
    public string UserId  get; set; 

这里的 EventId 可以是 1、2、3、4 和 5(枚举)。从这个列表中,我想过滤一个月中所有星期的记录,然后计算平均值。注意:weekList 是当月星期一的列表。

(EventId = 1 的记录数)/(EventId = 2 或 5 和 EventId = 1 的 empNum 的记录数(CreateDate 5 天前或更新))

我所做的是:-

int calledCount = lstEventData?.Where(e => e.EventId == 1 &&
                     e.CreateDate >= weekList.ToList()[week] &&
                     e.CreateDate <= weekList.ToList()[week].AddDays(4)).Count() ?? 0;
                     

int totalCount = (lstEventData?
                    .Where(e => (e.EventCd == 2 || e.EventCd == 5) &&
                                 e.CreateDate >= weekList.ToList()[week] &&
                                 e.CreateDate <= weekList.ToList()[week].AddDays(4))
                    .GroupBy(e => e.empNum)
                    .Select(x => x.First())
                    .Count()) ?? 0;
                    
 int avgCalls = Convert.ToDecimal(calledCount) / Convert.ToDecimal(totalCount);

相同的示例 SQL 结构:

SELECT * FROM CalledList c  INNER JOIN
        Events e 
        ON (c.empNum = e.empNum AND c.EventId IN(2,5) 
        AND c.CreateDate > e.CreateDate - 5
        AND c.EventId = 1

我怎样才能实现我的实际目标?

【问题讨论】:

你很可能不需要调用 ToList() (它不是已经是一个列表了吗?),但绝对不应该这样做,因为它在每次迭代中都会发生lstEventData。如果确实需要调用 ToList(),请在 linq 查询之前执行一次并引用新变量。更好的是,由于 week 没有改变,只需将 weekList[week] 设置为变量即可。 你能更详细地解释你在计算什么吗?您有似乎相互矛盾的信息(例如,“和empNum'sEventId = 1”是什么意思?你怎么能是一个月的所有星期和CreateDate 5 天或更晚? 另外,你的代码有什么问题? 【参考方案1】:

根据我从您的代码和解释中可以推断出的信息,我认为这就是您所追求的:

// first, to reduce parsing your weekList EVERY time, get a reference to your start/end dates
  var wkStart = weekList.ToList()[week];

  // This may not quite work since if your weekstart is Monday midnight, 
  // adding 4 days is Friday midnight, so 12:01 AM Friday won't fall into your range.
  // I'd recommend doing AddDays(5), and using less than (<), not less than or equal (<=)
  var wkEnd = wkStart.AddDays(5); 
  
  // using consts to avoid "magic numbers"
  const int CALLED_LIST = 1;
  const int OTHER_EVENTS = 2;

  // Then get all of the events in that period that match the EventIds you want, 
  // just so your followup queries are against a smaller set
  var events = 
    lstEventData?
    .Where(e => (
    (e.EventId == 1 && e.CreateDate >= wkStart && e.CreateDate <= wkEnd) // event ID 1 within date range
                    || e.EventId == 2 || e.EventId == 5 // or event ID 2 or 5, ingoring date range for the moment
                    ))
    // Create a lookup to split by event ID to have two separate lists to join below    
    .ToLookup(e => e.EventId == 1 ? CALLED_LIST : OTHER_EVENTS);    
    
  var calledList = events[CALLED_LIST].ToList();
  var otherEvents = events[OTHER_EVENTS].ToList();
  
  if ((calledList?.Count ?? 0) == 0)
  
    // you need to handle empty or null to
    // avoid a DivdeByZero error below.
    return;
  
  // by handling the null/empty above, you no longer need to handle it everywhere below;
  var joinedResults = 
    calledList.Join(otherEvents, 
      c => c.empNum,
      e => e.empNum,
      (c,e) => new Parent = c, Child = e)
      .Where(joined => joined.Parent.CreateDate > joined.Child.CreateDate.AddDays(-5));     
  
  // From here, you'll need to figure out what average you're trying to get. 
  // I made a note in the comments that I'm unclear what average you're trying to calculate.

【讨论】:

谢谢,但最终我想要得到的 calledCount 是给定示例 sql 的计数:SELECT * FROM CalledList c INNER JOIN Events e ON (c.empNum = e.empNum AND c.EventId IN (2,5) AND c.CreateDate > e.CreateDate - 5 AND c.EventId = 1 我看到你的 sql 这意味着你正在查询两个不同的表,但是在你的 c# 中,你正在查询同一个列表。那正确吗? CalledList 真的与 lstEventData 相同,其中 EventId == 1? 是的,CalledList 和 lstEventData 一样,其中 EventId == 1 所以我想我更接近了,但我不太确定empNum 在这里发挥作用。您的avgCalls 计数似乎并不关心empNum 您是要获得每个empNum 的平均值还是只是EventId==1EventId==2|5 的总体平均值?

以上是关于LINQ/Lambda 表达式:加入列表并使用给定公式查找数据的平均计数的主要内容,如果未能解决你的问题,请参考以下文章

linq lambda多组加入

在 LINQ Lambda 表达式中使用 GroupBy、Count 和 Sum

用于从实体类及其导航属性中选择多个列的 Linq Lambda 表达式

LINQ Lambda 连接错误 - 无法从使用中推断

(Linq/Lambda) 使用 2 个 DBContext 连接 2 个或更多表

如何使用linq / lambda计算具有唯一属性的列表中的对象数?