116 messaggi dal 12 febbraio 2016
Buongiorno a tutti,
torno a chiedervi altre domande sull'MVC che ho deciso di approfondire lo studio.
Vorrei capire come recuperare le colonne di una seconda tabella tramite una chiave foreign, mi spiego meglio.
Ho una tabella "Prodotti" con le seguenti colonne: Id, Nome, CatId.
Ho anche una seconda tabella "Categorie" con le seguenti colonne: Id, Nomecat.

Ora vorrei creare un ViewModel che contenga le seguenti colonne: Id, Nome e Nomecat.
Dato che la colonna CatId è una chiave foreign come posso recuperare i valore delle colonne delle due tabelle?
Io per recuperare le colonne di una tabella uso questo:

db.Prodotti.ToArray().Select(x => new ProdottoVM(x)).ToList();
11.724 messaggi dal 09 febbraio 2002
Contributi
Ciao,
dato che stai usando Entity Framework, dovresti mappare la relazione uno-a-molti che esiste tra Categoria e Prodotto. Ecco come fare: leggi qui al paragrafo "Configure a One-to-Many Relationship using Fluent API"
https://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx

In quella guida ti vengono presentate le entità Student e Grade, che sono l'esatto equivalente delle tue classi di entità Prodotto e Categoria. Come puoi leggere lì, dovrai anche creare una proprietà di navigazione all'interno di ciascuna delle due classi di entità.

Ecco per esempio la classe Student che contiene una proprietà di navigazione "Grade" verso l'oggetto Grade relazionato ad essa. Puoi vedere anche la foreign key GradeId.
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int? GradeId { get; set; }
    public Grade Grade { get; set; }
}


Allo stesso modo tu dovrai creare una proprietà di navigazione che ti porta da Prodotto a Categoria.
La query LINQ perciò diventerà la seguente:
db.Prodotti.Include(x => x.Categoria).Select(x => new ProdottoVM(x)).ToList();


L'Include ti serve a indicare che sei interessato sia ai prodotti che alle relative categorie. Dentro il costruttore della classe ProdottoVM potrai leggere sia le proprietà del Prodotto (es. x.Nome) che le proprietà della Categoria (es. x.Categoria.NomeCat).


ciao,
Moreno

PS.
Ma tu vuoi visualizzare nella pagina TUTTI i prodotti che hai nel database? E se fossero 1 milione? Non usi alcun filtro o paginazione?

Enjoy learning and just keep making
116 messaggi dal 12 febbraio 2016
Ciao Moreno,
grazie per la risposta ma la modifica la devo fare nell'entity (Prodotto e Categoria) o nei ViewModel (ProdottoVM e CategoriaVM)?

BrightSoul ha scritto:

PS.
Ma tu vuoi visualizzare nella pagina TUTTI i prodotti che hai nel database? E se fossero 1 milione? Non usi alcun filtro o paginazione?


Quello sarà un altro argomento da studiare ma in un momento successivo, vado per step!

Ciao
11.724 messaggi dal 09 febbraio 2002
Contributi
Ciao.

la modifica la devo fare nell'entity (Prodotto e Categoria) o nei ViewModel (ProdottoVM e CategoriaVM)?
  • Nelle classi di entità Prodotto e Categoria devi aggiungere le proprietà di navigazione;
  • Nel codice del viewmodel ProdottoVM, dato che riceve un'entità Prodotto nel costruttore, dovrai andare a leggere le informazioni sulla categoria dalla proprietà di navigazione Categoria che avrai appunto creato in Prodotto.


ciao,
Moreno
Modificato da BrightSoul il 14 giugno 2019 10:32 -

Enjoy learning and just keep making
116 messaggi dal 12 febbraio 2016
Allora ho aggiornato le entity:

 [Table("Prodotti")]
    public class Prodotto
    {
        [Key]
        public int IdProdotto { get; set; }
        public string Nomeprodotto { get; set; }
        public string descrizione { get; set; }
        public decimal Importo { get; set; }

        public CategoriaDTO Categoria { get; set; }
    }

  [Table("Categorie")]
    public class CategoriaDTO
    {
        [Key]
        public int IdCat { get; set; }
        public string Nomecat { get; set; }

        public ICollection<Prodotto> Prodotti { get; set; }
    }




e questi sono i relativi ViewModel:

public class ProdottoVM
    {
        public ProdottoVM()
        {

        }

        public ProdottoVM(Prodotto row)
        {
            IdProdotto = row.IdProdotto;
            Nomeprodotto = row.Nomeprodotto;
            Descrizione = row.descrizione;
            Importo = row.Importo;
            CatId = row.CatId;
            CatName = row.Categoria.Nomecat;

        }


        public int IdProdotto { get; set; }
        public string Nomeprodotto { get; set; }
        public string Descrizione { get; set; }
        public int CatId { get; set; }
        public string CatName { get; set; }
        public CategoriaVM Categoria { get; set; }

}

    public class CategoriaVM
    {
        public CategoriaVM()
        {

        }

        public CategoriaVM(Categoria row)
        {
            IdCat = row.IdCat;
            Nomecat = row.Nomecat;

        }

        public int IdCat { get; set; }
        public string Nomecat { get; set; }
    }



