Berechnete Spalte im EF-Code zuerst

79

Ich muss eine Spalte in meiner Datenbank haben, die von der Datenbank als (Summe der Zeilen) - (Summe der Zeilen b) berechnet wird. Ich verwende das Code-First-Modell, um meine Datenbank zu erstellen.

Folgendes meine ich:

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)
}

Wie kann ich dies in EF CodeFirst erreichen?

CodeDemen
quelle

Antworten:

137

Sie können berechnete Spalten in Ihren Datenbanktabellen erstellen . Im EF-Modell kommentieren Sie einfach die entsprechenden Eigenschaften mit dem DatabaseGeneratedAttribut:

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

Oder mit fließendem Mapping:

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

Wie von Matija Grcic und in einem Kommentar vorgeschlagen, ist es eine gute Idee, die Eigenschaft private setzu erstellen, da Sie sie wahrscheinlich nie im Anwendungscode festlegen möchten. Entity Framework hat keine Probleme mit privaten Setzern.

Hinweis: Für EF .NET Core sollten Sie ValueGeneratedOnAddOrUpdate verwenden, da HasDatabaseGeneratedOption nicht vorhanden ist, z.

modelBuilder.Entity<Income>().Property(t => t.Summ)
    .ValueGeneratedOnAddOrUpdate()
Gert Arnold
quelle
26
Ich weiß davon, aber wie füge ich eine Formel hinzu, um sie über EF zu meiner Datenbank zu berechnen, damit sie von der Konsolen-Commad-Update-Datenbank erstellt wird?
CodeDemen
9
Bitte geben Sie dies in Ihrer Frage deutlich an. Dies bedeutet, dass Migrationen eine berechnete Spalte erstellen sollen. Es ist ein Beispiel hier .
Gert Arnold
2
Muss der Setter privat sein?
Cherven
1
@Cherven Ja, wahrscheinlich besser das zu tun.
Gert Arnold
6
Diese Antwort sollte aktualisiert werden, um hinzuzufügen, dass der Modellbauer für EF Core eine Methode verwenden sollte, ValueGeneratedOnAddOrUpdate()da HasDatabaseGeneratedOptiondiese nicht vorhanden ist. Ansonsten tolle Antwort.
Max
33
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 */ }
    }
}

Verweise:

Matija Grcic
quelle
+1 zum Hinzufügen eines privaten Sets. Die berechnete Spalte sollte beim Hinzufügen neuer Objekte nicht festgelegt werden.
Taher
1
Zufällig habe ich diese Frage erneut angezeigt und jetzt sehe ich, dass der Teil /* do your sum here */nicht zutrifft. Wenn die Eigenschaft innerhalb der Klasse berechnet wird, sollte sie als kommentiert werden [NotMapped]. Der Wert stammt jedoch aus der Datenbank, daher sollte es sich nur um eine einfache getEigenschaft handeln.
Gert Arnold
1
@GertArnold siehe hier - "Da die FullName-Eigenschaft von der Datenbank berechnet wird, wird sie auf der Objektseite nicht mehr synchronisiert, sobald wir eine Änderung an der FirstName- oder LastName-Eigenschaft vornehmen. Glücklicherweise können wir das Beste aus beiden Welten haben hier durch Hinzufügen der Berechnung zurück zum Getter auf der FullName-Eigenschaft "
AlexFoxGill
@AlexFoxGill Was ist dann der Sinn? Warum sollten Sie sich die Mühe machen, berechnete Werte zu speichern, wenn Sie sie jedes Mal dynamisch neu berechnen, falls sie nicht mehr synchron sind?
Rudey
@RuudLenders, damit Sie die berechnete Spalte in LINQ-Abfragen verwenden können.
AlexFoxGill
13

Ab 2019 können Sie mit EF Core Spalten auf saubere Weise mit der fließenden API berechnen:

Angenommen, dies DisplayNameist die berechnete Spalte, die Sie definieren möchten. Sie müssen die Eigenschaft wie gewohnt definieren, möglicherweise mit einem privaten Eigenschaftenzugriff, um die Zuweisung zu verhindern

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    // this will be computed
    public string DisplayName { get; private set; }
}

Adressieren Sie es dann im Model Builder mit der Spaltendefinition:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .Property(p => p.DisplayName)
        // here is the computed query definition
        .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
}

