仅限 EF6 及更高版本 - 此页面中讨论的功能、API 等已引入实体框架 6。 如果使用的是早期版本,则部分或全部信息不适用。

默认情况下,Code First 会将所有实体配置为使用直接表访问来执行插入、更新和删除命令。 从 EF6 开始,你可以将 Code First 模型配置为对模型中的部分或所有实体使用存储过程。

基本实体映射

你可以选择通过 Fluent API 使用存储过程进行插入、更新和删除。

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

这样做会导致 Code First 使用一些约定来生成数据库中存储过程的预期形状。

  • 三个名为 <type_name>_Insert<type_name>_Update<type_name>的存储过程_Delete (例如Blog_Insert、Blog_Update和Blog_Delete) 。
  • 参数名称对应于属性名称。

    如果使用 HasColumnName() 或 Column 特性重命名给定属性的相应列,则此名称用于参数而不是属性名称。

  • 插入存储过程将为每个属性提供一个参数,标记为存储生成(标识或计算)的属性除外。 该存储过程应该返回一个结果集,其中每个存储生成的属性都有一列。
  • 更新存储过程将为每个属性提供一个参数,标记为“计算”存储生成模式的属性除外。 某些并发标记需要原始值的参数,有关详细信息,请参阅下面的“并发标记”部分。 该存储过程应该返回一个结果集,其中每个计算属性都有一列。
  • 删除存储过程应为实体的键值提供一个参数(如果实体具有复合键,则应该提供多个参数)。 此外,删除过程还应该为目标表上的任何独立关联外键(未在实体中声明相应外键属性的关系)提供参数。 某些并发标记需要原始值的参数,有关详细信息,请参阅下面的“并发标记”部分。
  • 使用以下类作为示例:

    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)  
    BEGIN
      INSERT INTO [dbo].[Blogs] ([Name], [Url])
      VALUES (@Name, @Url)
      SELECT SCOPE_IDENTITY() AS BlogId
    CREATE PROCEDURE [dbo].[Blog_Update]  
      @BlogId int,  
      @Name nvarchar(max),  
      @Url nvarchar(max)  
      UPDATE [dbo].[Blogs]
      SET [Name] = @Name, [Url] = @Url     
      WHERE BlogId = @BlogId;
    CREATE PROCEDURE [dbo].[Blog_Delete]  
      @BlogId int  
      DELETE FROM [dbo].[Blogs]
      WHERE BlogId = @BlogId
    

    替代默认值

    你可以替代默认配置的部分或全部值。

    你可以更改一个或多个存储过程的名称。 此示例仅重命名更新存储过程。

    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 块语法。

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

    这些调用都是可链接、可组合的。 下面的示例将重命名所有三个存储过程及其参数。

    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)  
    BEGIN
      INSERT INTO [dbo].[Blogs] ([Name], [Url])
      VALUES (@Name, @Url)
      SELECT SCOPE_IDENTITY() AS generated_blog_id
    

    在类中没有外键的关系(独立关联)

    当类定义中包含外键属性时,可以像任何其他属性一样重命名相应的参数。 如果类中不存在没有外键属性的关系,则默认参数名称 <为 navigation_property_name>_<primary_key_name>

    例如,以下类定义将导致存储过程中出现一个用于插入和更新 Post 的 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 方法标识关系的另一端,然后配置与每个键属性对应的参数。

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

    更新和删除存储过程可能还需要处理并发操作:

  • 如果实体包含并发标记,存储过程可以选择提供一个输出参数,用于返回更新/删除的行数(受影响的行数)。 此类参数必须使用 RowsAffectedParameter 方法配置。
    默认情况下,EF 使用 ExecuteNonQuery 的返回值来确定受影响的行数。 如果在 sproc 中执行任何导致 ExecuteNonQuery 的返回值在执行结束时不正确(从 EF 的角度来看)的逻辑,则指定 rows affected 输出参数很有用。
  • 对于每个并发令牌,将有一个名为 <property_name>的参数_Original (例如Timestamp_Original ) 。 系统会向其传递此属性的原始值 - 从数据库查询时的值。
  • 由数据库计算的并发标记(例如时间戳)将只有原始值参数。
  • 设置为并发标记的非计算属性还将具有更新过程中新值的参数。 对于新值,将使用已经讨论过的命名约定。 此类标记的一个示例是使用 Blog 的 URL 作为并发标记,新值是必需的,因为该标记可以通过代码更新为新值(与仅由数据库更新的时间戳标记不同)。
  • 下面是使用时间戳并发标记的示例类和更新存储过程。

    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  
      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),
      UPDATE [dbo].[Blogs]
      SET [Name] = @Name, [Url] = @Url     
      WHERE BlogId = @BlogId AND [Url] = @Url_Original
    

    替代默认值

    你可以选择引入 rows affected 参数。

    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 (例如PostTag_Insert和PostTag_Delete) 。
  • 对于每种类型,参数将为键值。 每个参数的名称type_name<>_<property_name> (例如Post_PostId和Tag_TagId) 。
  • 下面是插入和更新存储过程的示例。

    CREATE PROCEDURE [dbo].[PostTag_Insert]  
      @Post_PostId int,  
      @Tag_TagId int  
      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  
      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")));