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

时间:2013-01-08 14:35:50

标签: c# sql linq view

我有一个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时做什么?

7 个答案:

答案 0 :(得分:7)

您的问题与此类似:Using a view with no primary key with Entity

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

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<YearlySalesOnEachCountry>()
           .HasKey(x => new { x.CountryId, x.OrYear });     
}

答案 1 :(得分:4)

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

对于这些情况,请考虑使用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()”(link)。

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

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

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

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

希望EF小组考虑为每个查询提供允许禁用密钥要求和自动使用AsNoTracking()的视图选项。

答案 2 :(得分:2)

我添加了

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

是我在解决这个问题的第一个专栏。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

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

我实际上并不需要在结果表中使用columns值,但它确实有助于LINQ在查询时保持记录的唯一性。似乎SQL在这方面做得很好,但是LINQ需要一些帮助来保持记录的清晰。

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

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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