本文介紹了使用EF核心過濾包括時無效的列名的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!
問題描述
在修改DB First項目(使用Fluent Migrator)并搭建EF上下文以生成模型時,我遇到了這個錯誤。我通過進行代碼優先簡化來重現它。這意味著我不能接受建議修改批注或流暢配置的答案,因為這將在下一次遷移和搭建時刪除并重新創建。
簡化的想法是設備具有:
多個屬性
表示設備隨時間的變化的多個歷史記錄
每個歷史記錄條目都有一個可選位置
IOW您可以將設備移動到不同的位置(或不在任何位置),并隨著時間的推移進行跟蹤。
我提出的代碼優先模型來模擬這一點,如下所示:
public class ApiContext : DbContext
{
public ApiContext(DbContextOptions<ApiContext> options) : base(options) { }
public DbSet<Device> Devices { get; set; }
public DbSet<History> Histories { get; set; }
public DbSet<Location> Locations { get; set; }
}
public class Device
{
public int DeviceId { get; set; }
public string DeviceName { get; set; }
public List<History> Histories { get; } = new List<History>();
public List<Attribute> Attributes { get; } = new List<Attribute>();
}
public class History
{
public int HistoryId { get; set; }
public DateTime DateFrom { get; set; }
public string State { get; set; }
public int DeviceId { get; set; }
public Device Device { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Attribute
{
public int AttributeId { get; set; }
public string Name { get; set; }
public int DeviceId { get; set; }
public Device Device { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public List<History> Histories { get; } = new List<History>();
}
運行以下查詢以選擇所有設備運行正常。我使用filtered include僅選擇此視圖的最新歷史記錄:
var devices = _apiContext.Devices.AsNoTracking()
.Include(d => d.Histories.OrderByDescending(h => h.DateFrom).Take(1))
.ThenInclude(h => h.Location)
.Include(d => d.Attributes)
.Select(d => d.ToModel()).ToList();
這很好用,但是當我嘗試使用相同的ID只選擇一個設備時,包括:
var device = _apiContext.Devices.AsNoTracking()
.Include(d => d.Histories.OrderByDescending(h => h.DateFrom).Take(1))
.ThenInclude(h => h.Location)
.Include(d => d.Attributes)
.First(d => d.DeviceId == deviceId)
.ToModel();
我收到以下錯誤:
Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'LocationId'.
Invalid column name 'HistoryId'.
Invalid column name 'DateFrom'.
Invalid column name 'LocationId'.
Invalid column name 'State'.
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
at Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
at Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(DbContext _, Boolean result)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.First[TSource](IQueryable`1 source, Expression`1 predicate)
at efcore_test.App.PrintSingleDevice(Int32 deviceId) in C:UsersIainprojectsefcore-5-bugefcore-testApp.cs:line 44
at efcore_test.Program.<>c__DisplayClass1_0.<Main>b__4(App app) in C:UsersIainprojectsefcore-5-bugefcore-testProgram.cs:line 28
at efcore_test.Program.RunInScope(IServiceProvider serviceProvider, Action`1 method) in C:UsersIainprojectsefcore-5-bugefcore-testProgram.cs:line 35
at efcore_test.Program.Main(String[] args) in C:UsersIainprojectsefcore-5-bugefcore-testProgram.cs:line 28
ClientConnectionId:1418edb2-0889-4f4d-9554-85344c9a35a9
Error Number:207,State:1,Class:16
我搞不懂為什么這對多行有效,但對單行無效。
為了完整起見,ToModel()
只是返回POCO的擴展方法。
我甚至不知道從哪里開始尋找,歡迎您的想法!
編輯
錯誤報告:https://github.com/dotnet/efcore/issues/26585
轉載:https://github.com/thinkOfaNumber/efcore-5-test
推薦答案
更新:該錯誤已在EF Core6.0中修復,因此下一個錯誤僅適用于EF Core5.0。
看起來您遇到了EF Core 5.0查詢翻譯錯誤,所以我建議您尋找/報告給EF Core GitHub問題跟蹤器。
據我所知,這是由于Take
運算符(這基本上就是First
方法在第二種情況下使用的運算符)將根查詢作為子查詢下推造成的。這以某種方式擾亂了生成的子查詢別名,并導致無效的SQL。
通過比較第一個查詢生成的SQL可以看出
SELECT [d].[DeviceId], [d].[DeviceName], [t0].[HistoryId], [t0].[DateFrom], [t0].[DeviceId], [t0].[LocationId], [t0].[State], [t0].[LocationId0], [t0].[LocationName], [a].[AttributeId], [a].[DeviceId], [a].[Name]
FROM [Devices] AS [d]
OUTER APPLY (
SELECT [t].[HistoryId], [t].[DateFrom], [t].[DeviceId], [t].[LocationId], [t].[State], [l].[LocationId] AS [LocationId0], [l].[LocationName]
FROM (
SELECT TOP(1) [h].[HistoryId], [h].[DateFrom], [h].[DeviceId], [h].[LocationId], [h].[State]
FROM [Histories] AS [h]
WHERE [d].[DeviceId] = [h].[DeviceId]
ORDER BY [h].[DateFrom] DESC
) AS [t]
LEFT JOIN [Locations] AS [l] ON [t].[LocationId] = [l].[LocationId]
) AS [t0]
LEFT JOIN [Attribute] AS [a] ON [d].[DeviceId] = [a].[DeviceId]
ORDER BY [d].[DeviceId], [t0].[DateFrom] DESC, [t0].[HistoryId], [t0].[LocationId0], [a].[AttributeId]
和第二個(或僅在第一個Select
之前插入.Where(d => d.DeviceId == deviceId).Take(1)
):
SELECT [t].[DeviceId], [t].[DeviceName], [t1].[HistoryId], [t1].[DateFrom], [t1].[DeviceId], [t1].[LocationId], [t1].[State], [t1].[LocationId0], [t1].[LocationName], [a].[AttributeId], [a].[DeviceId], [a].[Name]
FROM (
SELECT TOP(1) [d].[DeviceId], [d].[DeviceName]
FROM [Devices] AS [d]
WHERE [d].[DeviceId] = @__deviceId_0
) AS [t]
OUTER APPLY (
SELECT [t].[HistoryId], [t].[DateFrom], [t].[DeviceId], [t].[LocationId], [t].[State], [l].[LocationId] AS [LocationId0], [l].[LocationName]
FROM (
SELECT TOP(1) [h].[HistoryId], [h].[DateFrom], [h].[DeviceId], [h].[LocationId], [h].[State]
FROM [Histories] AS [h]
WHERE [t].[DeviceId] = [h].[DeviceId]
ORDER BY [h].[DateFrom] DESC
) AS [t0]
LEFT JOIN [Locations] AS [l] ON [t].[LocationId] = [l].[LocationId]
) AS [t1]
LEFT JOIN [Attribute] AS [a] ON [t].[DeviceId] = [a].[DeviceId]
ORDER BY [t].[DeviceId], [t1].[DateFrom] DESC, [t1].[HistoryId], [t1].[LocationId0], [a].[AttributeId]
注意OUTER APPLY
中第一個SELECT [t].[HistoryId]...
中[t]
的用法,在第一個查詢中,它是FROM
子句中內部Histories
子查詢的別名,而在第二個查詢中,它是外部Devices
子查詢的別名,這兩個子查詢中沒有錯誤消息中提到的列。顯然,在第二種情況下,應該使用[t0]
。
由于它是一個錯誤,您必須等待修復它。在此之前,我建議的解決方法是在EF核心查詢上下文之外顯式執行行限制運算符(First
),例如
var device = _apiContext.Devices.AsNoTracking()
.Include(d => d.Histories.OrderByDescending(h => h.DateFrom).Take(1))
.ThenInclude(h => h.Location)
.Include(d => d.Attributes)
.Where(d => d.DeviceId == deviceId) // instead of .First(d => d.DeviceId == deviceId)
.AsEnumerable() // switch to client evaluation (LINQ to Objects context)
.First() // and execute `First` here
.ToModel();
這篇關于使用EF核心過濾包括時無效的列名的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,