复杂的Linq查询 - 尝试添加.Where()或.Any()谓词以降低查询复杂度(在VB中)

时间:2010-09-14 00:57:18

标签: asp.net vb.net linq linq-to-sql lambda

我遇到了一个相当复杂的Linq查询问题,我需要一些建议。这涉及VB语法,所以我需要该平台的具体答案,因为我有时很难将C#语法转换为VB。

我必须连接两个主表,我需要按ASP.NET Web表单中的元素过滤结果。这些过滤器是动态创建的,所以我必须使用很多where扩展来过滤查询。我想用尽可能优化的SQL执行查询。

我首先在TW_Sites和TW_Investigators之间进行简单连接。然后有两个涉及的子表。 TW_InvestigatorToArea和TW_InvestigatorToDisease。虽然大多数where子句工作正常,但我发现性能问题现在不会成为问题,但随着表格越来越大将成为一个问题。

数组DiseaseCategories和DiseaseAreas将是CheckBoxList结果的结果。

    Protected Sub LoadResults()
    ' Get Dictionary of Filters
    Dim FilterDictionary As OrderedDictionary = Session.Item("InvestigatorFilterDictionary")
    ' Initialize LinqToSql
    Dim LinqDbHandler As TrialWatchDC = New TrialWatchDC(WebConfigurationManager.ConnectionStrings("DataSourceName").ConnectionString)
    ' Create List of Categories to Filter By
    Dim DiseaseCategories() As Integer = {1, 2, 3, 4, 5, 6, 11, 22, 361, 77, 82, 99, 400}
    Dim CategorySubQuery = From ic In LinqDbHandler.TW_InvestigatorsToDiseases Where DiseaseCategories.Contains(ic.DiseaseCategoryID) Select ic.InvestigatorID Distinct
    ' Dim CategorySubArray = CategorySubQuery.ToArray()
    ' Create List of Areas to Filter By
    Dim AreaCategories() As Integer = {17, 1, 3, 5}
    Dim AreaSubQuery = From ic In LinqDbHandler.TW_InvestigatorsToAreas Where AreaCategories.Contains(ic.AreaID) Select ic.InvestigatorID Distinct
    Dim AreaSubArray = AreaSubQuery.ToArray()
    Dim dc As DbCommand
    Dim ThisQuery = From Site In LinqDbHandler.TW_Sites _
                    Join Investigator In LinqDbHandler.TW_Investigators On Site.TrialSiteID Equals Investigator.TrialSiteID _
                    Join SiteType In LinqDbHandler.TW_SiteTypes On Site.SiteTypeID Equals SiteType.SiteTypeID _
                    Order By Site.ResearchCenterName, Investigator.InvestigatorName
        Select New With {.TrialSiteID = Site.TrialSiteID, _
                         .InvestigatorID = Investigator.InvestigatorID, _
                         .ResearchCenterName = Site.ResearchCenterName, _
                         .SiteTypeID = SiteType.SiteTypeID, _
                         .TypeLabel = SiteType.TypeLabel, _
                         .CenterState = Site.CenterState, _
                         .CenterCountry = Site.CenterCountry, _
                         .ContactName = Site.ContactName, _
                         .ContactEMail = Site.ContactEMail, _
                         .ContactPhone = Site.ContactPhone, _
                         .IsRcppSubscriber = Site.IsRcppSubscriber, _
                         .InvestigatorName = Investigator.InvestigatorName, _
                         .IsPublicationSubscriber = Investigator.IsPublicationSubscriber, _
                         .HasPhase01 = Investigator.HasPhase01, _
                         .HasPhase02 = Investigator.HasPhase02, _
                         .HasPhase03 = Investigator.HasPhase03, _
                         .HasPhase04 = Investigator.HasPhase04, _
                         .AreaList = String.Join(",", (From ia In LinqDbHandler.TW_InvestigatorsToAreas Join a In LinqDbHandler.Disease_Areas On ia.AreaID Equals a.Area_Number Where ia.InvestigatorID = Investigator.InvestigatorID Order By a.Area_Name Select a.Area_Name Distinct).ToArray()), _
                         .CategoryList = String.Join(",", (From id In LinqDbHandler.TW_InvestigatorsToDiseases Join d In LinqDbHandler.Disease_Categories On id.DiseaseCategoryID Equals d.Category_Number Where id.InvestigatorID = Investigator.InvestigatorID Order By d.Category_Name Select d.Category_Name Distinct).ToArray())}
    If Not String.IsNullOrEmpty(FilterDictionary.Item("CountryFilter")) Then
        ThisQuery = ThisQuery.Where(Function(s) s.CenterCountry = FilterDictionary.Item("CountryFilter").ToString())
    End If
    If Not String.IsNullOrEmpty(FilterDictionary.Item("SiteType")) Then
        ThisQuery = ThisQuery.Where(Function(s) s.SiteTypeID = Convert.ToInt32(FilterDictionary.Item("SiteType")))
    End If
    dc = LinqDbHandler.GetCommand(ThisQuery)
    If Not String.IsNullOrEmpty(FilterDictionary.Item("StateFilter")) Then
        ThisQuery = ThisQuery.Where(Function(s) s.CenterState = FilterDictionary.Item("StateFilter").ToString())
    End If
    dc = LinqDbHandler.GetCommand(ThisQuery)
    ThisQuery = ThisQuery.Where(Function(i) CategorySubArray.Contains(i.InvestigatorID))
    ThisQuery = ThisQuery.Where(Function(i) AreaSubArray.Contains(i.InvestigatorID))
    dc = LinqDbHandler.GetCommand(ThisQuery)
    Trace.Warn("Command", dc.CommandText)
    For Each dcp As SqlParameter In dc.Parameters
        Trace.Warn(dcp.ParameterName.ToString(), dcp.Value.ToString())
    Next
    Dim ThisLinqResult = ThisQuery
    InvestigatorResultGrid.DataSource = ThisLinqResult
    InvestigatorResultGrid.DataBind()

