Voglio estrarre dati da più tabelle utilizzando LINQ nella mia applicazione .NET Core. Ecco un esempio:
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; }
}
Questi hanno una relazione uno-a-molti nella mia soluzione. Un cliente ha molte transazioni e una transazione ha un cliente. Se volessi prendere le 10 transazioni più recenti e 10 gli ultimi clienti in una query LINQ, come dovrei farlo? Ho letto che .Union()
dovrebbe essere in grado di farlo, ma non funzionerà per me. Esempio:
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();
Questo mi dà due liste di tipo Guid
, ma contengono gli stessi elementi. Non sono sicuro che sia solo io a capire questo errore, ma sembra un po 'strano. Sono felice finché chiede al database una volta.
Hai scritto:
Volevo prendere le ultime 10 transazioni e 10 nuovi clienti in una query LINQ
È un po 'oscuro quello che vuoi. Dubito che tu voglia una sequenza con un mix di clienti e transazioni. Immagino che tu voglia i 10 nuovi clienti, ognuno con le loro ultime 10 transazioni?
Mi chiedo il motivo per cui dovresti discostarti dalle prime convenzioni del codice framework dell'entità . Se il tuo cliente di classe rappresenta una riga nel tuo database, sicuramente non ha un HashSet<Transaction>
?
Un one-to-many di un Customer
con le sue Transactions
dovrebbe essere modellato come segue:
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;}
}
Questo è tutto ciò che il framework di entità deve conoscere per rilevare le tabelle che si desidera creare, per rilevare la relazione uno-a-molti e per rilevare le chiavi primarie e le chiavi esterne. Solo se desideri nomi diversi di tabelle o colonne, avrai bisogno di attributi e / o API fluente
Le principali differenze tra le mie classi e le tue, è che la relazione uno-a-molti è rappresentata da proprietà virtuali . HashSet è un ICollection. Dopo tutto, la tabella Transactions
è una raccolta di righe, non un HashSet
Nel framework di entità, le colonne delle tabelle sono rappresentate da proprietà non virtuali; le proprietà virtuali rappresentano le relazioni tra le tabelle (uno-a-molti, molti-a-molti, ...)
Molte persone tendono a (unire) le tabelle di join, quando usano il framework di entità. Tuttavia, la vita è molto più semplice se si utilizzano le proprietà virtuali
Torna alla tua domanda
Voglio (alcune proprietà di) i 10 nuovi clienti, ciascuno con (diverse proprietà di) le loro ultime 10 transazioni
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
Il framework Entity conosce la relazione uno-a-molti e riconosce che per questo è necessario un join di gruppo.
Una delle parti più lente della query è il trasferimento dei dati selezionati dal DBMS al processo locale. Quindi è consigliabile limitare i dati selezionati ai dati che si prevede di utilizzare. Se il Cliente con Id 4 ha 1000 Transazioni, sarebbe uno spreco trasferire la chiave esterna per ogni Transazione, perché sai che ha valore 4.
Se vuoi davvero unirti a te stesso:
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);