Операция Пересечение с linq - PullRequest
1 голос
/ 22 апреля 2020

Извините за странный заголовок вопроса, но я не знаю, как его сформулировать более кратко. Если вы знаете, как это сформулировать лучше, я буду рад, если вы отредактируете мой вопрос. Итак, у меня есть следующая таблица:

enter image description here

Я говорю о CustomerId и EventType полях. Остальное не важно. Я думаю, вы понимаете, что эта таблица является чем-то вроде журнала событий клиентов. Некоторые клиенты делают событие - у меня есть событие в таблице. Просто.

Мне нужно выбрать события всех клиентов, где у каждого клиента было событие с типом registration и типом deposit. Другими словами, клиент имел registration раньше? У того же клиента был deposit? Если да и да - мне нужно выбрать все события этого клиента.

Как я могу сделать это с помощью LINQ?

Так что я могу написать SQL как

select *
From "CustomerEvents"
where "CustomerId" in (
    select distinct "CustomerId"
    from "CustomerEvents"
    where "EventType" = 'deposit'

    intersect

    select distinct "CustomerId"
    from "CustomerEvents"
    where "EventType" = 'registration'
)

Это работает, но как это написать на LINQ?

И второй вопрос. SQL выше работает, но не универсально. Что если завтра мне нужно будет показать события клиентов, у которых registration, deposit и - одно новое событие - visit? Я должен написать новый запрос. Например:

select *
From "CustomerEvents"
where "CustomerId" in (
    select "CustomerId"
    from "CustomerEvents"
    where "EventType" = 'deposit'

    intersect

    select distinct "CustomerId"
    from "CustomerEvents"
    where "EventType" = 'registration'

     intersect

    select distinct "CustomerId"
    from "CustomerEvents"
    where "EventType" = 'visit'
)

Неудобно :( В качестве исходных данных у меня есть Список с типами событий. Есть ли способ сделать это динамически? Я имею в виду, у меня есть одно новое событие в списке - у меня новое Пересечение.

PS Я использую Postgres и. NET Ядро 3.1

Обновление

Здесь я приведу схему enter image description here

Ответы [ 2 ]

1 голос
/ 22 апреля 2020

Я не проверял, будет ли это правильно переводиться на SQL, но если мы предположим, что ctx.CustomerEvents равно DbSet<CustomerEvent>, вы можете попробовать это:

var targetCustomerIds = ctx
  .CustomerEvents
  .GroupBy(event => event.CustomerId)
  .Where(grouped => 
    grouped.Any(event => event.EventType == "deposit")
    && grouped.Any(event => event.EventType == "registration"))
  .Select(x => x.Key)
  .ToList();

и затем выбрать все события для этих клиентов:

var events = ctx.CustomerEvents.Where(event => targetCustomerIds.Contains(event.CustomerId));

Чтобы получить targetCustomerIds динамически с переменным числом типов событий, вы можете попробовать это:

// for example
var requiredEventTypes = new [] { "deposit", "registration" };

// First group by customer ID
var groupedByCustomerId = ctx
  .CustomerEvents
  .GroupBy(event => event.CustomerId);

// Then filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);

// Then select the target customer IDs
var targetCustomerIds = filtered.Select(x => x.Key).ToList();

// Finally, select your target events
var events = ctx.CustomerEvents.Where(event => 
  targetCustomerIds.Contains(event.CustomerId));

Вы можете определить метод GetFilteredGroups как это:

private static IQueryable<IGrouping<int, CustomerEvent>> GetFilteredGroups(
    IQueryable<IGrouping<int, CustomerEvent>> grouping,
    IEnumerable<string> requiredEventTypes)
{
    var result = grouping.Where(x => true);
    foreach (var eventType in requiredEventTypes)
    {
        result = result.Where(x => x.Any(event => event.EventType == eventType));
    }

    return result;
}

В качестве альтернативы , вместо выбора идентификаторов целевых клиентов, вы можете попытаться напрямую выбрать целевые события из отфильтрованных групп:

// ...
// Filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);

// Select your events here
var results = filtered.SelectMany(x => x).Distinct().ToList();

Что касается невозможности перевести запрос в SQL В зависимости от размера вашей базы данных и, в частности, от размера таблицы CustomerEvents, это решение может быть или не быть идеальным, но вы могли бы загрузить оптимизировать сбор в память и выполнить там группировку:

// for example
var requiredEventTypes = new [] { "deposit", "registration" };

// First group by customer ID, but load into memory
var groupedByCustomerId = ctx
  .CustomerEvents
  .Where(event => requiredEventTypes.Contains(event.EventType))
  .Select(event => new CustomerEventViewModel 
    { 
      Id = event.Id, 
      CustomerId = event.CustomerId, 
      EventType = event.EventType 
    })
  .GroupBy(event => event.CustomerId)
  .AsEnumerable();

// Then filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);

// Then select the target customer IDs
var targetCustomerIds = filtered.Select(x => x.Key).ToList();

// Finally, select your target events
var events = ctx.CustomerEvents.Where(event => 
  targetCustomerIds.Contains(event.CustomerId));

Вам нужно будет создать тип с именем CustomerEventViewModel, подобный этому (чтобы вам не приходилось загружать все экземпляры сущности CustomerEvent в память ):

public class CustomerEventViewModel
{
  public int Id { get; set; }
  public int CustomerId { get; set; }
  public string EventType { get; set; }
}
* 103 2 * И измените GetFilteredGroups следующим образом:
private static IEnumerable<IGrouping<int, CustomerEvent>> GetFilteredGroups(
    IEnumerable<IGrouping<int, CustomerEvent>> grouping,
    IEnumerable<string> requiredEventTypes)
{
    var result = grouping.Where(x => true);
    foreach (var eventType in requiredEventTypes)
    {
        result = result.Where(x => x.Any(event => event.EventType == eventType));
    }

    return result;
}

Теперь оно должно работать нормально.

0 голосов
/ 22 апреля 2020

Спасибо за @Дежан Янюшевич. Он опытный разработчик. Но, похоже, EF не может перевести его решение на SQL (или просто мои руки растут не с того места). Я публикую sh здесь мое решение для этой ситуации. Это просто глупо. Так. У меня в таблице EventType. Это строка. И у меня от клиента следующий запрос фильтра:

List<string> eventType

Просто список с типами событий. Итак, в действии у меня есть следующий код фильтра:

if (eventType.Any())
{
    List<int> ids = new List<int>();

    foreach (var e in eventType)
    {
        var customerIdsList =
            _context.customerEvents.Where(x => x.EventType == e).Select(x => x.CustomerId.Value).Distinct().ToList();

        if (!ids.Any())
        {
            ids = customerIdsList;
        }
        else
        {
            ids = ids.Intersect(customerIdsList).ToList();
        }
    }

    customerEvents = customerEvents.Where(x => ids.Contains(x.CustomerId.Value));
}

Не очень быстро, но работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...