[Entity Framework] Beziehung richtig umsetzten

  • C#

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    [Entity Framework] Beziehung richtig umsetzten

    Hallo,

    ich habe folgende Entity Models:

    C#-Quellcode

    1. class Feld
    2. {
    3. public int Id { get; set; }
    4. public string Bezeichnung { get; set; }
    5. }
    6. class Text
    7. {
    8. public int Id { get; set; }
    9. public string Titel { get; set; }
    10. public string Content { get; set; }
    11. public ICollection<Feld> Felder { get; set; }
    12. }


    Ich möchte, dass die Texte mehreren Feldern zugeordnet werden können.
    Wenn ich anhand diesen Models eine Migration erstelle und in die Datenbank einspiele, dann gibt es dort garkeine Spalte für <Feld> ... mach ich irgendetwas falsch?
    Ich mach es immer so:

    C#-Quellcode

    1. class Feld
    2. {
    3. public int Id { get; set; }
    4. public string Bezeichnung { get; set; }
    5. public Text Text { get; set; }
    6. public int TextId { get; set; }
    7. }
    8. class Text
    9. {
    10. public int Id { get; set; }
    11. public string Titel { get; set; }
    12. public string Content { get; set; }
    13. public ICollection<Feld> Felder { get; set; }
    14. public void Text()
    15. {
    16. this.Felder = new Hashset<Felder>();
    17. }
    18. }


    Somit kann im Code dann direkt per Feld.Text auf die Text Properties des Objektes zugegriffen werden. Sieh dir dazu auch LazyLoading an.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Wenn dann würde es in deinem Beispiel in der Tabelle Feld eine Spalte TextId geben. Keine Spalte Feld.

    Dein Code ist eigentlich valid für eine 0..1 zu n Beziehung.
    Das ist meine Signatur und sie wird wunderbar sein!
    Hallo,

    mein "Feld" Model hat aber keine Spalte "Text" da ja mehrere Texte einem Feld zugeordnet werden können, es soll eine many to many relation werden.
    Ich bräuchte ja eigentlich eine dritte Tabelle Felder_Texte, aber ich weiß nicht wie ich Entity sagen kann, dass es diese Tabelle erstellen soll und wie ich damit arbeite.
    Also laut deinen Entitäten hast du eine 0..1 to many Beziehung. Ein Text kann mehrere Felder haben und nicht umgekehrt.
    Verwendest du EF 6 oder EF Core ?
    Bei EF Core musst du die Auflösungsentität selber erstellen.
    Bei EF 6 musst du dann logischerweise in beide Entitäten die jeweilige ICollection angeben:

    VB.NET-Quellcode

    1. class Feld
    2. {
    3. public void Feld() {
    4. this.Texte= new Hashset<Text>();
    5. }
    6. public int Id { get; set; }
    7. public string Bezeichnung { get; set; }
    8. public ICollection<Text> Texte { get; set; }
    9. }
    10. class Text
    11. {
    12. public int Id { get; set; }
    13. public string Titel { get; set; }
    14. public string Content { get; set; }
    15. public ICollection<Feld> Felder { get; set; }
    16. public void Text()
    17. {
    18. this.Felder = new Hashset<Felder>();
    19. }
    20. }
    Das ist meine Signatur und sie wird wunderbar sein!
    Sorry das ich mich einmische. :S

    @Mono dein Code stellt aber eine n:m Beziehung dar und keine 1:n.

    Wie Mono schon gesagt hat muss bei EF Core sowohl die Zwischentabelle bei einer n:m Beziehung selbst erstellt, als auch über die Fluent API konfiguriert werden. Siehe hier.

    Außerdem möchte ich nicht unerwähnt lassen das du die Relation-Properties falls du irgendwann LazyLoading verwenden willst als Virtual kennzeichnen solltest.
    Das gilt für beide EF Versionen da seit der 2.1 nun auch in EF Core verfügbar. Weiter Infos gibt es hier.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Verwende Version 6.2 von Core steht nix.. habs nun hinbekommen. Entity Framework hat eine TextFelds Tabelle erstellt. Ich habe nun ein Text erstellt und mehreren Feldern zugewiesen. In der Datenbank sieht alles gut aus, aber mit folgendem Code wird mir angezeigt, dass text.Felder.Count 0 ist. Habe aber 2 Felder hinzugefügt.

    C#-Quellcode

    1. ​using (WFCDbContext db = new WFCDbContext())
    2. {
    3. Text text = db.Texte.find(1);
    4. console.WriteLine(text.Felder.Count.ToString());
    5. }

    windowsfan schrieb:

    Verwende Version 6.2 von Core steht nix

    Dann hast du EF 6 in verwendung.

    windowsfan schrieb:

    Entity Framework hat eine TextFelds Tabelle erstellt.

    JA, es empfielt sich eher die Eigenschaften und Klassen in Englisch zu erstellen da EF 6 die Pluralisierung selbst vornimmt und das führt dann zu doofen Tabellennamen. Kann aber beim überschreiben von OnModelCreating Konfiguriert werden.

    PS: Bedenke bitte das der obige Code zu zwei(!!) Roundtrips zur DB führt. Einmal wird die Entität mit der ID aus der DB geladen (Ohne Felder da du kein Include verwendet hast und in der zweiten Zeile wird durch das LazyLoading eine weitere Abfrage gesendet welche die Felder für die Entität abruft. Das mag jetzt nicht spürbar sein, hier kann man sich aber ENORME Perfomancebremsen einfahren.
    Stell dir nur mal vor du Ladest eine Tabelle in ein DataGrid und hast eine Spalte in welcher auf eine Eigenschaft verwiesen wird welche in einer Beziehung steckt.
    Da hast du gleich statt nur einem Roundtrip gleich ein paar 1000 zusammen. =O

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Danke für dein Feedback - du hast natürlich recht das sollte ich beachten, aber momentan scheiterts schon daran, dass in meinem Text object die Felder nicht verknüpft sind. Das müsste doch automatisch funktionieren oder?
    Also die Liste text.Felder ist leer...
    Hallo @windowsfan

    Bitte setze meine Ratschläge auch um!

    Nofear23m schrieb:

    Außerdem möchte ich nicht unerwähnt lassen das du die Relation-Properties falls du irgendwann LazyLoading verwenden willst als Virtual kennzeichnen solltest.

    LazyLoading funkt nur wenn das Property als virtual (in vb Overridable) gekennzeichnet ist da EF dieses sonst nicht überschreiben kann.

    Dann wird auch automatisch "Felder" befüllt.
    Jedoch solltest du mit Includes arbeiten da hier beim ersten Roundtrip diese Daten durch einen JOIN mitgeladen werden.

    C#-Quellcode

    1. var text = db.Texte
    2. .Where(t => t.Id == 1)
    3. .Include(t1 => t1.Felder)
    4. .Single();

    Aus dem Kopf

    Allerdings ist das auch eher unschön da ja hier alle Felder geladen werden was du nicht benötigst wenn du nur(!!) die Anzahl benötigst.
    Besser ist es hier in diesem Fall überhaupt nur die Felder abzufragen.

    Weider aus dem Kopf, muss nicht genau stimmen, bin kein C# ler

    C#-Quellcode

    1. int c = db.Felder.Where(f => f.Texte.Where(t => t.Id = 1).Any()).Count()


    Das wäre das sauberste Query denke ich. Müsste ein kleines Testprojekt anlegen um zu wissen obs auch kompiliert, aber falls Syntaxfehler drinnen sind musst du hald sehen wo.
    Wie gesagt, bin kein C# ler und EF 6 habe ich schon länger nicht mehr gemacht da EF Core schon sehr stabil und viel schneller läuft.

    Edit:
    Habs gerade getestet:
    Probier folgendes: int c = db.Fields.Count(f => f.Texts.Select(s => s.Id).Contains(textId));

    Habe ein Testprogramm angelegt wo man das sehr gut Testen kann:

    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Data.Entity;
    4. using System.Diagnostics;
    5. using System.Linq;
    6. using System.Text;
    7. namespace cSharpEf6Test
    8. {
    9. class Program
    10. {
    11. static void Main(string[] args)
    12. {
    13. using (var db = new FieldsTestContext())
    14. {
    15. db.Database.CreateIfNotExists();
    16. db.Database.Log = s => Debug.WriteLine("EFLog", s);
    17. Console.Write("Enter a Title for a new Text: ");
    18. var name = Console.ReadLine();
    19. var newText = new Text { Title = name , Fields = new List<Field>()};
    20. Console.Write("How many field should i create? ");
    21. int cfields = Convert.ToInt32(Console.ReadLine());
    22. for (int i = 0; i < cfields; i++)
    23. {
    24. newText.Fields.Add(new Field() {Caption = $"Caption {i.ToString()}"});
    25. }
    26. db.Texts.Add(newText);
    27. db.SaveChanges();
    28. var query = from b in db.Texts
    29. orderby b.Title
    30. select b;
    31. Console.WriteLine("All texts in the database:");
    32. foreach (var item in query)
    33. {
    34. Console.WriteLine($"'{item.Title}' with Id '{item.Id.ToString()}");
    35. }
    36. Console.Write($"Load field-count from text with Id:");
    37. int textId = Convert.ToInt32(Console.ReadLine());
    38. int c = db.Fields.Count(f => f.Texts.Select(s => s.Id).Contains(textId));
    39. Console.WriteLine($"Count:{c.ToString()}");
    40. Console.WriteLine("Press any key to exit...");
    41. Console.ReadKey();
    42. }
    43. }
    44. }
    45. public class Field
    46. {
    47. public int Id { get; set; }
    48. public string Caption { get; set; }
    49. public virtual ICollection<Text> Texts { get; set; }
    50. }
    51. public class Text
    52. {
    53. public int Id { get; set; }
    54. public string Title { get; set; }
    55. public string Content { get; set; }
    56. public virtual ICollection<Field> Fields { get; set; }
    57. }
    58. public class FieldsTestContext : DbContext
    59. {
    60. public DbSet<Field> Fields { get; set; }
    61. public DbSet<Text> Texts { get; set; }
    62. }
    63. }



    Hier wird eine korrekte und schnelle SQL Abfrage generiert welche wirklich als Ergebniss nur eine Zeile mit einer Spalte (der Anzahl) zurückgibt. Hier mit der ID 1 als Beispiel.

    SQL-Abfrage

    1. SELECT
    2. [GroupBy1].[A1] AS [C1]
    3. FROM ( SELECT
    4. COUNT(1) AS [A1]
    5. FROM [dbo].[Fields] AS [Extent1]
    6. WHERE EXISTS (SELECT
    7. 1 AS [C1]
    8. FROM [dbo].[TextFields] AS [Extent2]
    9. WHERE ([Extent1].[Id] = [Extent2].[Field_Id]) AND ([Extent2].[Text_Id] = 1)
    10. )
    11. ) AS [GroupBy1]


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Nofear23m“ ()

    Nanu, plötzlich so still hier. @windowsfan mein Edit gesehen?

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Zu EntityFramework (Core) und Beziehungen kann ich dir nur empfehlen es direkt selbst zu mappen, damit kann man sich einige Kopfschmerzen sparen.
    Die DatabaseContext Klasse bietet dazu eine überschreibbare Methode namens protected override void OnModelCreating(DbModelBuilder modelBuilder), mit welcher du die Model-Beziehungen herstellen kannst.
    Das ganze nennt sich übrigens Fluent API (zur Referenz).

    In der Methode könntest du dann z.B. hiermit deine 1:n Beziehung explizit herstellen:

    C#-Quellcode

    1. protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    2. modelBuilder.Entity<Text>()
    3. .HasMany(x => x.Felder)
    4. .WithOne(x => x.Text)
    5. .HasPrincipalKey(x => x.Id)
    6. .HasForeignKey(x => x.TextId);
    7. }


    Damit das oben genannte Mapping funktioniert müssen die Klassen mindestens diese Felder haben:

    C#-Quellcode

    1. public class Feld {
    2. public int Id { get; set; }
    3. public int TextId { get; set; }
    4. public Text Text { get; set; }
    5. }
    6. public class Text {
    7. public int Id { get; set; }
    8. public virtual ICollection<Feld> Felder { get; set; }
    9. }


    Nur mal nebenbei in den Raum geworfen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „OPTiX“ ()

    Ja, deshalb haben wir nachgefragt ob er unter Core proggt, aber er hat ja EF 6 in verwendung.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##