Entity Framework Code First (六)存儲過程

  聲明:本文只針對 EF6+

  默認情況下,Code First 對實體進行插入、更新、刪除操作是直接在表上進行的,從 EF6 開始你可以選擇使用存儲過程(Stored Procedures

 

簡單實體映射 Basic Entity Mapping

  注意:本文將使用 Fluent API 來配置使用存儲過程

複製代碼
public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }

    [Timestamp]
    public byte[] Timestamp { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}
複製代碼
modelBuilder.Entity<Blog>()
    .MapToStoredProcedures();

  上面的代碼執行後,Code First 將利用某些約定在數據庫中生成一些存儲過程:

  • 生成三個存儲過程,名稱分別爲<type_name>_Insert<type_name>_Update<type_name>_Delete (本例爲 Blog_Insert, Blog_Update, Blog_Delete);
  • 參數名對應於屬性名 (注意:如果在 property上使用 HasColumnName() 或者 Column attribute 來重命名,那麼參數也將使用這個重命名過的名稱 );
  • The insert stored procedure 爲每一個屬性都有一個參數,除了那些標記爲數據庫產生的(identity or computed),返回結果爲那些標記爲數據庫產生的屬性列;
  • The update stored procedure 爲每一個屬性都有一個參數,除了那些標記爲數據庫產生且模式爲 computed 的。一些併發標記的需要一個代表原始值的參數(更多信息請參考 Concurrency Tokens section)。返回值爲那些 computed property 的列;
  • The delete stored procedure 參數爲實體主鍵(或者組合主鍵),此外也需要爲每一個獨立關聯的外鍵準備一個參數(指那些沒有在實體上定義相應外鍵屬性的關係),一些併發標記的需要一個代表原始值的參數(更多信息請參考 Concurrency Tokens section

  存儲過程的具體內容如下

複製代碼
CREATE PROCEDURE [dbo].[Blog_Insert]
    @Name [nvarchar](max),
    @Url [nvarchar](max)
AS
BEGIN
    INSERT [dbo].[Blog]([Name], [Url])
    VALUES (@Name, @Url)
    
    DECLARE @BlogId int
    SELECT @BlogId = [BlogId]
    FROM [dbo].[Blog]
    WHERE @@ROWCOUNT > 0 AND [BlogId] = scope_identity()
    
    SELECT t0.[BlogId], t0.[Timestamp]
    FROM [dbo].[Blog] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[BlogId] = @BlogId
END


CREATE PROCEDURE [dbo].[Blog_Update]
    @BlogId [int],
    @Name [nvarchar](max),
    @Url [nvarchar](max),
    @Timestamp_Original [rowversion]
AS
BEGIN
    UPDATE [dbo].[Blog]
    SET [Name] = @Name, [Url] = @Url
    WHERE (([BlogId] = @BlogId) AND (([Timestamp] = @Timestamp_Original) OR ([Timestamp] IS NULL AND @Timestamp_Original IS NULL)))
    
    SELECT t0.[Timestamp]
    FROM [dbo].[Blog] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[BlogId] = @BlogId
END


CREATE PROCEDURE [dbo].[Blog_Delete]
    @BlogId [int],
    @Timestamp_Original [rowversion]
AS
BEGIN
    DELETE [dbo].[Blog]
    WHERE (([BlogId] = @BlogId) AND (([Timestamp] = @Timestamp_Original) OR ([Timestamp] IS NULL AND @Timestamp_Original IS NULL)))
END
複製代碼

Overriding the Defaults

  你可以重寫部分或全部的默認配置

重寫存儲過程名

  重寫 update 存儲過程名

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

  重寫所有的存儲過程名

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

  效果與下面使用一樣 lambda block syntax

複製代碼
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"));
    });
複製代碼

重寫存儲過程參數名

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

  上面所有的操作都是可組合的和鏈式的,如如下示例重命名所有的存儲過程及其參數名

複製代碼
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 [dbo].[Blog]([Name], [Url])
    VALUES (@Name, @Url)
    
    DECLARE @BlogId int
    SELECT @BlogId = [BlogId]
    FROM [dbo].[Blog]
    WHERE @@ROWCOUNT > 0 AND [BlogId] = scope_identity()
    
    SELECT t0.[BlogId] AS generated_blog_identity, t0.[Timestamp]
    FROM [dbo].[Blog] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[BlogId] = @BlogId
END
複製代碼

 

無外鍵關係 Relationships Without a Foreign Key in the Class

  如果實體上有定義外鍵屬性,那麼其重命名操作與其它屬性無異。如果實體間的關係存在,但是並沒定義外鍵屬性,那麼默認的參數名爲 <navigation_property_name>_<primary_key_name>

複製代碼
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; } 
}
複製代碼

  如上類定義將會導致在 Insert 和 Update Post 存儲過程中產生參數 Blog_BlogId

複製代碼
CREATE PROCEDURE [dbo].[Post_Insert]
    @Title [nvarchar](max),
    @Content [nvarchar](max),
    @Blog_BlogId [int]
AS
BEGIN
    INSERT [dbo].[Post]([Title], [Content], [Blog_BlogId])
    VALUES (@Title, @Content, @Blog_BlogId)
    
    DECLARE @PostId int
    SELECT @PostId = [PostId]
    FROM [dbo].[Post]
    WHERE @@ROWCOUNT > 0 AND [PostId] = scope_identity()
    
    SELECT t0.[PostId]
    FROM [dbo].[Post] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[PostId] = @PostId
END


CREATE PROCEDURE [dbo].[Post_Update]
    @PostId [int],
    @Title [nvarchar](max),
    @Content [nvarchar](max),
    @Blog_BlogId [int]
AS
BEGIN
    UPDATE [dbo].[Post]
    SET [Title] = @Title, [Content] = @Content, [Blog_BlogId] = @Blog_BlogId
    WHERE ([PostId] = @PostId)
END
複製代碼

Overriding the Defaults  

  通過提供主鍵屬性給 Parameter 方法,你可以重命名在類中沒有包含的外鍵參數名

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

  生成的 Insert 存儲過程如下

複製代碼
CREATE PROCEDURE [dbo].[Post_Insert]
    @Title [nvarchar](max),
    @Content [nvarchar](max),
    @blog_id [int]
AS
BEGIN
    INSERT [dbo].[Post]([Title], [Content], [Blog_BlogId])
    VALUES (@Title, @Content, @blog_id)
    
    DECLARE @PostId int
    SELECT @PostId = [PostId]
    FROM [dbo].[Post]
    WHERE @@ROWCOUNT > 0 AND [PostId] = scope_identity()
    
    SELECT t0.[PostId]
    FROM [dbo].[Post] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[PostId] = @PostId
END
複製代碼

  如果在從屬實體(dependent entity)上沒有導航屬性(navigation property)(例如 Post.Blog),你可以使用 Navigation(原文是 Association 方法,但筆者發現根本沒有此方法) 方法來確定另一端的關係然後爲相應的主鍵(或組合主鍵)配置參數

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}
modelBuilder.Entity<Post>()
  .MapToStoredProcedures(s => 
      s.Insert(i => i.Navigation<Blog>(
      b => b.Posts,
      c => c.Parameter(b => b.BlogId, "blog_id"))));

  產生的存儲過程如下

複製代碼
CREATE PROCEDURE [dbo].[Post_Insert]
    @Title [nvarchar](max),
    @Content [nvarchar](max),
    @blog_id [int]
AS
BEGIN
    INSERT [dbo].[Post]([Title], [Content], [Blog_BlogId])
    VALUES (@Title, @Content, @blog_id)
    
    DECLARE @PostId int
    SELECT @PostId = [PostId]
    FROM [dbo].[Post]
    WHERE @@ROWCOUNT > 0 AND [PostId] = scope_identity()
    
    SELECT t0.[PostId]
    FROM [dbo].[Post] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[PostId] = @PostId
END
複製代碼

 

併發標記 Concurrency Tokens

  Update 和 Delete 存儲過程也需要處理併發問題:

  • 如果實體包含併發標記,存儲過程可選擇地有一個 Output 參數用於返回更新/刪除的列的數目,這樣一個參數必須通過方法 RowsAffectedParameter 配置(注意:EF 默認使用 ExecuteNonQuery 的返回值來確定有多少行受影響,如果你在存儲過程中執行邏輯操作將導致 ExecuteNonQuery 的返回值是錯誤的,此時指定一個行影響的 Output 參數是有必要的);
  • 每一個併發標記,都有一個參數,命名爲 <property_name>_Original (如 Timestamp_Original),這個參數將傳遞屬性的原始值 - 從數據庫查詢的值
    • 數據庫計算(computed)的併發標記 - 例如 timestamp - 將有一個原始值參數;
    • 非計算屬性的併發標記在 Update 存儲過程中也有一個更新值參數,只是使用之前討論過的爲新值的命名約定。此處的一個例子爲用 Blog 的 URL 作爲併發標記,更新後的新值是必須的因爲這個值可能被你的代碼更新成另一個新值(不像 Timestamp 標記只能被數據庫更新

  

  一個計算併發標記(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; }
}
複製代碼
modelBuilder.Entity<Blog>()
    .MapToStoredProcedures();
複製代碼
CREATE PROCEDURE [dbo].[Blog_Update]
    @BlogId [int],
    @Name [nvarchar](max),
    @Url [nvarchar](max),
    @Timestamp_Original [rowversion]
AS
BEGIN
    UPDATE [dbo].[Blog]
    SET [Name] = @Name, [Url] = @Url
    WHERE (([BlogId] = @BlogId) AND (([Timestamp] = @Timestamp_Original) OR ([Timestamp] IS NULL AND @Timestamp_Original IS NULL)))
    
    SELECT t0.[Timestamp]
    FROM [dbo].[Blog] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[BlogId] = @BlogId
END
複製代碼

  

  一個非計算併發標記(URL)例子

複製代碼
 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
BEGIN
    UPDATE [dbo].[Blog]
    SET [Name] = @Name, [Url] = @Url
    WHERE (([BlogId] = @BlogId) AND (([Url] = @Url_Original) OR ([Url] IS NULL AND @Url_Original IS NULL)))
END
複製代碼

Overriding the Defaults

  使用 RowsAffectedParameter 方法

modelBuilder.Entity<Blog>()
  .MapToStoredProcedures(s => 
      s.Update(u => u.RowsAffectedParameter("rows_affected")));
複製代碼
CREATE PROCEDURE [dbo].[Blog_Update]
    @BlogId [int],
    @Name [nvarchar](max),
    @Url [nvarchar](max),
    @Url_Original [nvarchar](max),
    @rows_affected [int] OUT
AS
BEGIN
    UPDATE [dbo].[Blog]
    SET [Name] = @Name, [Url] = @Url
    WHERE (([BlogId] = @BlogId) AND (([Url] = @Url_Original) OR ([Url] IS NULL AND @Url_Original IS NULL)))
    
    SET @rows_affected = @@ROWCOUNT
END
複製代碼

  

  對於計算併發標記 - 只有原始值需要傳遞 - 我們可以使用標準的 Pameter 方法來重命名參數名

 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")));

 

N:N 關係 Many to Many Relationships

  定義如下類

複製代碼
public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public virtual ICollection<Tag> Tags { get; set; }
}

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

    public virtual ICollection<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 (本例中爲 PostTag_Insert PostTag_Delete);
  • 參數爲每一類型的主鍵(或組合主鍵),命名爲 <type_name>_<property_name> (本例爲 Post_PostId 和 Tag_TagId
複製代碼
CREATE PROCEDURE [dbo].[PostTag_Insert]
    @Post_PostId [int],
    @Tag_TagId [int]
AS
BEGIN
    INSERT [dbo].[PostTag]([Post_PostId], [Tag_TagId])
    VALUES (@Post_PostId, @Tag_TagId)
END


CREATE PROCEDURE [dbo].[PostTag_Delete]
    @Post_PostId [int],
    @Tag_TagId [int]
AS
BEGIN
    DELETE [dbo].[PostTag]
    WHERE (([Post_PostId] = @Post_PostId) AND ([Tag_TagId] = @Tag_TagId))
END
複製代碼

Overriding the Defaults

  可以像配置實體存儲過程一樣來配置此存儲過程和參數的名稱

複製代碼
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")));
複製代碼

  產生的存儲過程如下

複製代碼
CREATE PROCEDURE [dbo].[add_post_tag]
    @post_id [int],
    @tag_id [int]
AS
BEGIN
    INSERT [dbo].[PostTag]([Post_PostId], [Tag_TagId])
    VALUES (@post_id, @tag_id)
END


CREATE PROCEDURE [dbo].[remove_post_tag]
    @post_id [int],
    @tag_id [int]
AS
BEGIN
    DELETE [dbo].[PostTag]
    WHERE (([Post_PostId] = @post_id) AND ([Tag_TagId] = @tag_id))
END
複製代碼

  原文鏈接:http://msdn.microsoft.com/en-us/data/dn468673

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章