Code First での挿入、更新、削除にストアド プロシージャを使用する
Note
EF6 以降のみ - このページで説明する機能、API などは、Entity Framework 6 で導入されました。 以前のバージョンを使用している場合、一部またはすべての情報は適用されません。
既定では、テーブルへの直接アクセスを使用して挿入、更新、削除の各コマンドを実行するよう、すべてのエンティティが Code First によって構成されます。 EF6 以降では、モデル内の一部または全部のエンティティについてストアド プロシージャを使用するように Code First モデルを構成できます。
基本的なエンティティ マッピング
ストアド プロシージャを使った挿入、更新、削除を選択するには、Fluent API を使用します。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures();
その結果、Code First により、いくつかの規約を使用して、しかるべき構造のストアド プロシージャがデータベースに作成されるようになります。
- <type_name>_Insert、<type_name>_Update、<type_name>_Delete という名前の 3 つのストアド プロシージャ (例: Blog_Insert、Blog_Update、Blog_Delete)。
- パラメーター名はプロパティ名に対応します。
Note
HasColumnName() または Column 属性を使用して、特定のプロパティに対応する列の名前を変更した場合、その名前が、プロパティ名に代わってパラメーターに使用されます。
- 挿入ストアド プロシージャは、ストア生成 (ID または計算) としてマークされたものを除く、すべてのプロパティのパラメーターを含みます。 ストアド プロシージャは、ストア生成された各プロパティに対応する列を含んだ結果セットを返す必要があります。
- 更新ストアド プロシージャは、"Computed" というストア生成パターンでマークされたものを除く、すべてのプロパティのパラメーターを含みます。 一部のコンカレンシー トークンには、元の値のパラメーターが必要です。詳細については、後述の「コンカレンシー トークン」セクションを参照してください。 ストアド プロシージャは、計算された各プロパティに対応する列を含んだ結果セットを返す必要があります。
- 削除ストアド プロシージャには、エンティティのキー値のパラメーター (エンティティに複合キーがある場合は複数のパラメーター) が必要です。 さらに、削除プロシージャには、ターゲット テーブル上の独立した関連付けの外部キーに使用するパラメーターも必要となります (エンティティで宣言された対応する外部キーのプロパティがないリレーションシップ)。 一部のコンカレンシー トークンには、元の値のパラメーターが必要です。詳細については、後述の「コンカレンシー トークン」セクションを参照してください。
例として、次のクラスを使用します。
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
}
既定のストアド プロシージャは次のようになります。
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
既定の構成をオーバーライドする
既定で構成された要素の一部または全部をオーバーライドすることができます。
1 つまたは複数のストアド プロシージャの名前を変更できます。 この例では、更新ストアド プロシージャのみ、名前を変更しています。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.HasName("modify_blog")));
この例では、3 つすべてのストアド プロシージャの名前を変更しています。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.HasName("modify_blog"))
.Delete(d => d.HasName("delete_blog"))
.Insert(i => i.HasName("insert_blog")));
これらの例では、呼び出しをチェーンさせていますが、ラムダ ブロック構文を使用することもできます。
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"));
});
この例では、更新ストアド プロシージャの BlogId プロパティのパラメーターの名前を変更しています。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.Parameter(b => b.BlogId, "blog_id")));
これらの呼び出しは、すべてチェーン可能かつコンポーザブルです。 3 つすべてのストアド プロシージャとそのパラメーターの名前を変更する例を次に示します。
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")));
データベースで生成された値を含む結果セット内の列の名前を変更することもできます。
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
クラスに外部キーのないリレーションシップ (独立した関連付け)
クラス定義に外部キーのプロパティが存在する場合、他のあらゆるプロパティと同じように、対応するパラメーターの名前を変更できます。 外部キーのプロパティをクラスに持たないリレーションシップが存在する場合、既定のパラメーター名は <navigation_property_name>_<primary_key_name> になります。
たとえば、次のクラス定義がある場合、Posts を挿入および更新するためのストアド プロシージャのパラメーター名は Blog_BlogId となります。
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; }
}
既定の構成をオーバーライドする
クラスに含まれていない外部キーのパラメーターを変更するには、Parameter メソッドに主キー プロパティのパスを渡します。
modelBuilder
.Entity<Post>()
.MapToStoredProcedures(s =>
s.Insert(i => i.Parameter(p => p.Blog.BlogId, "blog_id")));
依存エンティティにナビゲーション プロパティがない場合 (Post.Blog プロパティがないなど)、Association メソッドを使用してリレーションシップのもう一方の End を識別したうえで、それぞれのキー プロパティに対応するパラメーターを構成できます。
modelBuilder
.Entity<Post>()
.MapToStoredProcedures(s =>
s.Insert(i => i.Navigation<Blog>(
b => b.Posts,
c => c.Parameter(b => b.BlogId, "blog_id"))));
コンカレンシー トークン
更新と削除のストアド プロシージャには、コンカレンシーの扱いも必要になる場合があります。
- エンティティにコンカレンシー トークンが含まれている場合、更新または削除された行数 (処理行数) を返す出力パラメーターを、必要に応じてストアド プロシージャに設けることができます。 そのようなパラメーターは、RowsAffectedParameter メソッドを使用して構成する必要があります。
既定では、ExecuteNonQuery からの戻り値を使用して、処理行数が割り出されます。 実行の最後に ExecuteNonQuery の戻り値が (EF の観点から) 不正確になるようなロジックをストアド プロシージャの中で実行する場合に、処理行数の出力パラメーターを指定すると効果的です。 - コンカレンシー トークンにはそれぞれ、<property_name>_Original という名前のパラメーターがあります (例: Timestamp_Original)。 そこには、このプロパティの元の値、つまりデータベースに照会したときの値が渡されます。
- データベースによって計算されるコンカレンシー トークン (タイムスタンプなど) に含まれるのは、元の値のパラメーターだけです。
- 更新プロシージャでは、コンカレンシー トークンとして設定された非計算プロパティにも、新しい値のパラメーターがあります。 新しい値には、既に説明した名前付け規則が使用されます。 そのようなトークンの例としては、Blog の URL をコンカレンシー トークンとして使用していて、(データベースによってのみ更新される Timestamp トークンとは異なり) コードによって新しい値に更新される可能性があるために、新しい値が必須であるようなケースが該当します。
これは、タイムスタンプをコンカレンシー トークンとするクラスと更新ストアド プロシージャの例です。
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
次に示したのは、非計算コンカレンシー トークンを使用したクラスと更新ストアド プロシージャの例です。
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
既定の構成をオーバーライドする
必要に応じて、処理行数パラメーターを導入できます。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.RowsAffectedParameter("rows_affected")));
データベースによって計算されるコンカレンシー トークンでは、渡されるのは元の値のみです。この場合は、標準的なパラメーター名変更メカニズムを使用して、元の値のパラメーター名を変更できます。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s =>
s.Update(u => u.Parameter(b => b.Timestamp, "blog_timestamp")));
非計算コンカレンシー トークンでは、元の値と新しい値の両方が渡されます。この場合は、Parameter のオーバーロードを使用して、各パラメーターの名前を指定できます。
modelBuilder
.Entity<Blog>()
.MapToStoredProcedures(s => s.Update(u => u.Parameter(b => b.Url, "blog_url", "blog_original_url")));
多対多リレーションシップ
このセクションでは、例として次のクラスを使用します。
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; }
}
多対多リレーションシップは、次の構文でストアド プロシージャにマッピングできます。
modelBuilder
.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.MapToStoredProcedures();
他の構成を指定しなかった場合、既定により、次のストアド プロシージャ構造が使用されます。
- <type_one><type_two>_Insert と <type_one><type_two>_Delete という名前の 2 つのストアド プロシージャ (例: PostTag_Insert および PostTag_Delete)。
- パラメーターがそれぞれの型のキー値になります。 各パラメーターの名前は <type_name>_<property_name> になります (例: Post_PostId、Tag_TagId)。
挿入と更新のストアド プロシージャの例を次に示します。
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
既定の構成をオーバーライドする
プロシージャとパラメーターの名前は、エンティティのストアド プロシージャと同様の方法で構成できます。
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")));
.NET