要专业系统地学习EF前往《你必须掌握的Entity Framework 6.x与Core 2.0》这本书的作者(汪鹏,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/
EF6的基础知识就算学完了,书中提供了一个基础篇的实战训练,后面就是进阶内容。市面上专讲EF的书籍就两本吧,《你必须掌握的Entity Framework6.x与Core 2.0》这本可以说很难得了,还是很不错的,值得入手。
这里主要讲一些其他应该注意到的问题。
导航属性与外键属性作为筛选条件查询的区别
分页查询:先筛选后分页、先分页后筛选,执行的SQL语句大不同
语义可空:C#中的空与数据库中的空等价问题
调用表值函数
日期操作应该注意的问题
导航属性与外键属性作为筛选条件查询的区别
我针对产品表进行查询,我想查询某个订单的产品,我是应该根据导航属性来筛选还是外键属性来筛选呢?
order、product model 注意:BaseEntity不属于EF三大继承策略的任何一种


// 基类
public class BaseEntity
{
public BaseEntity()
{
this.Id = Guid.NewGuid().ToString();
this.AddTime = DateTime.Now;
}
public string Id { get; set; }
public DateTime AddTime { get; set; }
}
// 订单
public class Order:BaseEntity
{
public string OrderNO { get; set; }
public string Description { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
// 产品
public class Product : BaseEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Unit { get; set; }
public string FK_OrderId { get; set; }
public virtual Order Order { get; set; }
}
映射配置


protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().ToTable("tb_Orders")
.HasMany(x => x.Products)
.WithRequired(x => x.Order)
.HasForeignKey(x => x.FK_OrderId);
modelBuilder.Entity<Product>().ToTable("tb_Products");
// 移除表名复数契约
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
表结构、数据如下
根据导航属性来筛选


// 根据导航属性筛选
var res = ctx.Products.Where(x => x.Order != null).ToList();
Console.WriteLine(JsonConvert.SerializeObject(res, set));
Sql执行情况如下
表里面有六个产品,EF执行了七条SQL语句,第一条,查询出所有的产品,然后逐条产品去筛选
根据外键属性来筛选
View Code
SQL执情况和上面差不多,也是七条语句
问题在于这里,如果延迟加载被关闭了,那么根据导航属于查询是无效的。因为关闭了延迟加载,导航属性是Null


// 禁用延迟加载
this.Configuration.LazyLoadingEnabled = false;
就执行了一条SQL语句,查询所有产品


var res = ctx.Products.Where(x => x.Order != null).ToList();
// SELECT
// [Extent1].[Id] AS[Id],
// [Extent1].[Name] AS[Name],
// [Extent1].[Price] AS[Price],
// [Extent1].[Unit] AS[Unit],
// [Extent1].[FK_OrderId] AS[FK_OrderId],
// [Extent1].[AddTime]
// AS[AddTime]
//FROM[dbo].[tb_Products] AS[Extent1]
所以,应该按照外键属性来筛选比导航属性要好
分页查询:先筛选后分页、先分页后筛选,执行的SQL语句大不同
我们当然知道分页 ,当然是应该先把数据筛选、处理好,最后再来分页,这就跟先穿袜子后穿鞋子一样自然
这里看看,先分页和后分页,EF执行的SQL语句有何不同
先筛选再分页


// 查询价格小于50的产品
var res = ctx.Products.Where(x => x.Price < 50).OrderBy(x => x.Price).Skip(1).Take(5).ToList();
Console.WriteLine(JsonConvert.SerializeObject(res,set));


SELECT
[Extent1].[Id] AS[Id],
[Extent1].[Name] AS[Name],
[Extent1].[Price] AS[Price],
[Extent1].[Unit] AS[Unit],
[Extent1].[FK_OrderId] AS[FK_OrderId],
[Extent1].[AddTime]
AS[AddTime]
FROM[dbo].[tb_Products]
AS[Extent1]
WHERE[Extent1].[Price] < cast(50 as decimal(18))
ORDER BY row_number() OVER(ORDER BY [Extent1].[Price] ASC)
OFFSET 1 ROWS FETCH NEXT 5 ROWS ONLY
先分页再筛选


var res = ctx.Products.OrderBy(x => x.Price).Skip(1).Take(3).Where(x => x.Price < 50).ToList();


SELECT
[Limit1].[Id] AS[Id],
[Limit1].[Name] AS[Name],
[Limit1].[Price] AS[Price],
[Limit1].[Unit] AS[Unit],
[Limit1].[FK_OrderId] AS[FK_OrderId],
[Limit1].[AddTime]
AS[AddTime]
FROM(SELECT[Extent1].[Id] AS[Id], [Extent1].[Name] AS[Name], [Extent1].[Price] AS[Price], [Extent1].[Unit] AS[Unit], [Extent1].[FK_OrderId] AS[FK_OrderId], [Extent1].[AddTime] AS [AddTime]
FROM [dbo].[tb_Products] AS [Extent1]
ORDER BY row_number() OVER (ORDER BY [Extent1].[Price] ASC)
OFFSET 1 ROWS FETCH NEXT 3 ROWS ONLY
) AS[Limit1]
WHERE[Limit1].[Price] < cast(50 as decimal(18))
ORDER BY[Limit1].[Price] ASC
明显后面一种的更复杂、难读,原因只是我们得查询方法的位置变了一下
不得不说,LINQ为我们提供了强大的、面向对象的数据查询方法,如果不去关注真正的SQL执行情况,性能问题就在一点点增长。
语义可空
什么意思,就是C#中的空与数据库的空的等价问题,代码一贴就懂了
我要按照Name属性来查询产品


var res = ctx.Products.Where(x => x.Name == "洗发水").ToList();


SELECT
[Extent1].[Id] AS[Id],
[Extent1].[Name] AS[Name],
[Extent1].[Price] AS[Price],
[Extent1].[Unit] AS[Unit],
[Extent1].[FK_OrderId] AS[FK_OrderId],
[Extent1].[AddTime]
AS[AddTime]
FROM[dbo].[tb_Products]
AS[Extent1]
WHERE N'洗发水' = [Extent1].[Name]
但是现在这样做,我声明一个变量来保存“洗发水”


string name = "洗发水";
var res = ctx.Products.Where(x => x.Name == name).ToList();
在我们看来应该没有区别,但是真的有区别


SELECT
[Extent1].[Id] AS[Id],
[Extent1].[Name] AS[Name],
[Extent1].[Price] AS[Price],
[Extent1].[Unit] AS[Unit],
[Extent1].[FK_OrderId] AS[FK_OrderId],
[Extent1].[AddTime]
AS[AddTime]
FROM[dbo].[tb_Products]
AS[Extent1]
WHERE([Extent1].[Name] = @p__linq__0) OR(([Extent1].[Name] IS NULL) AND(@p__linq__0 IS NULL))
那么,我们可以通过在上下文构造函数里面设置一下,针对这种情况让EF能够生成更简单的SQL语句


public class EFDbContext:DbContext
{
public EFDbContext()
{
this.Configuration.UseDatabaseNullSemantics = true;
}
}
表值函数
我们怎样在EF中调用自定义的SQL函数呢?
我来一个返回所有产品的函数


create function func_getProducts()
returns @rtProducts table
(
Id nvarchar(36),
[Name] nvarchar(50),
Price decimal(18,2),
Unit nvarchar(10),
FK_OrderId nvarchar(36),
AddTime datetime
)
as
begin
insert @rtProducts
select Id,[Name],Price,Unit,FK_OrderId,AddTime from tb_Products
return
end
然后调用SqlQuery()方法就行了


var res = ctx.Database.SqlQuery<Product>("select *from func_getProducts()").ToListAsync().Result;
日期操作
如果我想要再products中查询,通过计算得到一个新列,比如说是产品添加时间和当前时间的差距,可能会这样写


var product = ctx.Products.Select(x => new { Time = DateTime.Now - x.AddTime });
但是不行啊,报错信息如下:
System.ArgumentException: DbArithmeticExpression arguments must have a numeric common type.
System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.CreateArithmetic(DbExpressionKind kind, DbExpression left, DbExpression right)
这种情况我们就需要SqlFunctions类中的方法来


var products = ctx.Products.Select(x => new { Time = SqlFunctions.DateDiff("DAY",x.AddTime,DateTime.Now)});
这样写就行了,但是如果这样


var products = ctx.Products.Select(x => new { Time = SqlFunctions.DateDiff("DAY",x.AddTime,DateTime.Now.AddDays(-2))});
就报错了:
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.
他说该方法不能转换为存储表达式。这一点也需要注意。
原文出处:https://www.cnblogs.com/jinshan-go/p/10283216.html
来源:oschina
链接:https://my.oschina.net/u/4354993/blog/3270841