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方法進行替代
