EntityFramework和EntityFramework.Extended使用说明——性能,语法和产生的sql

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EntityFramework和EntityFramework.Extended使用说明——性能,语法和产生的sql相关的知识,希望对你有一定的参考价值。

环境说明:EntityFramework 6.1.3和.Net Framework4.5
性能注意事项:https://msdn.microsoft.com/zh-cn/library/cc853327.aspx
比较精髓的一点:查询执行的各个阶段中的准备查询,每个唯一查询一次。包括编写查询命令、基于模型和映射元数据生成命令树和定义所返回数据的形状的成本。 因为实体 SQL查询命令和 LINQ 查询现已缓存,所以,以后执行相同查询所需的时间较少。
如果有缓存的话,那么查询命令转成sql语句的性能会进一步提高,是不是ORM的效率更接近Ado.Net了呢?

性能注意点:此处参考了 http://www.cnblogs.com/jake1/archive/2013/04/25/3043664.html
a.在数据库里面分页
b.延迟加载要合理使用
c.需要连表的地方要连表查询
d.查询数据库的次数和发出的sql语句的数量和长度
e.NoTracking的使用

表ContactInfo,GroupInfo说明:

技术分享
CREATE TABLE [dbo].[ContactInfo](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [ContactId] [nvarchar](128) NOT NULL,
    [IsDelete] [int] NOT NULL,
    [Account] [nvarchar](64) NOT NULL,
    [ContactName] [nvarchar](50) NOT NULL,
    [CommonMobile] [nvarchar](50) NULL,
    [HeadPortrait] [nvarchar](256) NULL,
    [AttFile] [nvarchar](256) NULL,
    [GroupId] [int] NULL,
 CONSTRAINT [PK_ContactInfo] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[GroupInfo](
    [GroupId] [int] IDENTITY(1,1) NOT NULL,
    [GroupName] [nvarchar](300) NOT NULL,
 CONSTRAINT [PK_GroupInfo] PRIMARY KEY CLUSTERED 
(
    [GroupId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
表的说明

1.0 分页查询

c#语句:

var db = new PhoneBookEntities();
db.GroupInfo.Where(c => c.GroupName.Length>=2).OrderByDescending(c => c.GroupId).Skip(2).Take(3).ToArray();

sql语句:

SELECT TOP (3) 
[Filter1].[GroupId] AS [GroupId], 
[Filter1].[GroupName] AS [GroupName]
FROM 
( SELECT [Extent1].[GroupId] AS [GroupId], [Extent1].[GroupName] AS [GroupName], 
row_number() OVER (ORDER BY [Extent1].[GroupId] DESC) AS [row_number]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE (LEN([Extent1].[GroupName])) >= 2
) AS [Filter1]

WHERE [Filter1].[row_number] > 2
ORDER BY [Filter1].[GroupId] DESC

 

2.0 FirstOrDefault,First

c#语句:

var db = new PhoneBookEntities();
db.GroupInfo.FirstOrDefault(c => c.GroupId == 1);

sql语句:

SELECT TOP (1) 
[Extent1].[GroupId] AS [GroupId], 
[Extent1].[GroupName] AS [GroupName]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE 1 = [Extent1].[GroupId]

 

延迟加载:

var db = new PhoneBookEntities();
var ci = db.ContactInfo.FirstOrDefault(c => c.ID == 9);

/* 此时产生的sql:*/

SELECT TOP (1) 
[Extent1].[ID] AS [ID], 
[Extent1].[ContactId] AS [ContactId], 
[Extent1].[IsDelete] AS [IsDelete], 
[Extent1].[Account] AS [Account], 
[Extent1].[ContactName] AS [ContactName], 
[Extent1].[CommonMobile] AS [CommonMobile], 
[Extent1].[HeadPortrait] AS [HeadPortrait], 
[Extent1].[AttFile] AS [AttFile], 
[Extent1].[GroupId] AS [GroupId]
FROM [dbo].[ContactInfo] AS [Extent1]
WHERE 9 = [Extent1].[ID] 
var gn= ci.GroupInfo.GroupName;

/* (运行到此行c#代码才会)产生sql:*/

exec sp_executesql NSELECT 
[Extent1].[GroupId] AS [GroupId], 
[Extent1].[GroupName] AS [GroupName]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE [Extent1].[GroupId] = @EntityKeyValue1,N@EntityKeyValue1 int,@EntityKeyValue1=1

如果是一条数据,用延迟加载是OK的.如果上面查询有多条如10条结果,每条结果都使用到GroupInfo属性,那么一共会有11条sql请求.效率低.应该使用连表,一条sql搞定.写法如下.

第一种写法 Join:

那如果是多条数据,应使用预加载.
c#语句:

var db = new PhoneBookEntities();
var ci = db.ContactInfo.Where(c => c.ID >3).Join(db.GroupInfo,c=>c.GroupId,g=>g.GroupId,(c,g)=>new{c.ContactName,g.GroupName});
foreach (var item in ci)
{
    MessageBox.Show(item.ContactName + "->" + item.GroupName);
}

sql语句:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[ContactName] AS [ContactName], 
[Extent2].[GroupName] AS [GroupName]
FROM [dbo].[ContactInfo] AS [Extent1]
INNER JOIN [dbo].[GroupInfo] AS [Extent2] ON [Extent1].[GroupId] = [Extent2].[GroupId]
WHERE [Extent1].[ID] > 3

说明:GroupJoin的用法,和Join使用类似.区别在于第四个参数resultSelector.

Join的第四个参数是 Func<ContactInfo,GroupInfo,anonymous type>.
GroupJoin的第四个参数是 Func<ContactInfo,IEnumerable<GroupInfo>,anonymous type>.
使用场景:联系人和他的好友.联系人一张表,好友关系一张表.联系人表和好友关系表做连接,查出多个联系人数据(包含他的好友),就应该使用GroupJoin.
简单说:
public partial class ContactInfo
{
public int ID { get; set; }
public string ContactName { get; set; }
public Nullable<int> GroupId { get; set; }
public virtual GroupInfo GroupInfo { get; set; }
}
ContactInfo和GroupInfo一对一,该用Join;
如果是这种情况(仅仅是假设)
public partial class ContactInfo
{
public int ID { get; set; }
public string ContactName { get; set; }
public Nullable<int> GroupId { get; set; }
public virtual List<GroupInfo> GroupInfo { get; set; }
}
ContactInfo和GroupInfo一对多,该用GroupJoin;

第二种写法 Include:

注意:数据库设计ContactInfo,GroupInfo 要有主外键关系.
c#语句:

var db = new PhoneBookEntities();
var ci = db.ContactInfo.Include("GroupInfo").Where(c => c.ID > 3).Select(c => new { c.ContactName, c.GroupInfo.GroupName });
foreach (var item in ci)
{
    MessageBox.Show(item.ContactName + "->" + item.GroupName);
}

sql语句:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[ContactName] AS [ContactName], 
[Extent2].[GroupName] AS [GroupName]
FROM [dbo].[ContactInfo] AS [Extent1]
LEFT OUTER JOIN [dbo].[GroupInfo] AS [Extent2] ON [Extent1].[GroupId] = [Extent2].[GroupId]
WHERE [Extent1].[ID] > 3

3.0 Add

c#语句:

var db = new PhoneBookEntities();
var giModel = new GroupInfo();
giModel.GroupName = "Test";
db.GroupInfo.Add(giModel);
db.SaveChanges();
//Add方法之后,会把数据库表记录中的GroupId给giModel.

sql语句:

exec sp_executesql NINSERT [dbo].[GroupInfo]([GroupName])
VALUES (@0)
SELECT [GroupId]
FROM [dbo].[GroupInfo]
WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity(),N@0 nvarchar(300),@0=NTest

 

4.0 AddRange

c#语句:

var db = new PhoneBookEntities();
var gi = new GroupInfo[] { new GroupInfo() { GroupName = "g1" }, new GroupInfo() { GroupName = "g2" }, new GroupInfo() { GroupName = "g3" }, };
db.GroupInfo.AddRange(gi);
db.SaveChanges();

sql语句:

exec sp_executesql NINSERT [dbo].[GroupInfo]([GroupName])
VALUES (@0)
SELECT [GroupId]
FROM [dbo].[GroupInfo]
WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity(),N@0 nvarchar(300),@0=Ng1

exec sp_executesql NINSERT [dbo].[GroupInfo]([GroupName])
VALUES (@0)
SELECT [GroupId]
FROM [dbo].[GroupInfo]
WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity(),N@0 nvarchar(300),@0=Ng2

...

共执行3次.但是连接只打开关闭了1次.

5.0 Remove

c#语句:

var db = new PhoneBookEntities();
var ci= db.GroupInfo.FirstOrDefault(c => c.GroupId == 214);
db.GroupInfo.Remove(ci);
db.SaveChanges();

sql语句:

SELECT TOP (1) 
[Extent1].[GroupId] AS [GroupId], 
[Extent1].[GroupName] AS [GroupName]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE 214 = [Extent1].[GroupId]

exec sp_executesql NDELETE [dbo].[GroupInfo]
WHERE ([GroupId] = @0),N@0 int,@0=214

感觉繁琐啦?解决方案,1,自己定义方法;2,引用EntityFramework.Extended

来源https://www.nuget.org/packages/EntityFramework.Extended/或者在当前项目里,引用点击右键,选择管理NuGet程序包,联机搜索并下载.
引用之后操作就简单了.

c#语句:

var db = new PhoneBookEntities(); 
db.GroupInfo.Delete(c => c.GroupName == "Test");//过时的方法
db.GroupInfo.Where(c => c.GroupName == "Test").Delete();//如果查到的记录数为0,也不报错.上边的先查 FirstOrDefault 后删 Remove,你猜猜报错吗
db.SaveChanges();

 

sql语句:

DELETE [dbo].[GroupInfo]
FROM [dbo].[GroupInfo] AS j0 INNER JOIN (
SELECT 
[Extent1].[GroupId] AS [GroupId]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE NTest = [Extent1].[GroupName]
) AS j1 ON (j0.[GroupId] = j1.[GroupId])

一条sql语句搞定删除.

6.0 更新操作


基于EntityFramework.Extended的更新操作.
c#语句:

db.GroupInfo.Where(c => c.GroupName.Contains("g")).Update(c => new GroupInfo() { GroupName = c.GroupName+"!"});
//此处没有db.SaveChanges();,一样执行了操作.

sql语句:

UPDATE [dbo].[GroupInfo] SET 
[GroupName] = [GroupName] + N! 
FROM [dbo].[GroupInfo] AS j0 INNER JOIN (
SELECT 
[Extent1].[GroupId] AS [GroupId]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE [Extent1].[GroupName] LIKE N%g%
) AS j1 ON (j0.[GroupId] = j1.[GroupId])

也是一条sql语句搞定批量修改.

(注意:update语句中只set了GroupName字段;
不用EntityFramework.Extended,用EF的先查后改,sql语句也是只set了GroupName字段.
更特殊的情况,以下例子:

var gi = db.GroupInfo.FirstOrDefault(c => c.GroupId == 219 );//此条记录的GroupName为"Test"
gi.GroupName = "Test";
db.SaveChanges();

EF会自动优化,最终结果只有一个select语句,而没有update语句. 此处细节,赞!

对比NHibernate 4,以下两行代码产生的sql语句会set Product表的[所有]字段
var pl = session.Query<Product>().FirstOrDefault(c => c.Name == "cnblogs");
pl.Name = "ICE";
)
如果先查出来要更改的数据,再修改.也是可以的. 但是从效率考虑,不管是c#写法还是产生的sql语句,基于EntityFramework.Extended的更新操作更优.

7.0 EntityFramework.Extended中Future的使用

c#语句:

var db = new PhoneBookEntities();
var fci = db.ContactInfo.Where(c => c.ID > 1).FutureFirstOrDefault();
var fgi = db.GroupInfo.Where(c => c.GroupId > 2).FutureFirstOrDefault();
ContactInfo ci= fci.Value;
GroupInfo gi = fgi.Value;

//采用Future的写法,不会立即查询数据库.只要调用结果的任意一个 .ToList,.ToArray或者.Value ,才会查数据库.并且只发一个请求(Query #1 + Query #2 拼接好后发给数据库,一起执行语句).

sql语句:

-- Query #1

SELECT TOP (1) 
[Extent1].[ID] AS [ID], 
[Extent1].[ContactId] AS [ContactId], 
[Extent1].[IsDelete] AS [IsDelete], 
[Extent1].[Account] AS [Account], 
[Extent1].[ContactName] AS [ContactName], 
[Extent1].[CommonMobile] AS [CommonMobile], 
[Extent1].[HeadPortrait] AS [HeadPortrait], 
[Extent1].[AttFile] AS [AttFile], 
[Extent1].[GroupId] AS [GroupId]
FROM [dbo].[ContactInfo] AS [Extent1]
WHERE [Extent1].[ID] > 1;

-- Query #2

SELECT TOP (1) 
[Extent1].[GroupId] AS [GroupId], 
[Extent1].[GroupName] AS [GroupName]
FROM [dbo].[GroupInfo] AS [Extent1]
WHERE [Extent1].[GroupId] > 2;

标注:EntityFramework.Extended相关部分参考了 顾振印的博文: http://www.cnblogs.com/GuZhenYin/p/5482288.html

8.0 AsNoTracking

c#语句:

var db = new PhoneBookEntities();
var gi = db.GroupInfo.FirstOrDefault(c => c.GroupId == 219);
MessageBox.Show(db.Entry(gi).State.ToString());//Unchanged
var giAnk = db.GroupInfo.AsNoTracking().FirstOrDefault(c => c.GroupId == 219);
MessageBox.Show(db.Entry(giAnk).State.ToString());//Detached

 

以上是关于EntityFramework和EntityFramework.Extended使用说明——性能,语法和产生的sql的主要内容,如果未能解决你的问题,请参考以下文章

EntityFramework和EntityFramework.Extended使用说明——性能,语法和产生的sql

基于EntityFramework的权限的配置和验证

EntityFramework优缺点

Xamarin 表单和 EntityFramework 属性兼容性

EntityFramework 7 更名为EntityFramework Core(预发布状态)

带有标识 2 和 EntityFramework 6(Oracle)的 ASP.NET Core MVC