【问题标题】:Filtering and Counting simultaneously in C#在 C# 中同时过滤和计数
【发布时间】:2021-08-16 18:03:05
【问题描述】:

我正在使用实体框架工作,我从我的 Get 方法中得到以下 json。

[
    {
        "id": 2,
        "name": "Coffee",
        "icon": "alarm",
        "staticEvents": [
            
            
            {
                "id": 36,
                "categoryId": 2,
                "time": "2021-12-26T12:22:46.609+10:00",
                "comment": null
            },
            {
                "id": 38,
                "categoryId": 2,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            },
            {
                "id": 40,
                "categoryId": 2,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            },
            {
                "id": 42,
                "categoryId": 2,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            },
            {
                "id": 44,
                "categoryId": 2,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            }
        ]
    },
    {
        "id": 3,
        "name": "Water",
        "icon": "alert",
        "staticEvents": [
            {
                "id": 32,
                "categoryId": 3,
                "time": "2021-05-28T14:33:47.765643+10:00",
                "comment": null
            },
            {
                "id": 37,
                "categoryId": 3,
                "time": "2021-05-20T13:00:58.123+10:00",
                "comment": null
            },
            {
                "id": 39,
                "categoryId": 3,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            },
            {
                "id": 41,
                "categoryId": 3,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            },
            {
                "id": 43,
                "categoryId": 3,
                "time": "2021-05-28T00:00:00+10:00",
                "comment": null
            }
        ]
    }
]

我需要以下格式的东西:

[
    {
        "month": June,
        "report": [

            {
                "categoryId": 2,
                "occurrences": 5,
            },

 {
                "categoryId": 3,
                "occurrences": 4,
            }

    }
]

我想这与 this thread 中的 GroupBy + Select 类似,但我无法获得子列表中的出现次数。

编辑:我正在尝试逐步运行查询,但出现错误。

  • 当我跑步时:
 var list = _context.Categories
            .SelectMany(c => c.StaticEvents )
            .GroupBy (staticEvent => new 
            {
                Year = staticEvent.Time.Year,
                Month = staticEvent.Time.Month
            })
            .ToList();

            foreach(StaticEvent i in list) {
                Console.WriteLine(i.CategoryId);
            }

我明白了

System.InvalidOperationException: Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side.

所以我用了:

var list = _context.Categories
            .SelectMany(c => c.StaticEvents )
            .AsEnumerable()
            .GroupBy (staticEvent => new 
            {
                Year = staticEvent.Time.Year,
                Month = staticEvent.Time.Month
            })
            .ToList();

然后我得到了

System.InvalidCastException: Unable to cast object of type 'System.Linq.Grouping`2[<>f__AnonymousType2`2[System.Int32,System.Int32],api.Entities.StaticEvent]' to type 'api.Entities.StaticEvent'.

【问题讨论】:

  • 那是你拥有的 json。在代表该结构的类中反序列化它,然后选择你需要的并序列化它。

标签: c# asp.net linq


【解决方案1】:

这是使用 Newtonsoft 和匿名类型解决此问题的一种方法,假设您的 json 字符串存储在名为 json 的变量中:

//Parse into array
var obj = JArray.Parse(json);

//Gather all events
var allEvents = obj.AsEnumerable().Select(t => t["staticEvents"])
    .SelectMany(t => t.Select(t1 => new { categoryId = t1["categoryId"].ToString(), time = DateTime.Parse(t1["time"].ToString()) }));

//Group events by month
var groupedByMonth = allEvents.GroupBy(r => r.time.Month, i => i.categoryId);

//Generate aggregates
var monthAggregates = groupedByMonth.Select(gbm =>
new
{
    month = gbm.Key,
    report =
    gbm.GroupBy(m => m)
    .Select(entry => new
    {
        categoryId = entry.Key, ocurrences = entry.Count()
    })
});

//Display
foreach (var monthAggregate in monthAggregates)
{
    var monthDate = new DateTime(DateTime.Now.Year, monthAggregate.month, 1);
    Console.WriteLine($"Month: {monthDate.ToString("MMMM")}");
    Console.WriteLine($"report:");
    foreach (var entry in monthAggregate.report)
    {
        Console.WriteLine($"category: {entry.categoryId}, ocurrences: {entry.ocurrences}");
    }                
}

输出:

Month: December
report:
category: 2, ocurrences: 1
Month: May
report:
category: 2, ocurrences: 4
category: 3, ocurrences: 5

我确信您可以对这段代码做很多事情以使其“更好”。它只是为了帮助您开始。

【讨论】:

  • 我更新了我的问题,解释了我在尝试针对 Entity Framework 运行建议的查询时遇到的一些错误。感谢您的宝贵时间!
【解决方案2】:

仅使用 lambda 表达式的示例

            var json = File.ReadAllText(@"C:\Temp\Arquivo.txt");
            var objs = JsonConvert.DeserializeObject<List<RequestObj>>(json);

            var resultObjs = objs
                            .SelectMany(x => x.StaticEvents.Select(y => y.Time))
                            .GroupBy(x => x.Month)
                            .Select(x => new
                            {
                                Month = x.First().ToString("MMMM"),
                                Index = x.Key
                            }) 
                            .Select(x => new ResultObj
                            {
                                Month = x.Month,
                                Report = objs
                                            .Where(y => y.StaticEvents.Where(z => z.Time.Month == x.Index).Any())
                                            .Select(y => new Report
                                            {
                                                CategoryId = y.Id,
                                                Occurrences = y.StaticEvents.Where(z => z.Time.Month == x.Index).Count()
                                            })
                                            .ToList()
                            })
                            .ToList();

【讨论】:

  • 这很好用,但不是针对 EF,我得到“无法翻译给定的 'GroupBy' 模式。在 'GroupBy' 之前调用 'AsEnumerable' 以在客户端对其进行评估。”
  • 我更新了我的问题,解释了我在尝试针对 Entity Framework 运行建议的查询时遇到的一些错误。感谢您的宝贵时间!
【解决方案3】:

将您的问题分成两个子问题:

  • 将Json读入Drinks的序列,每个Drink有零个或多个StaticEvents
  • 使用 LINQ 创建输出格式。

优势在于,如果您的输入格式是 XML、CSV 文件、数据库或互联网,您可以重复使用您的 LINQ 代码。

我假设你知道如何阅读 JSON。

class Drink
{
    public int Id {get; set;}
    public string Name {get; set;}
    public string Icon {get; set;}   // or maybe an enum?

    public ICollection<StaticEvent> StaticEvents {get; set;}
}

public StaticEvent
{
    public int Id {get; set;}
    public int CategoryId {get; set;}
    public DateTime Time {get; set;};
    public string Comment {get; set;}
}

public IEnumerable<Drink> ReadJsonDrinks() {...}

要求给定一个饮料序列,每个饮料具有零个或多个静态事件,创建一个月份的输出序列,每个月都有一个Reports 的序列。每个报告都有一个 CategoryId 和一些 Occurences。含义:这个CategoryId的StaticEvent在这个月发生了多少次。

我们将使用 SelectMany 来获取所有 StaticEvents(所有 Drinks)的一个序列。我们不会再使用饮料了。

一旦我们获得了所有静态事件的大序列,我们就可以将发生在同一个 [年、月] 组合中的静态事件分组。也就是说,Key为June 2021的组,拥有2021年6月发生的所有StaticEvent。

我知道您写了“六月”,但我认为您不想将 2020 年 6 月和 2021 年 6 月合并为一个月。如果您确实想要这个,请在​​下面的解决方案中省略提及年份。

要在June 2021 组中创建报告,我们必须再次使用 GroupBy:创建具有相同 CategoryId 的组 StaticEvent。我们使用参数 resultSelector 将每组转换成一个[CategoryId, 此categoryId的静态事件数]

对于这两个 GroupBy,我使用了具有参数 resultSelector 的 GroupBy 的重载。

因此,我们将从 SelectMany 开始,创建一个包含所有饮品的所有静态事件的大序列。

IEnumerable<Drink> drinks = ReadJsonDrinks();

var result = drinks.SelectMany(drink => drink.StaticEvents)

现在我们将发生在同一 [年、月] 组合中的静态事件分组:

.GroupBy(staticEvent => new
{
    Year = staticEvent.Time.Year,
    Month = staticEvent.Time.Month,
}

// parameter resultSelector, from every [year, month] combination, and all StaticEvents
// that have a Time with this [year, month] create one new:
(yearMonth, staticEventsWithThisYearMonth) => new
{
    ... // TODO: implement
});

现在我们想要什么:每[年月]?我们想要年份和月份,以及一系列报告。所以上面的LINQ:

(yearMonth, staticEventsWithThisYearMonth) => new
{
    Year = yearMonth.Year,
    Month = yearMonth.Month,

    // for the reports: make groups of staticEvents that have the same CategoryId:
    Reports = staticEventsWithThisYearMonth.GroupBy(staticEvent => staticEvent.CategoryId,

    // parameter resultSelector: for every CategoryId, and all staticEvents with this categoryId
    // (that were in this year/month), make one Report:
    (categoryId, staticEventsWithThisCategoryId) => new
    {
        CategoryId = categoryId,

        // for the occurrences: count the number of StaticEvents in this group:
        Occurences = staticEventsWithThisCategoryId.Count(),
    })
    .ToList(),
});

【讨论】:

  • 我更新了我的问题,解释了我在尝试针对 Entity Framework 运行建议的查询时遇到的一些错误。感谢您的宝贵时间!
猜你喜欢
  • 1970-01-01
  • 2021-09-12
  • 1970-01-01
  • 2019-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-27
相关资源
最近更新 更多