Agregowanie danych z action loga z pogrupowaniem na datę oraz typ

0

Cześć,

przechowuję w tabeli w DB informację o akcjach wykonanych przez usera, wygląda to w ten sposób (usunąłem zbędne kolumny):
screenshot-20221119223651.png
I teraz próbuję na potrzeby raportowe (do wykresu) wyciągnąć te dane pogrupowane po dacie co do dnia, a później dla danego typu w tej dacie wyciągnąć ilość wpisów.
Czyli docelowo chciałbym uzyskać taki efekt:

"data": [
    {
      "date": "2022-11-17",
      "actions": {
        "DepositoryEdit": 13,
        "DepositoryAdd": 7,
        "DepositoryAddComment": 2,
      }
    },
	    {
      "date": "2022-11-18",
      "actions": {
        "DepositoryEdit": 15,
        "DepositoryAdd": 25,
        "DepositoryAddComment": 6,
      }
    },
	    {
      "date": "2022-11-19"
      "actions": {
        "DepositoryEdit": 99,
        "DepositoryAdd": 5,
        "DepositoryAddComment": 11,
      }
    }
  ]

Udało mi się pogrupowac typie, ale nie potrafię wprowadzić tego dodatkowego elementu jakim jest podział na daty. Aktualnie otrzymuję coś takiego:

screenshot-20221119224837.png

Mój kod:

var allActions = dbx.DepositoryActionLogs.Where(z =>
                    z.Success && z.CreatedDate.Date >= dto.DateFrom && z.CreatedDate.Date <= dto.DateTo &&
                    (dto.UserGuid == null || z.UserId == userId))
                .GroupBy(x => new { x.CreatedDate.Date, Type = x.HistoryType })
                .Select(z => new
                {
                    CreatedDate = z.Key.Date,
                    z.Key.Type,
                    Count = z.Count()
                }).ToList();


dynamic expando = new ExpandoObject();
   foreach (var action in allActions)
            {
                
                DynamicallyHelpers.AddProperty(expando, action.Type.ToString(), (ulong)action.Count);
            }

Prośba o wsparcie.

EF Core + Postgres + .NET 6

1

Nie wiem jak się to przetłumaczy na SQLa, więc może być tak, że część trzeba będzie po stronie klienta zrobić, albo więcej pokombinować, ale

public class Test
{
    public Guid Id { get; set; } = Guid.NewGuid();

    public DateTime CreationDate { get; set; }

    public int HistoryType { get; set; }
}

var entries = new List<Test>
{
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 1 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 1 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = 3 },

    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = 2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = 3 },
};

var output = entries
    .GroupBy(x => x.CreationDate.Date) // tutaj ewentualnie po tym zrobić ToList i resztę tego LINQ po stronie klienta
    .Select(x => new
    {
        Date = x.Key.Date,
        Actions = x.GroupBy(x => x.HistoryType)
                    .Select(x => new { x.Key, Count = x.Count() })
                    .ToList()
    })
    .ToList();

Console.WriteLine(JsonConvert.SerializeObject(output, Formatting.Indented));

I otrzymujesz już prawie to co chciałeś

[
  {
    "Date": "2022-11-18T00:00:00+01:00",
    "Actions": [
      {
        "Key": 1,
        "Count": 2
      },
      {
        "Key": 2,
        "Count": 3
      },
      {
        "Key": 3,
        "Count": 1
      }
    ]
  },
  {
    "Date": "2022-11-15T00:00:00+01:00",
    "Actions": [
      {
        "Key": 2,
        "Count": 3
      },
      {
        "Key": 3,
        "Count": 1
      }
    ]
  }
]

I teraz tylko przemapować te Actions

var result = output.Select(x => new
{
    Data = x.Date,
    Actions = new
    {
        DepositoryEdit = x.Actions.FirstOrDefault(action => action.Key == 1)?.Count ?? 0,
        DepositoryAdd = x.Actions.FirstOrDefault(action => action.Key == 2)?.Count ?? 0,
        DepositoryAddComment = x.Actions.FirstOrDefault(action => action.Key == 3)?.Count ?? 0,
    }
})
.ToList();

I rezultat:

[
  {
    "Data": "2022-11-18T00:00:00+01:00",
    "Actions": {
      "DepositoryEdit": 2,
      "DepositoryAdd": 3,
      "DepositoryAddComment": 1
    }
  },
  {
    "Data": "2022-11-15T00:00:00+01:00",
    "Actions": {
      "DepositoryEdit": 0,
      "DepositoryAdd": 3,
      "DepositoryAddComment": 1
    }
  }
]

