2017-05-28 2 views
1

Nach anderen Fragen (here und here) ist es möglich, eindeutige Schlüsselverletzungen in Entity Framework 6 abzufangen, indem die ausgelöste Ausnahme abgefangen und inspiziert wird InnerException.Behandeln Sie doppelte Schlüsselverstöße in EntityFramework Core

Wenn DbContext.SaveChanges() mit einem doppelten Satz von Daten aufrufen, wird eine Ausnahme ausgelöst, aber es ist ein ziemlich Standard InvalidOperationException, und es ist InnerExceptionnull ist. Wie kann ich doppelte Schlüsselverletzungen in Entity Framework Core erkennen?

-Update mit mehr Kontext (pun intended)

Die spezifische Verletzung ich zu fangen bin versucht,/erkennen, wenn eine Verbindung zwischen zwei Einheiten Hinzufügen (Team und Benutzer), die durch eine many-to verbunden ist -viele Beziehung.

System.InvalidOperationException: Die Instanz des Entitätstyps 'TeamUser' kann nicht überwacht werden, da bereits eine andere Instanz dieses Typs mit demselben Schlüssel verfolgt wird. Beim Hinzufügen neuer Entitäten wird für die meisten Schlüsseltypen ein eindeutiger temporärer Schlüsselwert erstellt, wenn kein Schlüssel festgelegt ist (d. H. Wenn der Schlüsseleigenschaft der Standardwert für seinen Typ zugewiesen ist). Wenn Sie explizit Schlüsselwerte für neue Entitäten festlegen, stellen Sie sicher, dass sie nicht mit vorhandenen Entitäten oder temporären Werten kollidieren, die für andere neue Entitäten generiert wurden. Achten Sie beim Anhängen vorhandener Entitäten darauf, dass nur eine Entitätsinstanz mit einem bestimmten Schlüsselwert an den Kontext angehängt ist.

Benutzer Entitätsklasse:

public class User 
{ 
    [Key] 
    public string Name { get; set; } 

    public ICollection<TeamUser> TeamUsers { get; set; } 
} 

Team-Entity-Klasse:

public class Team 
{ 
    [Key] 
    public string Id { get; set; } 

    [Required] 
    public string Name { get; set; } 

    public ICollection<Template> Templates { get; set; } 
    public ICollection<Checklist> Checklists { get; set; } 

    public ICollection<TeamUser> TeamUsers { get; set; } 
} 

TeamUser Entitätsklasse:

public class TeamUser 
{ 
    public string TeamId { get; set; } 
    public Team Team { get; set; } 

    public string UserName { get; set; } 
    public User User { get; set; } 
} 

Meine DbContext Unterklasse konfiguriert die many-to-many-Beziehung zwischen Teams und Benutzer:

protected override void OnModelCreating(ModelBuilder modelBuilder) 
{ 
    var teamUserEntity = modelBuilder.Entity<TeamUser>(); 

    teamUserEntity 
     .HasKey(tu => new { tu.TeamId, tu.UserName }); 

    teamUserEntity 
     .HasOne(tu => tu.Team) 
     .WithMany(t => t.TeamUsers) 
      .HasForeignKey(tu => tu.TeamId); 

    teamUserEntity 
     .HasOne(tu => tu.User) 
     .WithMany(u => u.TeamUsers) 
     .HasForeignKey(tu => tu.UserName); 
} 

EF Kern der TeamUser Tabelle erzeugt hat, wie folgt:

CREATE TABLE "TeamUser" (
    "TeamId" TEXT NOT NULL, 
    "UserName" TEXT NOT NULL, 
    CONSTRAINT "PK_TeamUser" PRIMARY KEY ("TeamId", "UserName"), 
    CONSTRAINT "FK_TeamUser_Teams_TeamId" FOREIGN KEY ("TeamId") REFERENCES "Teams" ("Id") ON DELETE CASCADE, 
    CONSTRAINT "FK_TeamUser_Users_UserName" FOREIGN KEY ("UserName") REFERENCES "Users" ("Name") ON DELETE CASCADE 
); 
CREATE INDEX "IX_TeamUser_UserName" ON "TeamUser" ("UserName"); 
+0

Haben Sie Ihre eindeutige Constraint-Einstellung in Ihrem db-Kontext eingerichtet? –

+0

Ich meine so etwas wie: entity.HasAlternateKey (p => neue {p.ProductName}). HasName ("U_ProductName"); –

+0

@Herzl Bitte überprüfen Sie die aktualisierte Frage, ich habe die relevanten Entity-Klassen und 'OnModelCreating' Methode hinzugefügt. –

Antwort

1

Der Grund, warum Sie nicht in der Lage sind zu erkennen, Die doppelte Schlüsselverletzung liegt daran, dass Sie eine einzelne Instanz von dbcontext verwenden, um doppelte Daten zu speichern. Lassen Sie mich mit einer Probe-Controller erklären:

MyController.cs

public class MyController : Controller 
{ 
    private readonly MyDbContext _context; 

    public MyController(MyDbContext context) 
    { 
     _context = context; 
    } 

    public IActionResult AddFirst() 
    { 
     var user = new User 
     { 
      Name = "Alice" 
     }; 
     _context.Users.Add(user); 

     var team = new Team 
     { 
      Id = "uniqueteamid", 
      Name = "A Team" 
     }; 
     _context.Teams.Add(team); 

     var teamuser1 = new TeamUser() 
     { 
      User = user, 
      Team = team 
     }; 
     _context.TeamUsers.Add(teamuser1); 

     _context.SaveChanges(); 

     return View(); 
    } 

