Получение данных из нескольких таблиц в одном запросе SQL с использованием LINQ и Entity Framework (Core)

2

Я хочу получить данные из нескольких таблиц с помощью LINQ в моем приложении.NET Core. Вот пример:

public class Customer {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime Created { get; set; }

    public HashSet<Transaction> Transactions { get; set; }
}

public class Transaction {
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    public DateTime Created { get; set; }

    public Guid CustomerId { get; set; }
    public Customer Customer { get; set; }
}

Они имеют отношение один ко многим в моем решении. Один клиент имеет много транзакций, а одна транзакция имеет одного клиента. Если бы я хотел собрать 10 последних транзакций и 10 последних клиентов в одном запросе LINQ, как бы я это сделал? Я читал, что .Union() должен быть в состоянии сделать это, но он не будет работать для меня. Пример:

var ids = _context
    .Customers
    .OrderByDescending(x => x.Created)
    .Take(10)
    .Select(x => x.Id)
    .Union(_context
        .Transactions
        .OrderByDescending(x => x.Created)
        .Take(10)
        .Select(x => x.CustomerId)
    )
    .ToList();

Это дает мне два списка типа Guid, но они содержат одинаковые элементы. Не уверен, что это только я, кто понимает это неправильно, но это кажется немного странным. Я счастлив, пока он запрашивает базу данных один раз.

  • 1
    Почему один запрос является обязательным? 10 последних транзакций могут быть сделаны одним (и, возможно, «старым») клиентом. Если вам нужно 10 последних транзакций и 10 последних клиентов, вам нужно 2 запроса. Если вам нужно 10 последних транзакций, включая информацию о клиентах, просто используйте Include , но это может вернуть менее 10 (и, возможно, «старых») клиентов.
Теги:
linq
entity-framework
entity-framework-core

3 ответа

1

Вы написали:

Я хотел получить 10 последних транзакций и 10 последних клиентов в одном запросе LINQ.

Немного неясно, что вы хотите. Я сомневаюсь, что вы хотите одну последовательность с сочетанием клиентов и транзакций. Я предполагаю, что вы хотите 10 новых клиентов, каждый из которых имеет свои последние 10 транзакций?

Интересно, почему вы отклоняетесь от первых соглашений, касающихся кода структуры сущностей? Если ваш класс Customer представляет строку в вашей базе данных, то наверняка у него нет HashSet<Transaction>?

Customer один ко многим" с его Transactions должен быть смоделирован следующим образом:

class Customer
{
    public int Id {get; set;}
    ... // other properties

    // every Customer has zero or more Transactions (one-to-many)
    public virtual ICollection<Transaction> Transactions {get; set;}
}
class Transaction
{
    public int Id {get; set;}
    ... // other properties

    // every Transaction belongs to exactly one Customer, using foreign key
    public int CustomerId {get; set;}
    public virtual Customer Customer {get; set;}
}

public MyDbContext : DbContext
{
    public DbSet<Customer> Customers {get; set;}
    public DbSet<Transaction> Transactions {get; set;}
}

Это все, что должна знать инфраструктура сущностей для определения таблиц, которые вы хотите создать, для определения ваших отношений "один ко многим" и для обнаружения первичных ключей и внешних ключей. Только если вам нужны разные имена таблиц или столбцов, вам понадобятся атрибуты и/или свободный API

Основное различие между моими и вашими классами состоит в том, что отношение "один ко многим" представлено виртуальными свойствами. HashSet - это ICollection. В конце концов, ваша таблица Transactions представляет собой набор строк, а не HashSet

В каркасе сущностей столбцы ваших таблиц представлены не виртуальными свойствами; виртуальные свойства представляют отношения между таблицами (один ко многим, многие ко многим,...)

Довольно много людей склонны (group-) присоединяться к таблицам, когда они используют каркас сущностей. Тем не менее, жизнь намного проще, если вы используете виртуальные свойства

Вернуться к вашему вопросу

Я хочу (некоторые свойства) 10 новых клиентов, каждый из которых (несколько свойств) их 10 последних сделок