ale skąd ten expando ci się wziął?

0
1a2b3c4d5e napisał(a):

ale skąd ten expando ci się wziął?

Właśnie zależało mi na tym, żebym nie musiał ręcznie wyciagac dla kazdej akcji counta. Na ten moment rodzajów akcji jest chyba z 15, a ta liczba może wzrosnąć. Stąd użycie ExpandoObject, który pozwala mi nazwać propertiesy dynamicznie. action.Type.ToString()
dzieje się to tutaj:

dynamic expando = new ExpandoObject();
            foreach (var action in allActions2)
            {
                
                DynamicallyHelpers.AddProperty(expando, action.Type.ToString(), (ulong)action.Count);
            }
  • metodka z Helpera:
public static void AddProperty(ExpandoObject expando, string propertyName, ulong propertyValue)
        {
            var expandoDict = expando as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                expandoDict.Add(propertyName, propertyValue);
        }

(HistoryType to enum)

2

@kobi55:

public enum HistoryType
{
    DepositoryTest,
    DepositoryEdit,
    DepositoryAdd,
    DepositoryAddComment
}

public class Test
{
    public Guid Id { get; set; } = Guid.NewGuid();

    public DateTime CreationDate { get; set; }

    public HistoryType HistoryType { get; set; }
}

var entries = new List<Test>
{
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)1 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)1 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-2), HistoryType = (HistoryType)3 },

    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = (HistoryType)2 },
    new Test { CreationDate = DateTime.Now.AddDays(-5), HistoryType = (HistoryType)3 },
};

var output = entries
.GroupBy(x => x.CreationDate.Date)
.Select(x => new
{
    Date = x.Key.Date,
    Actions = x.GroupBy(x => Enum.GetName<HistoryType>(x.HistoryType))
               .Select(x => new { x.Key, Count = x.Count() })
               .ToList()
})
.ToList();

var result = output.Select(x => new
{
    Data = x.Date,
    Actions = x.Actions.ToDictionary(x => x.Key, x => x.Count)
})
.ToList();

Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));
[
  {
    "Data": "2022-11-18T00:00:00+01:00",
    "Actions": {
      "DepositoryEdit": 2,
      "DepositoryAdd": 3,
      "DepositoryAddComment": 1
    }
  },
  {
    "Data": "2022-11-15T00:00:00+01:00",
    "Actions": {
      "DepositoryAdd": 3,
      "DepositoryAddComment": 1
    }
  }
]
0

@1a2b3c4d5e: dzięki :) już wcześniejsza opcja od Ciebie działała poprawnie, tylko mialem cos do ogarniecia i nie zdążyłem podziękować. Rzeczywiście, nie pomyślałem, że to co robie z ExpandoObject zwykłe Dictionary ;)

Niestety, EF sobie nie radzi z tłumaczeniem takiego zapytania, dlatego musiałem pobrać odfiltrowane dane z DB i grupować je po stronie klienta.

Dziękuję za pomoc ;)

Wrzucam wersję docelową gdyby ktoś kiedyś szukał czegoś takiego:

        public async Task<ActionTypeByDateRangeChartResponseDto> GetActionTypeByDateRangeChart(
            ActionTypeByDateRangeChartDto dto)
        {
            var userId = await dbx.Users.Where(u => u.Guid == dto.UserGuid).Select(x => x.UserId)
                .SingleOrDefaultAsync();

            var output = (await dbx.DepositoryActionLogs.Where(z =>
                    z.Success
                    && z.CreatedDate.Date >= dto.DateFrom
                    && z.CreatedDate.Date <= dto.DateTo
                    && (dto.UserGuid == null || z.UserId == userId))
                .ToListAsync())
                .GroupBy(x => x.CreatedDate.Date)
                .Select(x => new
                {
                    x.Key.Date,
                    Actions = x.GroupBy(x => Enum.GetName(x.HistoryType))
                        .Select(x => new { x.Key, Count = x.Count() })
                        .ToList()
                })
                .ToList();

            var result = output.Select(x => new ActionReportEntry()
            {
                Date = x.Date,
                Actions = x.Actions.ToDictionary(z => z.Key!, z => z.Count)
            })
                .ToList();

            return new ActionTypeByDateRangeChartResponseDto
            {
                Data = result
            };
        }

1 użytkowników online, w tym zalogowanych: 0, gości: 1