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
Entity Framework Code First (六)存储过程的更多相关文章
- Entity Framework Code First执行SQL语句、视图及存储过程
1.Entity Framework Code First查询视图 Entity Framework Code First目前还没有特别针对View操作的方法,但对于可更新的视图,可以采用与Table ...
- Entity Framework Code First学习系列目录
Entity Framework Code First学习系列说明:开发环境为Visual Studio 2010 + Entity Framework 5.0+MS SQL Server 2012, ...
- Entity Framework Code First学习系列
Entity Framework Code First学习系列目录 Entity Framework Code First学习系列说明:开发环境为Visual Studio 2010 + Entity ...
- Entity Framework Code first(转载)
一.Entity Framework Code first(代码优先)使用过程 1.1Entity Framework 代码优先简介 不得不提Entity Framework Code First这个 ...
- Entity Framework Code First数据库连接
1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器->程序包管理器控制台,执行以下语句: PM> Insta ...
- Entity Framework Code First属性映射约定
Entity Framework Code First与数据表之间的映射方式有两种实现:Data Annotation和Fluent API.本文中采用创建Product类为例来说明tity Fram ...
- Entity Framework Code First关系映射约定
本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...
- Entity Framework Code First使用DbContext查询
DbContext.DbSet及DbQuery是Entity Framework Code First引入的3个新的类,其中DbContext用于保持数据库会话连接,实体变化跟踪及保存,DbSet用于 ...
- Entity Framework Code First添加修改及删除单独实体
对于一个单独实体的通常操作有3种:添加新的实体.修改实体以及删除实体. 1.添加新的实体 Entity Framework Code First添加新的实体通过调用DbSet.Add()方法来实现. ...
- Entity Framework Code First实体对象变动跟踪
Entity Framework Code First通过DbContext.ChangeTracker对实体对象的变动进行跟踪,实现跟踪的方式有两种:变动跟踪快照和变动跟踪代理. 变动跟踪快照:前面 ...
随机推荐
- POJ1129Channel Allocation[迭代加深搜索 四色定理]
Channel Allocation Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 14601 Accepted: 74 ...
- AC日记——统计单词数 openjudge 1.12 5
05:统计单词数 总时间限制: 1000ms 内存限制: 65536kB 描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次 ...
- [No000061]"别人"凭什么要帮你?&理解中国人的人际和谐&外人、自己人与另一半
你出身平凡家庭:你毕业于普通大学:你没有田晓霞这样的妻子或者普京这样的丈夫:在权力.金钱乃至能力积累上,你才刚刚上路.你很年轻,你渴望成功,那么,"别人"凭什么帮你? " ...
- iOS多线程之NSThread详解
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面.iOS多线程的使 ...
- java的string常用操作
import java.util.*; public class Demo3 { public static void main(String args[]){ String str = " ...
- javascript中的链表结构—双向链表
1.概念 上一个文章里我们已经了解到链表结构,链表的特点是长度不固定,不用担心插入新元素的时候新增位置的问题.插入一个元素的时候,只要找到插入点就可以了,不需要整体移动整个结构. 这里我们了解一下双向 ...
- C语言:枚举类型
整数常量的符号名称... #include <stdio.h> enum _bool {false,true}; int main(){ enum colors { red, orange ...
- php碎片
1.flock LOCK_EX 独占锁定 LOCK_SH 共享锁定 LOCK_UN 解除锁定 LOCK_NB 锁定但不堵塞进程,直接返回false 2. fseek SEEK_CUR SEEK_END ...
- 4816 江哥的dp题b
4816 江哥的dp题b 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 给出两个1-N的随机排列A,B.若 ...
- DefaultFilesMiddleware中间件如何显示默认页面
DefaultFilesMiddleware中间件如何显示默认页面 DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认 ...