E questa è la query Linq:

db.Prodotti.Include(x => x.Categoria).Select(x => new ProdottoVM(x)).ToList();




Quando avvio il sito web ottengo la seguente eccezione:
Only parameterless constructors and initializers are supported in LINQ to Entities.
11.724 messaggi dal 09 febbraio 2002
Contributi
Ok, allora prova così, con un ToList() aggiuntivo, in modo che i prodotti e le loro categorie vengano recuperati dal db e caricati in memoria, E POI, proiettati sui ViewModel.
db.Prodotti.Include(x => x.Categoria).ToList().Select(x => new ProdottoVM(x)).ToList();


L'alternativa sarebbe questa, cioè evitare di usare il costruttore con parametro di ProdottoVM.
db.Prodotti.Include(x => x.Categoria).Select(x => new ProdottoVM
{
  IdProdotto = x.IdProdotto,
  Nomeprodotto = x.NomeProdotto,
  descrizione = x.descrizione,
  Importo = x.Importo,
  Categoria = new CategoriaDTO 
  {
     IdCat = x.Categoria.IdCat,
     Nomecat = x.Categoria.Nomecat
  }
}).ToList();


ciao,
Moreno

PS. aggiungo alcune cosine:

Allora ho aggiornato le entity

Categoria e Prodotto sono "entity class", cioè "classi di entità".
Le "entità" invece sono le singole istanze, ad esempio uno dei prodotti o una delle categorie, aventi dei valori e un Id che li identifica.


CategoriaDTO

Una classe di entità non dovrebbe chiamarsi "CategoriaDTO", perché un DTO è un oggetto deputato al trasporto dei dati mentre invece una classe di entità può/deve contenere anche logica applicativa (es. per validare i dati che le vengono impostati dall'esterno). Anche per coerenza con la classe di entità "Prodotto", dovrebbe chiamarsi semplicemente "Categoria".

Enjoy learning and just keep making
116 messaggi dal 12 febbraio 2016
Ciao Moreno,
scusa che ti rispondo adesso ma pian piano sono riuscito ad ottenere il risultato secondo i tuoi suggerimenti.

Ora vorrei però approfondire il discorso filtri e paginazione. Volendo visualizzare ad esempio 10 record per pagina, come gestisco la "query"? Per i filtri invece?
Ti ringrazio per tutte le dritte che dai a tutti noi!
11.724 messaggi dal 09 febbraio 2002
Contributi
Ciao, prego!

Per la paginazione puoi usare gli extension method Skip e Take che sono spiegati qui.
https://www.codingame.com/playgrounds/213/using-c-linq---a-practical-overview/skip-and-take

Ecco un esempio per estrarre i prodotti della pagina 4, dove per ogni pagina ci sono 10 elementi.
int pagina = 4; //Questo valore lo leggi dalla richiesta (Es. se è stato passato via querystring con ?page=4)
int elementiPerPagina = 10;

var prodotti = db.Prodotti
  .Skip(elementiPerPagina * (pagina - 1)) //Saltiamo i 30 elementi delle prime 3 pagine
  .Take(elementiPerPagina) //ne prendiamo 10
  .Include(x => x.Categoria)
  .Select(x => new ProdottoVM
  {
   ...
  })
  .ToList();


Per il filtro invece puoi usare l'extension method Where, eventualmente inserendolo in un if se volessi aggiungerlo alla query in maniera condizionale (es. solo se l'utente ha digitato un testo da cercare).

Continuiamo dall'esempio precedente.
int pagina = 4;
int elementiPerPagina = 10;
string testoDaCercare = "tavolo"; //Questo lo leggi dalla richiesta (es. dalla querystring)

var query = db.Prodotti
  .Skip(elementiPerPagina * (pagina - 1)) //Saltiamo i 30 elementi delle prime 3 pagine
  .Take(elementiPerPagina) //ne prendiamo 10
  .Include(x => x.Categoria);

//La query LINQ è componibile, quindi posso applicare un filtro con Where in maniera condizionale
//Cioè solo se l'utente ha digitato qualcosa da cercare
if (!string.IsNullOrEmpty(testoDaCercare)) {
  query = query.Where(x => x.Descrizione.Contains(testoDaCercare))
}

//e poi continuo
var prodotti = query.Select(x => new ProdottoVM
{
  ...
})
.ToList();


ciao,
Moreno

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.