我有一个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时做什么?
答案 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(),否则您可能会获得缓存结果。