zoukankan      html  css  js  c++  java
  • EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解

    EF Core中:

    • 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体。
    • 此外调用Queryable.Join方法返回的匿名类型也不会被DbContext所跟踪(实测调用Queryable.Join方法返回EF Core中的实体类型也不会被DbContext所跟踪)。

    Queryable.Count等聚合方法和Queryable.Join方法返回的结果不会被跟踪,原因是因为这两种方法返回的结果类型并没有被DbContext的OnModelCreating方法映射为实体,所以DbContext自然就不会去跟踪这两种方法返回的结果。

    RelationalQueryableExtensions.FromSql方法

    RelationalQueryableExtensions.FromSql方法的签名如下:

    public static IQueryable<TEntity> FromSql<TEntity>([NotNullAttribute] this IQueryable<TEntity> source, [NotParameterized] RawSqlString sql, [NotNullAttribute] params object[] parameters) where TEntity : class;

    可以看到FromSql方法其实是IQueryable<TEntity>类型的扩展方法,由于其返回的也是IQueryable<TEntity>类型,所以我们在使用FromSql方法时还可以结合其它Linq方法,例如下面的示例中,我们在FromSql方法后还使用了Linq中的Count方法来做聚合查询:

    var users = dbContext.User.FromSql<User>("select * from [MD].[User]").Count();

    这时FromSql方法传入的SQL语句会作为子查询,我们可以通过EF Core的后台日志看到生成的SQL语句如下:

    =============================== EF Core log started ===============================
    Executed DbCommand (58ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
    SELECT COUNT(*)
    FROM (
        select * from [MD].[User]
    ) AS [u]
    =============================== EF Core log finished ===============================

    此外因为FromSql方法是IQueryable<TEntity>类型的扩展方法,所以我们也可以在FromSql方法前使用其它Linq方法,例如下面的例子中我们在FromSql方法前使用Where方法来查询User表中Username不为null的行:

    var users = dbContext.User.Where(e => e.Username != null).FromSql<User>("select * from [MD].[User]").Count();

    我们可以通过EF Core的后台日志看到生成的SQL语句如下:

    =============================== EF Core log started ===============================
    Executed DbCommand (68ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
    SELECT COUNT(*)
    FROM (
        select * from [MD].[User]
    ) AS [e]
    WHERE [e].[Username] IS NOT NULL
    =============================== EF Core log finished ===============================

    我们可以看到,EF Core生成的后台SQL语句在外层查询上加了一个Where条件来过滤Username不为null的行。

    FromSql方法还可以直接查询数据库中的存储过程,例如我们现在有一个数据库存储过程叫SP_GetUsers,其中只有一个简单的User表查询,定义如下:

    CREATE PROCEDURE [MD].[SP_GetUsers]
    AS
    BEGIN
        select * from [MD].[User]
    END
    GO

    我们可以使用FromSql方法调用存储过程SP_GetUsers来返回User表的三行数据,如下所示:

    var users = dbContext.User.FromSql<User>("exec [MD].[SP_GetUsers]").ToList();

    可以看到FromSql方法最后成功返回了三个User实体。

    也可以在FromSql方法调用存储过程时使用其它Linq方法,例如下面我们在FromSql方法后使用了Count聚合查询:

    var users = dbContext.User.FromSql<User>("exec [MD].[SP_GetUsers]").Count();

    不过这个时候,我们可以通过下面EF Core的后台日志看到,其做SQL查询的时候只是调用了存储过程,并没有做Count聚合查询,说明聚合查询Count是EF Core将数据库的数据加载到内存中的实体对象后再做的,没有在数据库层面做Count聚合查询。

    =============================== EF Core log started ===============================
    Executed DbCommand (62ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
    exec [MD].[SP_GetUsers]
    =============================== EF Core log finished ===============================

    FromSql方法也支持在查询时传入参数,示例如下:

    var users = dbContext.User.FromSql<User>("select * from [MD].[User] Where Username={0} AND DataStatus={1}", "Jim", 1).ToList();

    我们可以通过EF Core的后台日志看到生成的SQL语句如下:

    =============================== EF Core log started ===============================
    Executed DbCommand (151ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='0']
    select * from [MD].[User] Where Username=@p0 AND DataStatus=@p1
    =============================== EF Core log finished ===============================

      

    FromSql方法返回的实体对象会被DbContext所跟踪

    FromSql方法返回的实体对象(该对象的类型有被DbContext的OnModelCreating方法映射为实体),是会被DbContext所跟踪的,比如调用下面FromSql方法中的SQL会返回三行User数据生成三个User实体,这三个User实体是存在于DbContext中被跟踪的实体集合中的。

    var users = dbContext.User.FromSql<User>("select u1.* from [MD].[User] as u1 inner join [MD].[User] as u2 on u1.UserCode=u2.UserCode").ToList();

    我们可以通过EF Core的后台日志看到生成的SQL语句如下:

    =============================== EF Core log started ===============================
    Executed DbCommand (55ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
    select u1.* from [MD].[User] as u1 inner join [MD].[User] as u2 on u1.UserCode=u2.UserCode
    =============================== EF Core log finished ===============================

    FromSql方法中SQL语句查询的列顺序可以和实体类的属性顺序不一样

    例如我们现在在SQL Server数据库中有一个表Language,其有三个列定义如下:

    CREATE TABLE [MD].[Language](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [LanguageCode] [nvarchar](20) NULL,
        [LanguageName] [nvarchar](50) NULL,
     CONSTRAINT [PK_Language] 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],
     CONSTRAINT [IX_Language] UNIQUE NONCLUSTERED 
    (
        [LanguageCode] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    然后其生成的EF Core实体类Language如下,类中三个属性的顺序和Language表中三个列的顺序相同:

    public partial class Language
    {
        public int Id { get; set; }
        public string LanguageCode { get; set; }
        public string LanguageName { get; set; }
    }

    然后我们使用EF Core的FromSql方法来用SQL语句查询Language表时,在SQL查询中将列的顺序反过来写,如下所示:

    string sql = @"
                    SELECT 
                    [LanguageName],
                    [LanguageCode],
                    [ID]
                    FROM [MD].[Language]
                ";
    
    var languages = dbContext.Language.FromSql(sql).ToList();

    可以看到虽然SQL语句中,SELECT语句后面的列顺序和实体类Language的属性顺序不一致,但是这对于FromSql方法来说并没有影响,FromSql方法还是正确地查询出了两条Language表的数据,如下图所示:

    FromSql方法中SQL语句返回的列最好和EF Core的实体类相匹配

    FromSql方法中SQL语句返回的列数,默认情况下不能小于EF Core实体类的属性数。

    例如我们现在数据库中有一个User表,有五个数据列:

    CREATE TABLE [dbo].[User](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [Name] [nvarchar](50) NULL,
        [Age] [int] NULL,
        [Sex] [int] NULL,
        [Email] [nvarchar](50) NULL,
     CONSTRAINT [PK_User] 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]

    但是我们在EF Core的实体类User中,多定义了一个属性DataStatus,如下所示:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
        public int? DataStatus { get; set; }
    }

    然后我们使用FromSql方法时,在SQL查询中不查询列DataStatus:

    var users = dbContext.User.FromSql(@"SELECT 
                                            [ID]
                                            ,[Name]
                                            ,[Age]
                                            ,[Sex]
                                            ,[Email]
                                            FROM [dbo].[User]").ToList();

    执行时FromSql方法会抛出System.InvalidOperationException异常:

    异常信息显示,列DataStatus在FromSql方法的返回结果中不存在。

    如果在EF Core实体类User中,实在有多的属性DataStatus,其实也是可以的,但是要在DataStatus属性上标记NotMapped特性(所属System.ComponentModel.DataAnnotations.Schema命名空间),如下所示:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
    
        [NotMapped]
        public int? DataStatus { get; set; }
    }

    这样使用FromSql方法时,在SQL查询中不查询列DataStatus,就不会抛出异常了,只不过返回的结果中,DataStatus属性全为null而已:

    另外如果EF Core实体类User中,属性DataStatus是非public(internal、protected、private)的:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
        internal int? DataStatus { get; set; }
    }

    那么在使用FromSql方法时,在SQL查询中不查询列DataStatus,也是不会抛出异常的,只不过返回的结果中,DataStatus属性全为null而已:

    其实DataStatus是非public(internal、protected、private)的时候,就算使用FromSql方法时,在SQL查询中查询了列DataStatus:

    var users = dbContext.User.FromSql(@"SELECT 
                                            [ID]
                                            ,[Name]
                                            ,[Age]
                                            ,[Sex]
                                            ,[Email]
                                            ,1 AS [DataStatus]
                                            FROM [dbo].[User]").ToList();

    EF Core实体类User的DataStatus属性也始终为null:

    因为FromSql方法只会为public的EF Core实体类属性绑定值。

    此外如果EF Core实体类User中,属性DataStatus只有get或set访问器:

    只有get访问器:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
    
        protected int? dataStatus;
        public int? DataStatus
        {
            get
            {
                return dataStatus;
            }
        }
    }

    只有set访问器:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
    
        protected int? dataStatus;
        public int? DataStatus
        {
            set
            {
                dataStatus = value;
            }
        }
    }

    那么在使用FromSql方法时,在SQL查询中不查询列DataStatus,也是不会抛出异常的:

    相反如果FromSql方法中SQL语句返回的列数,大于EF Core实体类的属性数,这是完全没问题的:

    例如我们在EF Core的实体类User中,有五个属性,如下所示:

    public partial class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
        public int? Sex { get; set; }
        public string Email { get; set; }
    }

    然后我们使用FromSql方法时,在SQL查询中多查询一个列DataStatus,如下:

    var users = dbContext.User.FromSql(@"SELECT 
                                            [ID]
                                            ,[Name]
                                            ,[Age]
                                            ,[Sex]
                                            ,[Email]
                                            ,1 AS [DataStatus]
                                            FROM [dbo].[User]").ToList();

    这样执行是完全没有问题的,FromSql方法不会抛出异常:

    最后,通过实验发现,目前在EF Core的实体类中,也不是所有数据类型的属性都要求FromSql方法的返回结果中要有列相对应:

    • C#中所有SQL Server数据库基础类型:int(对应SQL Server类型int)、string(对应SQL Server类型nvarchar或varchar)、DateTime(对应SQL Server类型datetime),byte[](对应SQL Server类型varbinary)等,要求FromSql方法的返回结果中要有列相对应
    • C#中枚举(enum)类型,要求FromSql方法的返回结果中要有列相对应
    • C#中的复杂类型,最典型的例子就是C#中自定义的类,不要求FromSql方法的返回结果中要有列相对应

    以上总结如果以后发现进一步信息,会再做更新。

    EF Core 3.0更新
    注意在EF Core 3.0中,FromSql方法和ExecuteSqlCommand方法都已经过时,请使用FromSqlRaw方法和ExecuteSqlRaw方法进行替代

  • 相关阅读:
    HDU 6191 Query on A Tree ( 2017广西邀请赛 && 可持久化Trie )
    BZOJ 4318 OSU! ( 期望DP )
    洛谷 P2473 [SCOI2008]奖励关 ( 期望DP )
    Codeforces #499 E Border ( 裴蜀定理 )
    HDU 6444 Neko's loop ( 2018 CCPC 网络赛 && 裴蜀定理 && 线段树 )
    HDU 6438 Buy and Resell ( 2018 CCPC 网络赛 && 贪心 )
    Nowcoder Hash Function ( 拓扑排序 && 线段树优化建图 )
    Nowcoder Playing Games ( FWT 优化 DP && 博弈论 && 线性基)
    js中的深拷贝与浅拷贝
    nrm 源管理器
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9876322.html
Copyright © 2011-2022 走看看