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 N‘SELECT [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 N‘INSERT [dbo].[GroupInfo]([GroupName]) VALUES (@0) SELECT [GroupId] FROM [dbo].[GroupInfo] WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity()‘,N‘@0 nvarchar(300)‘,@0=N‘Test‘
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 N‘INSERT [dbo].[GroupInfo]([GroupName]) VALUES (@0) SELECT [GroupId] FROM [dbo].[GroupInfo] WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity()‘,N‘@0 nvarchar(300)‘,@0=N‘g1‘ exec sp_executesql N‘INSERT [dbo].[GroupInfo]([GroupName]) VALUES (@0) SELECT [GroupId] FROM [dbo].[GroupInfo] WHERE @@ROWCOUNT > 0 AND [GroupId] = scope_identity()‘,N‘@0 nvarchar(300)‘,@0=N‘g2‘ ...
共执行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 N‘DELETE [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 N‘Test‘ = [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
Xamarin 表单和 EntityFramework 属性兼容性