Ho questi due modelli:
public class Product
{
public int Id { get; set; }
public int ProductGroupId { get; set; }
public int ProductGroupSortOrder { get; set; }
// ... some more properties
public ICollection<ProductInCategory> InCategories { get; set; }
}
public class ProductInCategory
{
public int Id { get; set; }
public int ProductId { get; set; }
public int ProductCategoryId { get; set; }
public int SortOrder { get; set; }
// Nav.props.:
public Product Product { get; set; }
public ProductCategory ProductCategory { get; set; }
}
Alcuni Product
sono raggruppati tramite la proprietà ProductGroupId
e voglio essere in grado di rimuovere interi gruppi di Product
da ProductInCategory
in una singola query Db.
Il metodo controller riceve un product_id
e un category_id
, non un ProductGroupId
.
Per un singolo Product
ho utilizzato questa query per rimuoverla dalla categoria:
ProductInCategory unCategorize = await _context.ProductsInCategories
.Where(pic => pic.ProductId == product_id && pic.ProductCategoryId == category_id)
.FirstOrDefaultAsync();
e poi:
_context.Remove(unCategorize);
await _context.SaveChangesAsync();
Ora, se ho un List<Product>
che voglio rimuovere da ProductsInCategories
, quale sarebbe la query?
Ho provato questo, ma non riesce sul. .Any()
-bit:
Product product = await _context.Products
.Where(p => p.Id == product_id)
.FirstOrDefaultAsync();
List<Product> products = await _context.Products
.Where(g => g.ProductGroupId == product.ProductGroupId)
.ToListAsync();
List<ProductInCategory> unCategorize = await _context.ProductsInCategories
.Where(pic => pic.ProductId == products.Any(p => p.Id)
&& pic.ProductCategoryId == category_id)
.ToListAsync();
Il metodo controller riceve un
product_id
e uncategory_id
, non unProductGroupId
La prima domanda è perché il metodo riceve product_id
mentre deve fare qualcosa con ProductGroupId
.
Questo ha un cattivo design, ma in ogni caso, per prima cosa traduciamo product_id
nel ProductGroupId
desiderato (ci costerà una query aggiuntiva su db):
int? productGroupId = await _context.Products
.Where(p => p.Id == product_id)
.Select(p => (int?)p.ProductGroupId)
.FirstOrDefaultAsync();
if (productGroupId == null)
{
// Handle non existing product_id
}
Il resto riguarda semplicemente l'accesso alla proprietà di navigazione all'interno della query LINQ to Entities, che verrà convertita da EF Core al join appropriato all'interno della query SQL generata. Non è necessario alcun elenco di Product
intermedi.
List<ProductInCategory> unCategorize = await _context.ProductsInCategories
.Where(pic => pic.Product.ProductGroupId == productGroupId)
.ToListAsync();
Potrei suggerire una correzione del codice per quello che vuoi, ma c'è una soluzione migliore: non caricare i dati per cominciare .
Nel tuo esempio di codice, stai caricando i dati dal database, prima di dire a EF di eliminare gli elementi caricati. Non è efficiente. Non ci dovrebbero essere motivi per caricare i dati, dovresti essere in grado di eseguire semplicemente la query senza dover caricare i dati.
Per quanto ne so, Entity Framework non è in grado di "cancellazione condizionale" (per mancanza di un nome migliore), ad esempio:
DELETE FROM People WHERE Name = 'Bob'
Se si desidera eliminare elementi in base a un particolare valore di colonna (diverso dall'ID dell'entità), non è possibile fare affidamento su Entity Framework a meno che non si desideri caricare i dati (che consumano le prestazioni).
Ci sono due opzioni migliori qui:
1. Esegui tu stesso la query SQL
context.Database.ExecuteSqlCommand(
"DELETE FROM Products WHERE ProductGroupId = " + product.ProductGroupId
);
È così che l'ho sempre fatto.
Sidenote: mi aspetto commenti sull'iniezione SQL. Per essere chiari: non c'è pericolo di SQL injection qui come
product.ProductGroupId
non è una stringa, e il suo valore è controllato dallo sviluppatore, non dall'utilizzatore finale.
Tuttavia, sono d'accordo sul fatto che l'utilizzo di parametri SQL sia una buona pratica. Ma in questa risposta, volevo fornire un semplice esempio per mostrare come eseguire una stringa contenente SQL.
2. Trova una libreria che ti consenta di cancellare senza caricare.
Sono inciampato su questo solo su Google ora. Entity Framework Extensions sembra aver implementato la funzionalità di eliminazione condizionale:
context.Customers.Where(x => x.ID == userId).DeleteFromQuery();
Nel tuo caso, sarebbe:
_context.Products.Where(g => g.ProductGroupId == product.ProductGroupId).DeleteFromQuery();
Sidenote :
Ho sempre usato Code First e EF ha sempre generato automaticamente eliminazioni a cascata. Pertanto, quando si elimina il genitore, anche i suoi figli vengono cancellati. Non sono sicuro che il tuo database abbia cancellazioni a cascata, ma sto assumendo un comportamento EF predefinito (secondo la mia esperienza).