In Code First gespeicherte Prozeduren zum Einfügen, Aktualisieren und Löschen
Hinweis
Nur EF6 und höher: Die Features, APIs usw., die auf dieser Seite erläutert werden, wurden in Entity Framework 6 eingeführt. Wenn Sie eine frühere Version verwenden, gelten manche Informationen nicht.
Standardmäßig konfiguriert Code First alle Entitäten so, dass Befehle zum Einfügen, Aktualisieren und Löschen 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 gespeicherter Prozeduren unter Verwendung der Fluent-API entscheiden:
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures();
Dadurch verwendet Code First einige Konventionen, 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)
- Die 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 speichergenerierte Eigenschaft zurückgeben.
- Die gespeicherte Update-Prozedur verfügt über einen Parameter für jede Eigenschaft, mit Ausnahme derjenigen, die als speichergeneriertes Muster von „berechnet“ gekennzeichnet sind. Einige Parallelitätstoken erfordern einen Parameter für den ursprünglichen Wert. Weitere Informationen dazu finden Sie weiter unten im Abschnitt Parallelitätstoken. Die gespeicherte Prozedur sollte ein Resultset mit einer Spalte für jede berechnete Eigenschaft zurückgeben.
- Die gespeicherte Delete-Prozedur sollte einen Parameter für den Schlüsselwert der Entität aufweisen (oder mehrere Parameter, wenn die Entität einen zusammengesetzten Schlüssel besitzt). Darüber hinaus sollte die Delete-Prozedur auch Parameter für alle Fremdschlüssel mit unabhängiger Zuordnung 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 dazu finden Sie weiter unten im Abschnitt Parallelitätstoken.
Verwendet man beispielsweise die folgende Klasse:
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 ändern, die standardmäßig konfiguriert wurden.
Sie können den Namen einer oder mehrerer gespeicherter Prozeduren ändern. In diesem Beispiel wird die gespeicherte Update-Prozedur 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, Sie können aber 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 Update-Prozedur umbenannt:
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.Parameter(b => b.BlogId, "blog_id")));
Diese Aufrufe sind alle verkettet und können zusammengesetzt werden. Hier 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, das von der Datenbank generierte Werte enthält:
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 der Klasse (unabhängige Zuordnungen)
Wenn eine Fremdschlüsseleigenschaft in der Klassendefinition enthalten ist, kann der entsprechende Parameter auf die gleiche Weise wie jede andere Eigenschaft. Wenn eine Beziehung ohne Fremdschlüsseleigenschaft in der Klasse vorhanden ist, lautet der Standardname des Parameters <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 werden würde:
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 Standardwerte
Sie können Parameter für Fremdschlüssel ändern, die nicht in der Klasse enthalten sind, indem Sie in der Parameter-Methode den Pfad zur Primärschlüsseleigenschaft 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 der oder 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 Parallelitäten berücksichtigen:
- 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 (d. h. der betroffenen Zeilen) zurückgibt. 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 Ausgabeparameters für betroffene 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 am Ende der Ausführung (aus der Sicht von EF) falsch wäre. - Für jedes Parallelitätstoken gibt es einen Parameter namens <property_name>_Original (z. B. Timestamp_Original). Dieser wird an den ursprünglichen Wert dieser Eigenschaft, d. h. als er von der Datenbank abgefragt wurde, übergeben.
- Parallelitätstoken, die von der Datenbank berechnet werden, z. B. Zeitstempel, haben nur einen ursprünglichen Value-Parameter.
- Nicht berechnete Eigenschaften, die als Parallelitätstoken festgelegt werden, weisen in der Update-Prozedur auch einen Parameter für den neuen Wert auf. Hierbei werden die bereits für neue Werte diskutierte Benennungskonventionen angewendet. Ein Beispiel für ein solches Token wäre die Verwendung einer Blog-URL als Parallelitätstoken. Der neue Wert ist erforderlich, da er von Ihrem Code auf einen neuen Wert aktualisiert werden kann (im Gegensatz zu einem Zeitstempeltoken, das nur von der Datenbank aktualisiert wird).
Hier eine Beispielklasse und die gespeicherte Update-Prozedur mit einem Zeitstempel-Parallelitätstoken:
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
Hier eine Beispielklasse und die gespeicherte Update-Prozedur 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 Parameter für betroffene Zeilen einführen:
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.RowsAffectedParameter("rows_affected")));
Bei berechneten Parallelitätstoken, bei denen nur der ursprüngliche Wert übergeben wird, können Sie einfach den Umbenennungsmechanismus für den Standardparameter verwenden, um den Parameter in 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, bei denen sowohl der ursprüngliche als auch der neue Wert übergeben werden, können Sie eine Überladung des Parameters verwenden, wodurch 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")));
m:n-Beziehungen
Wir verwenden in diesem Abschnitt die folgenden Klassen als Beispiel:
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; }
}
m:n-Beziehungen können gespeicherten Prozeduren mithilfe 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 die folgende gespeicherte Prozedurform verwendet:
- Zwei gespeicherte Prozeduren mit dem Namen <type_one><type_two>_Insert und <type_one><type_two>_Delete (z. B. PostTag_Insert und PostTag_Delete).
- Die Parameter sind der oder die Schlüsselwerte für jeden Typ. Der Name der einzelnen Parameter lautet dann <type_name>_<property_name> (z. B. Post_PostId und Tag_TagId).
Hier folgen beispielhafte gespeicherte Prozeduren zum Einfügen und Aktualisieren:
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")));