Получение данных из нескольких таблиц в одном запросе SQL с использованием LINQ и Entity Framework (Core) - PullRequest
0 голосов
/ 17 декабря 2018

Я хочу получить данные из нескольких таблиц с помощью 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, но они содержат одинаковые элементы.Не уверен, что это только я, кто понимает это неправильно, но это кажется немного странным.Я счастлив, пока он запрашивает базу данных один раз.

Ответы [ 3 ]

0 голосов
/ 17 декабря 2018

Вы писали:

Я хотел собрать 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

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

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

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

Я хочу (некоторые свойства) 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's 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

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

Одна из медленных частей вашего запроса - передача выбранных данных из СУБД в локальный процесс.Следовательно, разумно ограничить выбранные данные теми данными, которые вы фактически планируете использовать.Если у Клиента с 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 голосов
/ 18 декабря 2018

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

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 голосов
/ 17 декабря 2018

Попробуйте следующее.Я моделирую вашу базу данных _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();
...