Laden...

[gelöst] EF:CodeFirst: Mehrere 1:n-Assoziationen von selber Entität

Erstellt von Levion vor 11 Jahren Letzter Beitrag vor 11 Jahren 1.700 Views
Levion Themenstarter:in
114 Beiträge seit 2009
vor 11 Jahren
[gelöst] EF:CodeFirst: Mehrere 1:n-Assoziationen von selber Entität

verwendetes Datenbanksystem: SQL Server

Hallo, ich habe da mal ein wirklich haarstäubendes Problem. In meinem Schema befindet sich die Tabelle Buddy. Die Tabelle enthält zwie FKs zu der Tabelle User. D.h. _UserID _und _ToUserID _um eine Freundschaftsbeziehung abzubilden. (Siehe Anhang)

Ich kann ohne Probleme die Beziehung zwischen Users und Buddy abbilden. Wenn ich aber versuche zusätzlich den FK für ToUserID einzufügen kommt plötzlich dieses Schema heraus.

CreateTable(
                "dbo.Buddy",
                c => new
                    {
                        BuddyID = c.Guid(nullable: false),
                        UserID = c.Guid(nullable: false),
                        ToUserID = c.Guid(nullable: false),
                        Users_UserId = c.Guid(),
                        Users_UserId1 = c.Guid(),
                        User_UserId = c.Guid(),
                        ToUser_UserId = c.Guid(),
                    })
                .PrimaryKey(t => t.BuddyID)
                .ForeignKey("dbo.Users", t => t.Users_UserId)
                .ForeignKey("dbo.Users", t => t.Users_UserId1)
                .ForeignKey("dbo.Users", t => t.User_UserId)
                .ForeignKey("dbo.Users", t => t.ToUser_UserId)
                .Index(t => t.Users_UserId)
                .Index(t => t.Users_UserId1)
                .Index(t => t.User_UserId)
                .Index(t => t.ToUser_UserId);

Wieso baut er mir vier FKs ein? Meine angegeben Spalten werden praktisch ignoriert obwohl ich die angegeben habe. Ich habe versucht alles nochmal zu löschen, den FK immerwieder entfernt, in andere Reihenfolge eingesetzt etc. ... aber ich komm immer zu diesem Ergebnis.

Es funktioniert alles super bis zu dem Punkt wo ich versuche ToUserID zu assoziieren.

Kann mir jemand nen Tipp geben?

Gruß

EDIT: Bei den Tabellen Users und Report habe ich übrigens exakt das selbe Problem. Da geht es um die Spalten UserID und SubjectUserID.

16.828 Beiträge seit 2008
vor 11 Jahren

Deine Spalten (FKs) interessieren das EDMX hier nicht.
Wenn Du per EDMX Deine Modelle abbildest werden im Hintergrund FKs automatisch gemappt. Sie tauchen nachher zwar in der Datenbank auf aber nicht in den Klassen.
Die Magie steckt hier dann im csdl-Modell, das Du zum Laden mitgeben musst.

So wie Du das willst funktioniert das nicht mit dem EDMX-Designer.
Da musst Du auf Code-First umsteigen.

Levion Themenstarter:in
114 Beiträge seit 2009
vor 11 Jahren

Hi Abt! Danke für deine schnelle Antwort.

Ich bin ja bereits im "Code First" und will eigentlich das edmx und das entsprechende T4-Script nutzen um meine Modelle zu generieren. Anschließend schieb ich diese dann per Add-Migration und Update-Database an die Datenbank (Bisher habe ich die Modelle per Hand erstellt). Für die Buddies kommt z.B. das hier raus:

//------------------------------------------------------------------------------
// <auto-generated>
//    Dieser Code wurde aus einer Vorlage generiert.
//
//    Manuelle Änderungen an dieser Datei führen möglicherweise zu unerwartetem Verhalten Ihrer Anwendung.
//    Manuelle Änderungen an dieser Datei werden überschrieben, wenn der Code neu generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------

namespace BusinessSelection.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    [MetadataType(typeof(BuddyMetaData))]
    public partial class Buddy
    {
    	
        public Buddy()
        {
            this.Message = new HashSet<Message>();
        }
    
        public System.Guid BuddyID { get; set; }
        public System.Guid UserID { get; set; }
        public System.Guid ToUserID { get; set; }
    
        public virtual ICollection<Message> Message { get; set; }
        public virtual Users User { get; set; }
        public virtual Users ToUser { get; set; }
    }
}

Ich war (bisher) davon ausgegangen, dass für das Generieren NUR dieses Model und natürlich entsprechende Abhängigkeiten verwendet werden. Ich würde per Hand exakt die gleiche Struktur implementieren. Die Erstellung der FKs habe ich gelernt wird über die Namen der Properties gemacht. D.h. hier würde ich erwarten FK => UserID => Users.UserId und FK => ToUserID => Users.UserId.

Kannst du mir zeigen wo mein Denkfehler ist?

Grüße

Levion

Levion Themenstarter:in
114 Beiträge seit 2009
vor 11 Jahren

Mit Hilfe dieses Threads bin ich weitergekommen. Ich habe für das Model Buddy (über die MetaData-Klasse) folgenden code hinzugefügt.


[ForeignKey("User"), Column(Order = 0)]
public System.Guid UserID { get; set; }

[ForeignKey("ToUser"), Column(Order = 1)]
public System.Guid ToUserID { get; set; }

Damit konnte ich schonmal aushandeln, dass er die richtigen Spalten nimmt. Allerdings wurden immernoch 4 FKs angelegt. Also habe ich die Navigation-Properties bei dem Model Users entfernt und voilà:

namespace BusinessSelection.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class Buddy15 : DbMigration
    {
        public override void Up()
        {
            AddForeignKey("dbo.Buddy", "UserID", "dbo.Users", "UserId");
            AddForeignKey("dbo.Buddy", "ToUserID", "dbo.Users", "UserId");
            CreateIndex("dbo.Buddy", "UserID");
            CreateIndex("dbo.Buddy", "ToUserID");
        }
        
        public override void Down()
        {
            DropIndex("dbo.Buddy", new[] { "ToUserID" });
            DropIndex("dbo.Buddy", new[] { "UserID" });
            DropForeignKey("dbo.Buddy", "ToUserID", "dbo.Users");
            DropForeignKey("dbo.Buddy", "UserID", "dbo.Users");
        }
    }
}

So sollte es sein.

Die Frage ist dann nur: Wie kann ich an meinem Users-Model zwei Navigation-Properties BuddyToUser und _BuddyUser_schaffen die auf die richtigen FKs gehen?

Grüße

Levion

16.828 Beiträge seit 2008
vor 11 Jahren

Ich bin ja bereits im "Code First"

Allerdings wurden immernoch 4 FKs angelegt.

Mittels EDMX setzt Du die Variante "Model First" um. Alternativ beim Import eben "Database First".
Mir ist jetzt hier aus Deinem Beitrag nicht klar, wie Du EDMX und Code First trennst oder was Du tust.

Vielleicht erklärst Du nochmal Dein genaues Vorgehen.
Was machst Du mit dem EDMX dass er Dir Deinen Code generiert? Welchen Code Generator verwendest Du (ja, gibt verschiedene, da EDMX normalerweise keine echten POCOs erstellt!).
Wie sieht Dein Connection-String und die Connection-Klasse aus und wer erstellt diese? Du oder der Generator?

Das EDMX-Verhalten ist jedenfalls genau das, was Du hier vorfindest:
Die durch Deine Assoziationen vorhandenen FKs tauchen nicht im Code auf. Manuell kann man hier keine FKs hinzufügen. Daher muss man bei der Verbindungsfolge das CSDL angeben. Darin stehen die FKs, die dann nachher als Spalte in der DB wieder auftauchen - aber eben nicht im Code und nicht als Property im EDMX. Das schnallt der Generator nicht.

Edit: Ich meine mich gerade dran zu erinnern, dass dies nur möglich ist, wenn der FK wie die Entity+ID-Feld heißt.
In diesem Fall "UsersUserId" (PS: Klassennamen immer im Singular!).

Die Symptome, sprechen dafür, dass Du die beiden Dinge vermischt.

Users_UserId = c.Guid(),
Users_UserId1 = c.Guid(),
User_UserId = c.Guid(),
ToUser_UserId = c.Guid(),

