要专业系统地学习 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; }
- }
- View Code
映射配置
- 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);
- }
- View Code
表结构, 数据如下
根据导航属性来筛选
- // 根据导航属性筛选
- var res = ctx.Products.Where(x => x.Order != null).ToList();
- Console.WriteLine(JsonConvert.SerializeObject(res, set));
- View Code
Sql 执行情况如下
表里面有六个产品, EF 执行了七条 SQL 语句, 第一条, 查询出所有的产品, 然后逐条产品去筛选
根据外键属性来筛选
- // 根据外键属性筛选
- var res = ctx.Products.Where(x => !string.IsNullOrEmpty(x.FK_OrderId)).ToList();
- Console.WriteLine(JsonConvert.SerializeObject(res, set));
- View Code
SQL 执情况和上面差不多, 也是七条语句
问题在于这里, 如果延迟加载被关闭了, 那么根据导航属于查询是无效的. 因为关闭了延迟加载, 导航属性是 Null
- // 禁用延迟加载
- this.Configuration.LazyLoadingEnabled = false;
- View Code
就执行了一条 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]
- View Code
所以, 应该按照外键属性来筛选比导航属性要好
导航属性与外键属性作为筛选条件查询的区别
我们当然知道分页 , 当然是应该先把数据筛选, 处理好, 最后再来分页, 这就跟先穿袜子后穿鞋子一样自然
这里看看, 先分页和后分页, 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));
- View Code
- 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
- View Code
先分页再筛选
- var res = ctx.Products.OrderBy(x => x.Price).Skip(1).Take(3).Where(x => x.Price <50).ToList();
- View Code
- 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
- View Code
明显后面一种的更复杂, 难读, 原因只是我们得查询方法的位置变了一下
不得不说, LINQ 为我们提供了强大的, 面向对象的数据查询方法, 如果不去关注真正的 SQL 执行情况, 性能问题就在一点点增长.
语义可空
什么意思, 就是 C# 中的空与数据库的空的等价问题, 代码一贴就懂了
我要按照 Name 属性来查询产品
- var res = ctx.Products.Where(x => x.Name == "洗发水").ToList();
- View Code
- 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]
- View Code
但是现在这样做, 我声明一个变量来保存 "洗发水"
- string name = "洗发水";
- var res = ctx.Products.Where(x => x.Name == name).ToList();
- View Code
在我们看来应该没有区别, 但是真的有区别
- 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))
- View Code
那么, 我们可以通过在上下文构造函数里面设置一下, 针对这种情况让 EF 能够生成更简单的 SQL 语句
- public class EFDbContext:DbContext
- {
- public EFDbContext()
- {
- this.Configuration.UseDatabaseNullSemantics = true;
- }
- }
- View Code
表值函数
我们怎样在 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
- View Code
然后调用 SqlQuery()方法就行了
- var res = ctx.Database.SqlQuery<Product>("select *from func_getProducts()").ToListAsync().Result;
- View Code
日期操作
如果我想要再 products 中查询, 通过计算得到一个新列, 比如说是产品添加时间和当前时间的差距, 可能会这样写
- var product = ctx.Products.Select(x => new {
- Time = DateTime.Now - x.AddTime
- });
- View Code
但是不行啊, 报错信息如下:
- 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)
- });
- View Code
这样写就行了, 但是如果这样
- var products = ctx.Products.Select(x => new {
- Time = SqlFunctions.DateDiff("DAY",x.AddTime,DateTime.Now.AddDays(-2))
- });
- View Code
就报错了:
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