    public IActionResult AddSecond() 
    { 
     var teamuser2 = new TeamUser() 
     { 
      UserName = "Alice", 
      TeamId = "uniqueteamid" 
     }; 
     _context.TeamUsers.Add(teamuser2); 

     _context.SaveChanges(); 

     return View(); 
    } 

    public IActionResult AddFirstAndSecond() 
    { 
     var user = new User 
     { 
      Name = "Bob" 
     }; 
     _context.Users.Add(user); 

     var team = new Team 
     { 
      Id = "anotherteamid", 
      Name = "B Team" 
     }; 
     _context.Teams.Add(team); 

     var teamuser1 = new TeamUser() 
     { 
      User = user, 
      Team = team 
     }; 
     _context.TeamUsers.Add(teamuser1); 

     var teamuser2 = new TeamUser() 
     { 
      User = user, 
      Team = team 
     }; 
     _context.TeamUsers.Add(teamuser2); 

     _context.SaveChanges(); 

     return View(); 
    } 

    public IActionResult AddFirstAndSecondAgain() 
    { 
     var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>(); 
     optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1;Trusted_Connection=True;MultipleActiveResultSets=true"); 

     using (var context = new MyDbContext(optionsBuilder.Options)) 
     { 
      var user = new User 
      { 
       Name = "Cat" 
      }; 
      context.Users.Add(user); 
      context.SaveChanges(); 
     } 

     using (var context = new MyDbContext(optionsBuilder.Options)) 
     { 
      var team = new Team 
      { 
       Id = "andanotherteamid", 
       Name = "C Team" 
      }; 
      context.Teams.Add(team); 
      context.SaveChanges(); 
     } 

     using (var context = new MyDbContext(optionsBuilder.Options)) 
     { 
      var teamuser1 = new TeamUser() 
      { 
       UserName = "Cat", 
       TeamId = "andanotherteamid" 
      }; 
      context.TeamUsers.Add(teamuser1); 
      context.SaveChanges(); 
     } 

     using (var context = new MyDbContext(optionsBuilder.Options)) 
     { 
      var teamuser2 = new TeamUser() 
      { 
       UserName = "Cat", 
       TeamId = "andanotherteamid" 
      }; 
      context.TeamUsers.Add(teamuser2); 
      context.SaveChanges(); 
     } 

     return View(); 
    } 
} 

In diesem Controller gibt es 4 Aktionsmethoden: AddFirst, AddSecond, AddFirstAndSecond und AddFirstAndSecondAgain.

Fall 1 (AddFirst und AddSecond):

AddFirst Angenommen wird zuerst genannt. Dadurch werden ein neuer Benutzer, ein neues Team und ein TeamUser erstellt. Wenn nun anschließend AddSecond aufgerufen wird, wird versucht, einen doppelten TeamUser hinzuzufügen und eine doppelte Schlüsselverletzung auszulösen. Der Grund dafür ist, dass der zweite Aufruf zum Einfügen eines doppelten TeamUsers eine andere Instanz von dbcontext verwendet als der erste Aufruf von TeamUser.

Fall 2 (AddFirstAndSecond):

Sie AddFirstAndSecond nennen Angenommen. Dies wird eine ungültige Operationsausnahme auslösen. Warum? Da Sie eine einzelne Instanz von dbcontext verwenden, um sowohl den ersten TeamUser als auch den zweiten doppelten TeamUser hinzuzufügen. Entity Framework Core verfolgt bereits den ersten TeamUser, sodass er den zweiten doppelten TeamUser nicht verfolgen kann.

Fall 3 (AddFirstAndSecondAgain):

Wenn Sie wirklich doppeltes TeamUser in einer einzigen Aktion Methode hinzufügen müssen, können Sie verschiedene Instanzen von DbContext verwenden müssen, wenn jeder TeamUser hinzufügen. Werfen Sie einen Blick auf AddFirstAndSecondAgain Aktionsmethode. Dadurch wird auch eine Ausnahme für doppelte Schlüsselverletzung ausgelöst, da Sie verschiedene Instanzen von dbcontext verwenden, um den ersten und zweiten doppelten TeamUser hinzuzufügen.

+0

Entschuldigung, aber ich folge nicht wirklich, was Sie vorschlagen, dass ich tun muss. Könntest du dir das [Beispielprojekt] (https://github.com/NxSoftware/EFCoreDuplicateRecord) ansehen, das ich nach Github geschoben habe, um noch ein paar Hinweise zu geben? –

+0

Ich habe meine Antwort aktualisiert. Ich hoffe es hilft. – kimbaudi

+0

Wenn Sie etwas zu DbContext hinzufügen, indem Sie die 'Add'-Methode verwenden, beginnt der Kontext mit der Verfolgung (um zu überwachen, was beim Speichern von' SaveChanges' gespeichert werden muss). Wenn Sie versuchen, dieselbe Instanz mehrmals zum Kontext hinzuzufügen, löst EF eine Ausnahme aus, die die neuen Entitäten nicht verfolgen kann. Es ist wie ein eindeutiger Constraint-Verstoß, der innerhalb verfolgter Entitäten in einer einzelnen DbContext-Instanz auftritt.Als Zeiger sollten Sie nach der Stelle suchen, an der die Ausnahme auftritt, und nach der Entität, die zweimal in derselben Kontextinstanz hinzugefügt wird. Das würde Ihnen helfen herauszufinden, was es zweimal hinzugefügt hat. – Smit

Verwandte Themen