var query = dbContext.Customers                           // from the collection of Customer
    .OrderByDescending(customer => customer.Created)      // order this by descending Creation date
    .Select(customer => new                               // from every Customer select the
    {                                                     // following properties
         // select only the properties you actually plan to use
         Id = Customer.Id,
         Created = Customer.Created,
         Name = Customer.Name,
         ...

         LatestTransactions = customer.Transactions        // Order the customer collection
             .OrderBy(transaction => transaction.Created)  // of Transactions
             .Select(transaction => new                    // and select the properties
             {
                 // again: select only the properties you plan to use
                 Id = transaction.Id,
                 Created = transaction.Created,
                 ...

                 // not needed you know it equals Customer.Id
                 // CustomerId = transaction.CustomerId,
             })
             .Take(10)                                      // take only the first 10 Transactions
             .ToList(),
    })
    .Take(10);                                              // take only the first 10 Customers

Структура сущности знает отношение один-ко-многим и признает, что для этого необходимо соединение group-.

Одна из более медленных частей вашего запроса - передача выбранных данных из СУБД в локальный процесс. Следовательно, разумно ограничить выбранные данные теми данными, которые вы фактически планируете использовать. Если у Клиента с Id 4 есть 1000 Транзакций, было бы бесполезно передавать внешний ключ для каждой Транзакции, потому что вы знаете, что он имеет значение 4.

Если вы действительно хотите сделать это самостоятельно:

var query = dbContext.Customers                 // GroupJoin customers and Transactions
    .GroupJoin(dbContext.Transactions,
    customer => customer.Id,                    // from each Customer take the primary key
    transaction => transaction.CustomerId,      // from each Transaction take the foreign key
    (customer, transactions) => new             // take the customer with his matching transactions
    {                                           // to make a new:
       Id = customer.Id,
       Created = customer.Created,
       ...

       LatestTransactions = transactions
           .OrderBy(transaction => transaction.Created)
           .Select(transaction => new
           {
               Id = transaction.Id,
               Created = transaction.Created,
               ...
           })
           .Take(10)
           .ToList(),
       })
       .Take(10);
0

попробуй это:

var customers = customerService.GetAll().OrderByDescending(c => c.Created).Take(10).ToList().AsQueryable();
var transactions = transactionService.GetAll().OrderByDescending(t => t.Created).Take(10).ToList().AsQueryable();

transactions = transactions.Where(t => customers.Any(c => c.CustomerId  == t.Id));
0

Попробуйте следующее. Я моделирую вашу базу данных _context как класс, чтобы я мог проверить синтаксис. Помните, что один клиент может сопоставить несколько транзакций. Вы можете использовать идентификатор GroupBy, чтобы получить 10 разных клиентов.

    class Program
    {


        static void Main(string[] args)
        {
            Context _context = new Context();



            var ids = (from c in _context.customers
                       join t in _context.transactions on c.Id equals t.CustomerId
                       select new { c = c, t = t})
                       .OrderByDescending(x => x.c.Created)
                       .Take(10)
                       .ToList();

        }
    }

    public class Context
    {
        public List<Customer> customers { get; set; }
        public List<Transaction> transactions { get; set; }
    }
    public class Customer
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public DateTime Created { get; set; }

        public HashSet<Transaction> Transactions { get; set; }
    }

    public class Transaction
    {
        public Guid Id { get; set; }
        public decimal Amount { get; set; }
        public DateTime Created { get; set; }

        public Guid CustomerId { get; set; }
        public Customer Customer { get; set; }
    }

Вы можете попробовать это вместо этого:

            var ids = (from c in _context.customers
                       join t in _context.transactions on c.Id equals t.CustomerId
                       select new { c = c, t = t})
                       .OrderByDescending(x => x.c.Created)
                       .GroupBy(x => x.c.Id)
                       .SelectMany(x => x.Take(10))
                       .ToList();

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

            var transactions = _context.transactions
                       .OrderByDescending(x => x.Created)
                       .GroupBy(x => x.CustomerId)
                       .Select(x => x.Take(10))
                       .ToList();
  • 1
    Я даже не уверен, что это работает, потому что запрос занял так много времени, что мне пришлось отменить его. Я хотел сделать это, чтобы сэкономить время, запрашивая меньше и выполняя простой поиск. Такое ощущение, что он захватывает данные всей таблицы, а затем выполняет расчет o_O
  • 0
    Ты прав. Запрос должен выполнить сортировку каждой строки таблицы и выполнить объединение. Это занимает так много времени, потому что это делается в сетевой библиотеке, а не в SQL Server, который предназначен для выполнения этих запросов. Я бы порекомендовал создать хранимую процедуру, которая сделает запрос быстрее.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню