LINQ选择SQL视图得到错误的答案

我有一个SQL视图,它产生一个包含8列的响应。 它是一个相当复杂的,所以我不会在这里列出它,它不会增加我想要了解的问题。

当我使用此查询直接在SQL Manager中查询视图时

SELECT * FROM [GPPS].[dbo].[PartIndex] WHERE CategoryNameId = 182 AND CycleId = 13 AND BasketId = 304 AND MarketId = 8 ORDER BY ProductNameId 

我得到了预期的结果(前两行很重要)

 218 13 8 304 182 124 32575 162.84 218 13 8 304 182 124 32576 184.08 218 13 8 304 182 125 32577 156.13 218 13 8 304 182 127 32578 605.84 218 13 8 304 182 130 32579 141.51 

当我对视图执行以下LINQ时

 PartIndexes.Where(x => x.CategoryNameId == 182 && x.CycleId == 13 && x.BasketId == 304 && x.MarketId == 8) .ToList() .OrderBy(x => x.ProductNameId); 

我真的得到了

 218 13 8 304 182 124 32576 184.08 218 13 8 304 182 124 32576 184.08 218 13 8 304 182 125 32577 156.13 218 13 8 304 182 127 32578 605.84 218 13 8 304 182 130 32579 141.51 

正如您所看到的,前两个条目是相同的,并且ID(32575和32576)的区别已丢失。

在视图上运行LINQ查询时查看SQL事件探查器会生成以下SQL

 SELECT [Extent1].[SetNameId] AS [SetNameId], [Extent1].[CycleId] AS [CycleId], [Extent1].[MarketId] AS [MarketId], [Extent1].[BasketId] AS [BasketId], [Extent1].[CategoryNameId] AS [CategoryNameId], [Extent1].[ProductNameId] AS [ProductNameId], [Extent1].[PartId] AS [PartId], [Extent1].[Total] AS [Total] FROM (SELECT [PartIndex].[SetNameId] AS [SetNameId], [PartIndex].[CycleId] AS [CycleId], [PartIndex].[MarketId] AS [MarketId], [PartIndex].[BasketId] AS [BasketId], [PartIndex].[CategoryNameId] AS [CategoryNameId], [PartIndex].[ProductNameId] AS [ProductNameId], [PartIndex].[PartId] AS [PartId], [PartIndex].[Total] AS [Total] FROM [dbo].[PartIndex] AS [PartIndex]) AS [Extent1] WHERE (182 = [Extent1].[CategoryNameId]) AND (13 = [Extent1].[CycleId]) AND (304 = [Extent1].[BasketId]) AND (8 = [Extent1].[MarketId]) 

然后当我在SQL管理器中直接执行它时,我得到了所需的结果:

 218 13 8 304 182 124 32575 162.84 218 13 8 304 182 124 32576 184.08 218 13 8 304 182 125 32577 156.13 218 13 8 304 182 127 32578 605.84 218 13 8 304 182 130 32579 141.51 

任何人都知道这里可能会发生什么以及为什么执行LINQ请求会返回一个不同的结果,但是在执行LINQ查询生成的SQL时它会返回所需的结果?

当正确呈现时LINQ不直接使用时,SQL正在做什么?

您的问题与此类似: 使用没有主键的视图与实体

指定使您的行唯一的键。 您可以通过属性在实体映射上指定这些键:

 public class YearlySalesOnEachCountry { [Key, Column(Order=0)] public int CountryId { get; set; } public string CountryName { get; set; } [Key, Column(Order=1)] public int OrYear { get; set; } public long SalesCount { get; set; } public decimal TotalSales { get; set; } } 

或者你可以通过代码方法来做到这一点:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity() .HasKey(x => new { x.CountryId, x.OrYear }); } 

如果entity framework为视图选择的键不唯一,则可能无法正确返回结果。 对于某些视图,无法定义正确的键(包含所有非空列),并且不会消耗视图。

对于这些情况,请考虑使用EF Edmx接口手动定义密钥,如下所示:

  1) Any existing non-null field or 2) A separately added column "key" such as: select 1 as EfKey -- Must use with AsNoTracking() 

两种方法都需要为每个查询( 链接 )使用“AsNoTracking()”。

使用AsNoTracking()信号EF绕过其基于密钥的记录缓存机制。 如果没有AsNoTracking(),结果可能会损坏,包含重复的行。

使用(2)的一个优点是,如果忘记了AsNoTracking(),那么结果应该是如此糟糕以至于很容易被注意到。

避免使用row_number()的任何变体,因为它通常会阻止在SQL引擎中有效使用谓词。 这可以通过使用谓词查看SQL实际计划来validation。 (道歉,因为这是我最初发布的建议。)

  -- Avoid! select row_number() over (order by (select null)) as RowId, ... 

希望EF团队考虑为视图提供一个选项,允许禁用Key要求并自动使用每个查询的AsNoTracking()。

在前两行的第6列,您具有相同的值 – 124,如果这是此视图中的某个外键,则可以导致一行被过滤。 我使用数据表Load函数有类似的情况,因为它在将检索到的数据加载到数据表时应用约束。 尝试从linq删除密钥到SQL模式。

实际上来自@stanke的问题给了我一个想法。

我实际上稍微改变了视图以包含另一列,以便可以唯一地标识每个记录。

我实际上并不需要在结果表中使用columns值,但它确实有助于LINQ在查询时保持记录的唯一性。 看来SQL本身就可以做到这一点,但LINQ需要一些帮助来保持记录的清晰。

它现在在SQL和LINQ中都能正常工作

我没有看到您的LINQ有任何问题。

你如何填充PartIndexes(ORM,SqlCommand等)?

也许你的DAL或ORM映射中的逻辑混乱了,这搞乱了PartIndexes中存储的内容。

假设您正在使用EF(entity framework) 。 如果是这样,请在IQueryable上使用.AsNoTracking() ,否则您可能会获得缓存结果。

我补充道

ISNULL(CONVERT(VARCHAR(50),NEWID()),”)AS Pkid

作为我在视图中的第一个专栏来解决这个问题。