Freigeben über


Code-First-Insert-, -Update- und -Delete-gespeicherte Prozeduren

Hinweis

Nur EF6 und weiter – Die Funktionen, APIs und mehr, die auf dieser Seite besprochen werden, wurden in Entity Framework 6 eingeführt. Wenn Sie eine frühere Version verwenden, gelten einige oder alle Informationen nicht.

Standardmäßig konfiguriert Code First alle Entitäten so, dass Einfüge-, Aktualisierungs- und Löschbefehle mithilfe des direkten Tabellenzugriffs ausgeführt werden. Ab EF6 können Sie Ihr Code First-Modell so konfigurieren, dass gespeicherte Prozeduren für einige oder alle Entitäten in Ihrem Modell verwendet werden.

Grundlegende Entitätszuordnung

Sie können sich für das Einfügen, Aktualisieren und Löschen mithilfe der Fluent-API für gespeicherte Prozeduren entscheiden.

modelBuilder
  .Entity<Blog>()
  .MapToStoredProcedures();

Dies bewirkt, dass Code First einige Konventionen verwendet, um die erwartete Form der gespeicherten Prozeduren in der Datenbank zu erstellen.

  • Drei gespeicherte Prozeduren namens <type_name>_Insert, <type_name>_Update und <type_name>_Delete (z. B. Blog_Insert, Blog_Update und Blog_Delete).
  • Parameternamen entsprechen den Eigenschaftennamen.

    Hinweis

    Wenn Sie HasColumnName() oder das Column-Attribut verwenden, um die Spalte für eine bestimmte Eigenschaft umzubenennen, wird dieser Name anstelle des Eigenschaftennamens für Parameter verwendet.

  • Die gespeicherte Insert-Prozedur verfügt über einen Parameter für jede Eigenschaft, mit Ausnahme derjenigen, die als speichergeneriert (Identität oder berechnet) gekennzeichnet sind. Die gespeicherte Prozedur sollte ein Resultset mit einer Spalte für jede generierte Speichereigenschaft zurückgeben.
  • Die gespeicherte Aktualisierungsprozedur verfügt über einen Parameter für jede Eigenschaft, mit Ausnahme derjenigen, die mit einem generierten Speichermuster von "Computed" gekennzeichnet sind. Einige Parallelitätstoken erfordern einen Parameter für den ursprünglichen Wert. Weitere Informationen finden Sie im Abschnitt "Parallelitätstoken" weiter unten. Die gespeicherte Prozedur sollte ein Resultset mit einer Spalte für jede berechnete Eigenschaft zurückgeben.
  • Die gespeicherte Prozedur zum Löschen sollte einen Parameter für den Schlüsselwert der Entität aufweisen (oder mehrere Parameter, wenn die Entität einen zusammengesetzten Schlüssel aufweist). Darüber hinaus sollte die Löschprozedur parameter für alle unabhängigen Zuordnungs-Fremdschlüssel in der Zieltabelle aufweisen (Beziehungen, die keine entsprechenden Fremdschlüsseleigenschaften haben, die in der Entität deklariert sind). Einige Parallelitätstoken erfordern einen Parameter für den ursprünglichen Wert. Weitere Informationen finden Sie im Abschnitt "Parallelitätstoken" weiter unten.

Verwenden der folgenden Klasse als Beispiel:

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }  
}

Die standardmäßig gespeicherten Prozeduren wären:

CREATE PROCEDURE [dbo].[Blog_Insert]  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
BEGIN
  INSERT INTO [dbo].[Blogs] ([Name], [Url])
  VALUES (@Name, @Url)

  SELECT SCOPE_IDENTITY() AS BlogId
END
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId;
CREATE PROCEDURE [dbo].[Blog_Delete]  
  @BlogId int  
AS  
  DELETE FROM [dbo].[Blogs]
  WHERE BlogId = @BlogId

Überschreiben der Standardwerte

Sie können einen Teil oder alle elemente außer Kraft setzen, die standardmäßig konfiguriert wurden.

Sie können den Namen einer oder mehrerer gespeicherter Prozeduren ändern. In diesem Beispiel wird die gespeicherte Aktualisierungsprozedur nur umbenannt.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog")));

In diesem Beispiel werden alle drei gespeicherten Prozeduren umbenannt.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog"))  
     .Delete(d => d.HasName("delete_blog"))  
     .Insert(i => i.HasName("insert_blog")));

In diesen Beispielen werden die Aufrufe miteinander verkettet, Aber Sie können auch Lambda-Blocksyntax verwenden.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    {  
      s.Update(u => u.HasName("modify_blog"));  
      s.Delete(d => d.HasName("delete_blog"));  
      s.Insert(i => i.HasName("insert_blog"));  
    });

In diesem Beispiel wird der Parameter für die BlogId-Eigenschaft in der gespeicherten Updateprozedur umbenannt.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.Parameter(b => b.BlogId, "blog_id")));

Diese Aufrufe sind alle verkettet und kompositorierbar. Nachfolgend sehen Sie ein Beispiel, in dem alle drei gespeicherten Prozeduren und ihre Parameter umbenannt werden.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog")  
                   .Parameter(b => b.BlogId, "blog_id")  
                   .Parameter(b => b.Name, "blog_name")  
                   .Parameter(b => b.Url, "blog_url"))  
     .Delete(d => d.HasName("delete_blog")  
                   .Parameter(b => b.BlogId, "blog_id"))  
     .Insert(i => i.HasName("insert_blog")  
                   .Parameter(b => b.Name, "blog_name")  
                   .Parameter(b => b.Url, "blog_url")));

Sie können auch den Namen der Spalten im Resultset ändern, die datenbankgenerierte Werte enthalten.

modelBuilder
  .Entity<Blog>()
  .MapToStoredProcedures(s =>
    s.Insert(i => i.Result(b => b.BlogId, "generated_blog_identity")));
CREATE PROCEDURE [dbo].[Blog_Insert]  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
BEGIN
  INSERT INTO [dbo].[Blogs] ([Name], [Url])
  VALUES (@Name, @Url)

  SELECT SCOPE_IDENTITY() AS generated_blog_id
END

Beziehungen ohne Fremdschlüssel in einer Klasse (unabhängige Assoziationen)

Wenn eine Fremdschlüsseleigenschaft in der Klassendefinition enthalten ist, kann der entsprechende Parameter auf die gleiche Weise wie jede andere Eigenschaft umbenannt werden. Wenn eine Beziehung ohne Fremdschlüssel-Eigenschaft in der Klasse existiert, lautet der Standard-Parameter-Name <navigation_property_name>_<primary_key_name>.

Die folgenden Klassendefinitionen würden beispielsweise dazu führen, dass ein Blog_BlogId Parameter in den gespeicherten Prozeduren zum Einfügen und Aktualisieren von Beiträgen erwartet wird.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }

  public List<Post> Posts { get; set; }  
}  

public class Post  
{  
  public int PostId { get; set; }  
  public string Title { get; set; }  
  public string Content { get; set; }  

  public Blog Blog { get; set; }  
}

Überschreiben der Standardeinstellungen

Sie können Parameter für Fremdschlüssel ändern, die nicht in der Klasse enthalten sind, indem Sie den Pfad zur Primärschlüsseleigenschaft der Parameter-Methode angeben.

modelBuilder
  .Entity<Post>()  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.Parameter(p => p.Blog.BlogId, "blog_id")));

Wenn Sie keine Navigationseigenschaft für die abhängige Entität haben (d. h. keine Post.Blog Eigenschaft), können Sie die Association-Methode verwenden, um das andere Ende der Beziehung zu identifizieren und dann die Parameter zu konfigurieren, die den einzelnen Schlüsseleigenschaften entsprechen.

modelBuilder
  .Entity<Post>()  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.Navigation<Blog>(  
      b => b.Posts,  
      c => c.Parameter(b => b.BlogId, "blog_id"))));

Parallelitätstoken

Das Aktualisieren und Löschen gespeicherter Prozeduren muss möglicherweise auch mit Parallelität umgehen:

  • Wenn die Entität Parallelitätstoken enthält, kann die gespeicherte Prozedur optional über einen Ausgabeparameter verfügen, der die Anzahl der aktualisierten/gelöschten Zeilen zurückgibt (zeilen betroffen). Ein solcher Parameter muss mit der RowsAffectedParameter-Methode konfiguriert werden.
    Standardmäßig verwendet EF den Rückgabewert von ExecuteNonQuery, um zu bestimmen, wie viele Zeilen betroffen waren. Das Angeben eines betroffenen Ausgabeparameters für Zeilen ist nützlich, wenn Sie eine Logik in Ihrem Sproc ausführen, die dazu führen würde, dass der Rückgabewert von ExecuteNonQuery (aus EF-Sicht) am Ende der Ausführung falsch ist.
  • Für jedes Parallelitätstoken gibt es einen Parameter namens <property_name>_Original (z. B. Timestamp_Original). Der ursprüngliche Wert dieser Eigenschaft wird übergeben – der Wert, wenn er aus der Datenbank abgerufen wird.
    • Parallelitätstoken, die von der Datenbank berechnet werden, z. B. Zeitstempel, weisen nur einen ursprünglichen Wertparameter auf.
    • Nicht berechnete Eigenschaften, die als Parallelitätstoken festgelegt werden, weisen auch einen Parameter für den neuen Wert in der Aktualisierungsprozedur auf. Dies verwendet die bereits für neue Werte diskutierten Benennungskonventionen. Ein Beispiel für ein solches Token wäre die Verwendung einer Blog-URL als Parallelitätstoken, der neue Wert ist erforderlich, da dies von Ihrem Code auf einen neuen Wert aktualisiert werden kann (im Gegensatz zu einem Zeitstempeltoken, das nur von der Datenbank aktualisiert wird).