Dass Du Code First nutzt seh ich an der Tatsache des DbMigration.
Deine Klasse via autogenerated spricht aber für den EDMX-Weg.

Von den Metadaten nimmt man eigentlich auch wieder abstand und setzt - eigentlich - mittlerweile auf OnModelCreating(DbModelBuilder) bei der Context-Initialisierung (FluentAPI).

Wenn Du eine Modellierung willst, die nachher als Dokumentation dienst, wieso nutzt Du nicht einfach ein UML-Diagramm? Daraus kannst Du Dir ebenfalls C# Code generieren lassen.

Levion Themenstarter:in
114 Beiträge seit 2009
vor 11 Jahren

Vielleicht erklärst Du nochmal Dein genaues Vorgehen.

OK, das mach ich.

Was machst Du mit dem EDMX dass er Dir Deinen Code generiert?

In dem EDMX erstelle ich meine Entitäten und Assoziationen. Vorher habe ich das manuell in den einzelnen Klassen/Modellen gemacht. Meine Idee war, dass dies mit dem Designer schneller und eleganter geht. Ich ignoriere hier aber vollständig die Mappings. Vom EDMX schreibe ich keine Datenstrukturen in die Datenbank. Es sollen wirklich nur (Code-)Modelle generiert werden.

Welchen Code Generator verwendest Du (ja, gibt verschiedene, da EDMX normalerweise keine echten POCOs erstellt!).

Ich nutze den EF 5.x-DBContext-Generator.

Wie sieht Dein Connection-String und die Connection-Klasse aus und wer erstellt diese? Du oder der Generator?

Mein Connection-String schaut so aus:

<add name="Entities" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=meineDatenbank;Integrated Security=SSPI" providerName="System.Data.SqlClient" />

Die DbContext-Klasse wird generiert und heißt entsprechend Entities.

Die Symptome, sprechen dafür, dass Du die beiden Dinge vermischt.

Users_UserId = c.Guid(),  
Users_UserId1 = c.Guid(),  
User_UserId = c.Guid(),  
ToUser_UserId = c.Guid(),  
  

Dass Du Code First nutzt seh ich an der Tatsache des DbMigration.
Deine Klasse via autogenerated spricht aber für den EDMX-Weg.

Ja, da liegt wohl des Pudels Kern. Ich hatte gedacht, dass ich den Model-Designer des EDMX nutzen kann um meine Entitäten modellieren.

Die Probleme mit den chaotischen FKs scheinen aber aus dem DbMigration zu stammen. Ich verwende ja gar keine Informationen des CSDL. Wahrscheinlich liegt es tatsächlich an den Namenskonventionen. Bei FKs die tatsächlich so heißen wie die Hauptentität (+ID) habe ich ja auch gar keine Schwierigkeiten.

Wenn Du eine Modellierung willst, die nachher als Dokumentation dienst, wieso nutzt Du nicht einfach ein UML-Diagramm? Daraus kannst Du Dir ebenfalls C# Code generieren lassen.

Hm, ja mag sein. Vielleicht wäre das der bessere Weg ... VS bringt UML aber leider erst ab der Ultimate (oder?).

Gruß

Levi

EDIT:
P.S.:

Edit: Ich meine mich gerade dran zu erinnern, dass dies nur möglich ist, wenn der FK wie die Entity+ID-Feld heißt.
In diesem Fall "UsersUserId" (PS: Klassennamen immer im Singular!).

Das hat mir MS eingebrockt. Die Users-Tabelle im MVC-Template war Plural 😉

Levion Themenstarter:in
114 Beiträge seit 2009
vor 11 Jahren

Ich habe jetzt auch das Problem hinsichtlich der Navigationproperties lösen können. Es ist tatsächlich so, dass das EF mit mehreren Bezügen auf die selbe Entität nicht gut zurecht kommt. In dem Fall kann man die Eigenschaft InverseProperty mit angeben.


[InverseProperty("User")]
public virtual ICollection<Buddy> Buddies { get; set; }

[InverseProperty("ToUser")]
public virtual ICollection<Buddy> ToBuddies { get; set; }

Was die EDMX angeht, behalte ich das jetzt erstmal so bei.