zoukankan      html  css  js  c++  java
  • 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.

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

  • 相关阅读:
    关于js计算非等宽字体宽度的方法
    [NodeJs系列]聊一聊BOM
    Vue.js路由管理器 Vue Router
    vue 实践技巧合集
    微任务、宏任务与Event-Loop
    事件循环(EventLoop)的学习总结
    Cookie、Session和LocalStorage
    MySQL 树形结构 根据指定节点 获取其所在全路径节点序列
    MySQL 树形结构 根据指定节点 获取其所有父节点序列
    MySQL 创建函数报错 This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators
  • 原文地址:https://www.cnblogs.com/anyihen/p/12819081.html
Copyright © 2011-2022 走看看