Dies ist eine Beispielklasse und ein gespeichertes Prozedur-Update mit einem Zeitstempel-Konkurrenztoken.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }  
  [Timestamp]
  public byte[] Timestamp { get; set; }
}
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max),
  @Timestamp_Original rowversion  
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId AND [Timestamp] = @Timestamp_Original

Nachfolgend sehen Sie eine Beispielklasse und eine gespeicherte Prozedur für die Aktualisierung mit einem nicht berechneten Parallelitätstoken.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  [ConcurrencyCheck]
  public string Url { get; set; }  
}
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max),
  @Url_Original nvarchar(max),
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId AND [Url] = @Url_Original

Überschreiben der Standardwerte

Optional können Sie einen betroffenen Zeilenparameter einführen.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.RowsAffectedParameter("rows_affected")));

Bei Token für die durch die Datenbank berechnete Synchronisierung, bei denen nur der ursprüngliche Wert übergeben wird, können Sie einfach den Standardmechanismus zur Umbenennung von Parametern verwenden, um den Parameter für den ursprünglichen Wert umzubenennen.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.Parameter(b => b.Timestamp, "blog_timestamp")));

Bei nicht berechneten Parallelitätstoken – wobei sowohl der ursprüngliche als auch der neue Wert übergeben werden – können Sie eine Überladung des Parameters verwenden, mit der Sie einen Namen für jeden Parameter angeben können.

modelBuilder
 .Entity<Blog>()
 .MapToStoredProcedures(s => s.Update(u => u.Parameter(b => b.Url, "blog_url", "blog_original_url")));

Viele-zu-viele-Beziehungen

Wir verwenden die folgenden Klassen als Beispiel in diesem Abschnitt.

public class Post  
{  
  public int PostId { get; set; }  
  public string Title { get; set; }  
  public string Content { get; set; }  

  public List<Tag> Tags { get; set; }  
}  

public class Tag  
{  
  public int TagId { get; set; }  
  public string TagName { get; set; }  

  public List<Post> Posts { get; set; }  
}

Viele zu vielen Beziehungen können gespeicherten Prozeduren mit der folgenden Syntax zugeordnet werden.

modelBuilder  
  .Entity<Post>()  
  .HasMany(p => p.Tags)  
  .WithMany(t => t.Posts)  
  .MapToStoredProcedures();

Wenn keine andere Konfiguration angegeben wird, wird standardmäßig das folgende gespeicherte Prozedur-Shape verwendet.

  • Zwei gespeicherte Prozeduren mit den Namen <type_one><type_two>_Insert und <type_one><type_two>_Delete (z. B. PostTag_Insert und PostTag_Delete).
  • Die Parameter sind die Schlüsselwerte für jeden Typ. Der Name jedes Parameters ist <type_name>_<property_name> (z. B. Post_PostId und Tag_TagId).

Hier sind Beispiel zum Einfügen und Aktualisieren gespeicherter Prozeduren.

CREATE PROCEDURE [dbo].[PostTag_Insert]  
  @Post_PostId int,  
  @Tag_TagId int  
AS  
  INSERT INTO [dbo].[Post_Tags] (Post_PostId, Tag_TagId)   
  VALUES (@Post_PostId, @Tag_TagId)
CREATE PROCEDURE [dbo].[PostTag_Delete]  
  @Post_PostId int,  
  @Tag_TagId int  
AS  
  DELETE FROM [dbo].[Post_Tags]    
  WHERE Post_PostId = @Post_PostId AND Tag_TagId = @Tag_TagId

Überschreiben der Standardwerte

Die Prozedur- und Parameternamen können auf ähnliche Weise wie gespeicherte Entitätsprozeduren konfiguriert werden.

modelBuilder  
  .Entity<Post>()  
  .HasMany(p => p.Tags)  
  .WithMany(t => t.Posts)  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.HasName("add_post_tag")  
                   .LeftKeyParameter(p => p.PostId, "post_id")  
                   .RightKeyParameter(t => t.TagId, "tag_id"))  
     .Delete(d => d.HasName("remove_post_tag")  
                   .LeftKeyParameter(p => p.PostId, "post_id")  
                   .RightKeyParameter(t => t.TagId, "tag_id")));