Weitere Informationen finden Sie unter MSDN .

Yennefer
quelle
2

In EF6 können Sie die Zuordnungseinstellung einfach so konfigurieren, dass eine berechnete Eigenschaft wie folgt ignoriert wird:

Definieren Sie die Berechnung für die get-Eigenschaft Ihres Modells:

public class Person
{
    // ...
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName => $"{FirstName} {LastName}";
}

Stellen Sie dann ein, dass die Modellkonfiguration ignoriert wird

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //...
    modelBuilder.Entity<Person>().Ignore(x => x.FullName)
}
Fernando Vieira
quelle
1

Eine Möglichkeit ist dies mit 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;

Sie können dies innerhalb Ihres POCO-Objekts tun public class FirstTable, aber ich würde es nicht vorschlagen, da ich denke , dass es kein gutes Design ist.

Ein anderer Weg wäre die Verwendung einer SQL-Ansicht. Sie können eine Ansicht wie eine Tabelle mit Entity Framework lesen. Und innerhalb des Ansichtscodes können Sie Berechnungen durchführen oder was auch immer Sie wollen. Erstellen Sie einfach eine Ansicht wie

-- 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
Linus Caldwell
quelle
0

Ich würde dies tun, indem ich nur ein Ansichtsmodell verwende. Anstatt beispielsweise die FirstTable-Klasse als Datenbankentität zu haben, wäre es nicht besser, nur eine Ansichtsmodellklasse namens FirstTable zu haben und dann eine Funktion zu haben, mit der diese Klasse zurückgegeben wird, die die berechnete Summe enthält? Zum Beispiel wäre Ihre Klasse nur:

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

Und dann hätten Sie eine Funktion, die Sie aufrufen und die berechnete Summe zurückgibt:

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;
}

Grundsätzlich das gleiche wie bei einer SQL-Ansicht und wie bei @Linus erwähnt, halte ich es nicht für eine gute Idee, den berechneten Wert in der Datenbank zu belassen. Nur ein paar Gedanken.

craigvl
quelle
+1 für I don't think it would be a good idea keeping the computed value in the database- insbesondere, wenn Sie Azure SQL verwenden, das bei hoher Last zu einem Deadlock führt.
Piotr Kula
1
@ppumkin In den meisten Fällen wird die aggregierte Berechnung in der Datenbank besser durchgeführt. Denken Sie an etwas wie "Letzte Kommentar-ID". Sie möchten nicht jede CommentID zurückziehen müssen, um nur eine davon zu nehmen. Sie verschwenden nicht nur Daten und Speicher, sondern erhöhen auch die Belastung der Datenbank selbst, und Sie setzen mehr Zeilen länger gemeinsam genutzte Sperren. Darüber hinaus müssten Sie ständig viele Zeilen aktualisieren. Dies ist wahrscheinlich ein Design, das überprüft werden muss.
JoeBrockhaus
IN ORDNUNG. Ich wollte sie nur im Speicher behalten, berechnete Werte zwischenspeichern. Gehen Sie nicht für jeden Besucher in die Datenbank. Es wird Probleme verursachen. Ich habe das zu oft gesehen.
Piotr Kula
-2

Ich bin auf diese Frage gestoßen, als ich versucht habe, ein EF Code First-Modell mit einer Zeichenfolgenspalte "Slug" von einer anderen Zeichenfolgenspalte "Name" abzuleiten. Der Ansatz, den ich gewählt habe, war etwas anders, hat aber gut funktioniert, deshalb werde ich ihn hier teilen.

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; }

Das Schöne an diesem Ansatz ist, dass Sie die automatische Schneckengenerierung erhalten, ohne den Schneckensetzer freizulegen. Die .ToUrlSlug () -Methode ist nicht der wichtige Teil dieses Beitrags. Sie können an ihrer Stelle alles verwenden, um die Arbeit zu erledigen, die Sie erledigen müssen. Prost!

Patrick Michalina
quelle
Haben Sie nicht bearbeiten Sie alle Bits , die tatsächlich verknüpfen Slugzu Name? Wie derzeit geschrieben, sollte der NameSetter nicht einmal kompilieren.
Auspex
Nein, vor Ihrer Bearbeitung war es ein praktikables Beispiel. Nach Ihrer Bearbeitung macht es keinen Sinn.
Auspex