• EF6学习笔记十三:基础知识完结,零碎问题补缺


    要专业系统地学习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.

     他说该方法不能转换为存储表达式。这一点也需要注意。

  • 相关阅读:
    async 和 await
    C#中lock死锁
    Attribute特性
    数据库优化
    EF(ORM)
    依赖注入
    面向接口编程
    EF乐观锁与悲观锁
    为什么要使用RESTFUL风格?
    cloudsim 3.0.3下载与安装教程
  • 原文地址:https://www.cnblogs.com/anyihen/p/12819081.html
Copyright © 2020-2023  润新知