51

Ho bisogno di avere una colonna nel mio database calcolata dal database come (somma di righe) - (somma di rowsb). Sto usando un modello code-first per creare il mio database.Colonna calcolata in codice EF First

Ecco cosa intendo:

public class Income { 
     [Key] 
     public int UserID { get; set; } 
     public double inSum { get; set; } 
} 

public class Outcome { 
     [Key] 
     public int UserID { get; set; } 
     public double outSum { get; set; } 
} 

public class FirstTable { 
     [Key] 
     public int UserID { get; set; } 
     public double Sum { get; set; } 
     // This needs to be calculated by DB as 
     // (Select sum(inSum) FROM Income WHERE UserID = this.UserID) 
     // - (Select sum(outSum) FROM Outcome WHERE UserID = this.UserID) 
} 

Come posso raggiungere questo obiettivo in EF CodeFirst?

risposta

91

È possibile creare computed columns nelle tabelle del database. Nel modello EF basta annotare le proprietà corrispondenti con l'attributo DatabaseGenerated:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)] 
public double Summ { get; private set; } 

O con mappatura fluente:

modelBuilder.Entity<Income>().Property(t => t.Summ) 
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed) 

Come suggerito da Matija Grcic e in un commento, è una buona idea per fare il proprietà private set, perché probabilmente non si vorrebbe mai impostarlo nel codice dell'applicazione. Entity Framework non ha problemi con i setter privati.

+9

Lo so, ma come faccio ad aggiungere una formula per calcolarla nel mio database attraverso EF, in modo che venga creata dal database di aggiornamento della console? – CodeDemen

+6

Si prega di dichiararlo chiaramente nella domanda. Significa che vuoi che le migrazioni creino una colonna calcolata. C'è un esempio [qui] (http://www.davepaquette.com/archive/2012/09/23/calculated-columns-in-entity-framework-code-first-migrations.aspx). –

+2

+1 per dare sia l'annotazione AND il modo fluente. Grazie. – Tipx

2

Un modo sta facendo con LINQ:

var userID = 1; // your ID 
var income = dataContext.Income.First(i => i.UserID == userID); 
var outcome = dataContext.Outcome.First(o => o.UserID == userID); 
var summ = income.inSumm - outcome.outSumm; 

Si può farlo all'interno del vostro oggetto POCO public class FirstTable, ma non vorrei suggerire di, perché penso che non è un buon design.

Un altro modo sarebbe utilizzare una vista SQL. È possibile leggere una vista come una tabella con Entity Framework. E all'interno del codice di visualizzazione, puoi eseguire calcoli o qualsiasi altra cosa desideri. Basta creare una vista come

-- not tested 
SELECT FirstTable.UserID, Income.inCome - Outcome.outCome 
    FROM FirstTable INNER JOIN Income 
      ON FirstTable.UserID = Income.UserID 
     INNER JOIN Outcome 
      ON FirstTable.UserID = Outcome.UserID 
22
public string ChargePointText { get; set; } 

public class FirstTable 
{ 
    [Key] 
    public int UserID { get; set; } 

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]  
    public string Summ 
    { 
     get { return /* do your sum here */ } 
     private set { /* needed for EF */ } 
    } 
} 

Riferimenti:

+0

+1 per aggiungere set privato. La colonna calcolata non deve essere impostata quando si aggiungono nuovi oggetti. – Taher

+0

È accaduto di visualizzare nuovamente questa domanda e ora vedo che la parte '/ * fa la somma qui * /' non si applica. Se la proprietà è calcolata all'interno della classe, dovrebbe essere annotata come '[NotMapped]'. Ma il valore proviene dal database, quindi dovrebbe essere solo una semplice proprietà get. –

+0

@GertArnold vedere [qui] (http://www.davepaquette.com/archive/2012/09/23/calculated-columns-in-entity-framework-code-first-migrations.aspx) - * "Poiché FullName la proprietà viene calcolata dal database, non sarà sincronizzata sul lato dell'oggetto non appena apporteremo una modifica alla proprietà FirstName o LastName. Fortunatamente, possiamo avere il meglio di entrambi i mondi qui aggiungendo anche il calcolo indietro al getter della proprietà FullName "* – AlexFoxGill

1

vorrei andare su questo da solo utilizzando un modello di vista. Ad esempio, piuttosto che avere la classe FirstTable come entità db, non sarebbe meglio avere una classe del modello di vista chiamata FirstTable e quindi avere una funzione che viene utilizzata per restituire questa classe che includa la somma calcolata? Per esempio la classe sarebbe solo:

public class FirstTable { 
    public int UserID { get; set; } 
    public double Sum { get; set; } 
} 

E allora si avrebbe una funzione che si chiama che restituisce la somma calcolata:

public FirsTable GetNetSumByUserID(int UserId) 
{ 
    double income = dbcontext.Income.Where(g => g.UserID == UserId).Select(f => f.inSum); 
    double expenses = dbcontext.Outcome.Where(g => g.UserID == UserId).Select(f => f.outSum); 
    double sum = (income - expense); 
    FirstTable _FirsTable = new FirstTable{ UserID = UserId, Sum = sum}; 
    return _FirstTable; 
} 

In sostanza lo stesso di una vista SQL e come detto @Linus Non penso sarebbe una buona idea mantenere il valore calcolato nel database. Solo alcuni pensieri.

+0

+1 per 'Non penso sarebbe una buona idea mantenere il valore calcolato nel database '- specialmente se si utilizzerà SQL di Azure che inizierà a deadlock in caso di carico pesante. – ppumkin

+0

@ppumkin Nella maggior parte dei casi, il calcolo aggregato verrà eseguito meglio nel DB. Pensa a qualcosa come "ID commento più recente". Non vuoi dover ritirare ogni CommentID per prenderne solo uno. Non solo stai sprecando dati e memoria, ma stai anche aumentando il carico sul DB stesso, e in effetti metti i blocchi condivisi su più file più a lungo. Inoltre, dovresti * aggiornare * un sacco di file tutto il tempo, che probabilmente è un progetto che necessita di revisione. – JoeBrockhaus

+0

OK. Volevo solo tenerli in memoria, valori calcolati, memorizzati nella cache. Non andare al database per ogni visitatore. Sta per causare problemi. Ho visto questo accadere troppe volte. – ppumkin

0

Mi sono imbattuto in questa domanda quando provavo ad avere un codice EF Primo modello con una colonna di stringhe "Slug", derivato da un'altra colonna di stringhe "Nome". L'approccio che ho preso è stato leggermente diverso ma ha funzionato bene, quindi lo condividerò qui.

private string _name; 

public string Name 
{ 
    get { return _name; } 
    set 
    { 
     _slug = value.ToUrlSlug(); // the magic happens here 
     _name = value; // but don't forget to set your name too! 
    } 
} 

public string Slug { get; private set; } 

Che cosa è bella di questo approccio è che si ottiene la generazione automatica lumaca, senza mai esporre il setter lumaca. Il metodo .ToUrlSlug() non è la parte importante di questo post, è possibile utilizzare qualsiasi cosa al suo posto per eseguire il lavoro necessario. Saluti!