End Sub

最大的问题是,当你查看代码时,基本上我首先将过滤后的子查询转换为数组,然后将其传递给SQL代码。结果最终使SQL查询包含许多参数,如下所示。

SELECT [t0].[TrialSiteID], [t1].[InvestigatorID], [t0].[ResearchCenterName], [t2].[SiteTypeID], [t2].[TypeLabel], [t0].[CenterState], [t0].[CenterCountry], [t0].[ContactName],
    [t0].[ContactEMail], [t0].[ContactPhone], [t0].[IsRcppSubscriber], [t1].[InvestigatorName], [t1].[IsPublicationSubscriber], [t1].[HasPhase01], [t1].[HasPhase02], [t1].[HasPhase03],
    [t1].[HasPhase04]
    FROM [dbo].[TW_Sites] AS [t0]
    INNER JOIN [dbo].[TW_Investigators] AS [t1] ON [t0].[TrialSiteID] = [t1].[TrialSiteID]
    INNER JOIN [dbo].[TW_SiteTypes] AS [t2] ON [t0].[SiteTypeID] = ([t2].[SiteTypeID])
    WHERE ([t1].[InvestigatorID] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, 
    @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, 
    @p59, @p60, @p61, @p62, @p63, @p64, @p65, @p66, @p67, @p68, @p69, @p70, @p71, @p72, @p73, @p74, @p75, @p76, @p77, @p78, @p79, @p80, @p81, @p82, @p83, @p84, @p85, @p86, @p87, @p88, @p89, 
    @p90, @p91, @p92, @p93, @p94, @p95, @p96, @p97, @p98, @p99, @p100, @p101, @p102, @p103, @p104, @p105, @p106, @p107, @p108, @p109, @p110, @p111, @p112, @p113, @p114, @p115)) AND 
    ([t1].[InvestigatorID] IN (@p116, @p117, @p118, @p119, @p120, @p121, @p122, @p123, @p124, @p125, @p126, @p127, @p128, @p129, @p130, @p131, @p132, @p133, @p134, @p135, @p136, @p137, @p138, 
    @p139, @p140, @p141, @p142, @p143, @p144, @p145, @p146, @p147, @p148, @p149, @p150, @p151, @p152, @p153, @p154, @p155, @p156, @p157, @p158, @p159, @p160, @p161, @p162, @p163, @p164, @p165, 
    @p166, @p167, @p168, @p169, @p170, @p171, @p172, @p173, @p174, @p175, @p176, @p177, @p178, @p179, @p180, @p181, @p182, @p183, @p184, @p185, @p186, @p187, @p188, @p189, @p190, @p191, @p192, 
    @p193, @p194, @p195, @p196, @p197, @p198, @p199, @p200, @p201, @p202, @p203, @p204, @p205))
    ORDER BY [t0].[ResearchCenterName], [t1].[InvestigatorName] 

这是很多参数,只会变得更糟。基本上,没有条件的小IN子句,我有一个更大的IN子句与调查员ID。

所以,我想要做的是弄清楚如何,而不是将Area和Category查询转换为数组然后将它们附加到第三个查询,以获取查询以包含子表并直接搜索用于区域和类别的匹配ID。我需要能够使用谓词语法,因为区域和类别是两个子表,有时两者或两者都不包括在内。我知道它与.Any(),.Join()或.Where()谓词有关,我只是不知道如何让它工作。

基本上,我正在尝试更改SQL以使其看起来更像这样。

WHERE ([t1].[InvestigatorID] IN (SELECT InvestigatorID FROM TW_InvestigatorsToAreas
WHERE DiseaseCategoryID IN (@p101, @p102, @p103))) 

任何帮助或指导都将不胜感激。

2 个答案:

答案 0 :(得分:0)

这是LINQ to SQL还是EF?

大多数ORM都会生成动态SQL,并将IN语句的每个ID作为参数。一些更聪明的人会创建一个临时表并加入反对,或使用嵌套子查询(或者如果你真的有创意,你可以扩展ORM来做到这一点)。

我知道DataObjects .NET执行临时表操作,LLBLGen可以使用嵌套子查询进行连接(也就是预取路径),我确信至少还有其他几个也可以使用。

有一点需要注意:您的里程可能会有所不同。临时表的一大优点是你可以在SQL Server中达到2400参数限制(虽然我不确定这对你来说是不是一个问题......)。但是,某些,也许20个查询中的1个实际上会对临时表(甚至是索引表)执行速度慢得多,而不是仅将每个ID作为参数传递。不过,总的来说,你会有更好的表现,因为执行计划不需要为每个查询重新编译。

答案 1 :(得分:0)

看起来它只是归结为语法语句。我想你只需要从LinqToSql对象本身调用子查询。

If DCHash.Count > 0 Then
    ThisQuery = ThisQuery.Where(Function(i) (From ic In LinqDbHandler.TW_InvestigatorsToDiseases Where DiseaseCategories.Contains(ic.DiseaseCategoryID) Select ic.InvestigatorID).Contains(i.InvestigatorID))
End If
If AreaHash.Count > 0 Then
    ThisQuery = ThisQuery.Where(Function(i) (From ia In LinqDbHandler.TW_InvestigatorsToAreas Where DiseaseAreas.Contains(ia.AreaID) Select ia.InvestigatorID).Contains(i.InvestigatorID))
End If