2011-08-23 30 views
0

我正在嘗試使用從SQL返回的分組數據。 我正在寫的方法是爲「案例狀態概覽」屏幕提供數據。 它必須產生一個嵌套的XML文檔。如何在使用group by子句時在Linq查詢中創建嵌套投影?

現在,我可以用簡單的方法做到這一點,但我想知道是否可以使用linq「group by」語句,然後投影已嵌套的數據。 (最簡單的方式將只是從數據庫中拉回來的數據以表格的方式,然後循環通過它形成了輸出XML文檔)

下面是數據層次:

每一個案件都有DebtType和每個DebtType都有一個Client。

這裏是檢索數據的SQL:

SELECT ClientNames.ClientID            , 
     ClientNames.ClientCode           , 
     ClientNames.ClientName           , 
     DebtTypes.DebtTypeID            , 
     DebtTypes.DebtTypeShortDesc          , 
     DebtTypes.DebtTypeLongDesc          , 
     Cases.CurrentStateCode           , 
     SUM(1 - CAST(Cases.CaseClosed AS INT)) AS OpenCaseCount   , 
     SUM(CAST(Cases.CaseClosed AS  INT)) AS ClosedCaseCount  , 
     SUM(CAST(Cases.CaseOnHold AS  INT)) AS OnHoldCaseCount  , 
     SUM(CAST(Cases.CaseReferred AS INT)) AS ReferredCaseCount  , 
     COUNT(Cases.CaseID)      AS TotalCaseCount  , 
     SUM(Cases.CaseTotalPaid)    AS TotalAmountPaid  , 
     SUM(Cases.CaseCurrentOutstandingAmount) AS TotalAmountOutstanding, 
     SUM(Cases.CaseTotalDebtWrittenOff)  AS TotalAmountWrittenOff , 
     SUM(Cases.CaseTotalDebtCancelled)  AS TotalAmountCancelled 
FROM  ClientNames 
     INNER JOIN ClientDebtTypes 
     ON  ClientNames.ClientID = ClientDebtTypes.ClientID 
     INNER JOIN DebtTypes 
     ON  ClientDebtTypes.DebtTypeID = DebtTypes.DebtTypeID 
     INNER JOIN Cases 
     ON  ClientDebtTypes.ClientDebtTypeID = Cases.CaseClientDebtTypeID 
GROUP BY ClientNames.ClientID  , 
     ClientNames.ClientCode  , 
     ClientNames.ClientName  , 
     DebtTypes.DebtTypeID  , 
     DebtTypes.DebtTypeShortDesc, 
     DebtTypes.DebtTypeLongDesc , 
     Cases.CurrentStateCode 
ORDER BY ClientNames.ClientID, 
     DebtTypes.DebtTypeID, 
     CurrentStateCode 

使用Linqer它把它轉換成:

from clientnames in db.ClientNames 
join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID 
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID 
join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID } 
group new {clientnames, debttypes, cases} by new { 
    clientnames.ClientID, 
    clientnames.ClientCode, 
    clientnames.ClientName1, 
    debttypes.DebtTypeID, 
    debttypes.DebtTypeShortDesc, 
    debttypes.DebtTypeLongDesc, 
    cases.CurrentStateCode 
} into g 
orderby 
    g.Key.ClientID, 
    g.Key.DebtTypeID, 
    g.Key.CurrentStateCode 
select new { 
    ClientID = (System.Int32?)g.Key.ClientID, 
    g.Key.ClientCode, 
    g.Key.ClientName1, 
    DebtTypeID = (System.Int32?)g.Key.DebtTypeID, 
    g.Key.DebtTypeShortDesc, 
    g.Key.DebtTypeLongDesc, 
    g.Key.CurrentStateCode, 
    OpenCaseCount = (System.Int64?)g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)), 
    ClosedCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)), 
    OnHoldCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)), 
    ReferredCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)), 
    TotalCaseCount = (Int64?)g.Count(p => p.cases.CaseID != null), 
    TotalAmountPaid = (System.Decimal?)g.Sum(p => p.cases.CaseTotalPaid), 
    TotalAmountOutstanding = (System.Decimal?)g.Sum(p => p.cases.CaseCurrentOutstandingAmount), 
    TotalAmountWrittenOff = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtWrittenOff), 
    TotalAmountCancelled = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtCancelled) 
} 

現在,正如我所說,我會停在那裏,右一個for循環創建Xml數據。 但我試圖創建一個嵌套組(IGrouping<ClientName,IGrouping<DebtType,SummaryClass>>) ,然後以嵌套格式投影數據。

現在我們正在使用LinqToXsd創造出XML文檔的強類型的包裝,但本質上這一切都意味着,OUT輸出類型:

private class ClientSummary 
{ 
    public string ClientName { get; set; } 
    public IList<DebtTypeSummary> DebtTypes { get; set; } 
} 

private class DebtTypeSummary 
{ 
    public string DebtType { get; set; } 
    public IList<StateCodeSummary> StateCodes { get; set; } 
} 

private class StateCodeSummary 
{ 
    public string StateCode { get; set; } 
    public int TotalCount { get; set; } 
    public decimal TotalAmountPaid { get; set; } 
    //etc 
    //etc 
    //etc 
} 

現在,我得到儘可能寫如下的LINQ:

var grouping = from cases in db.Cases 
       join clientdebttypes in db.ClientDebtTypes on cases.CaseClientDebtTypeID equals clientdebttypes.ClientID 
       join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID 
       group cases by new ClientDebtTypePair() { ClientDebtType = clientdebttypes, DebtType = debttypes } into casesByClientDebtTypes 
       join clientnames in db.ClientNames on casesByClientDebtTypes.Key.ClientDebtType.ClientName equals clientnames 
       group casesByClientDebtTypes by clientnames; 

var projected = from casesByClientDebtTypes in grouping 
      let client = casesByClientDebtTypes.Key 
      select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
       { 
        Client = new Client() 
        { 
         ClientID = client.ClientID, 
         DisplayName = client.ClientName1, 
        }, 
        DebtTypes = from cases in casesByClientDebtTypes 
           let debttype = cases.Key.DebtType 
           select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType() 
           { 
            DebtType = new DebtType() 
            { 
             DebtTypeID = debttype.DebtTypeID, 
             Description = debttype.DebtTypeLongDesc, 
              DisplayName = debttype.DebtTypeShortDesc, 
            }, 
            StatesCodes = from cases2 in cases 
                select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType() 
                { 
                 ClosedCasesCount = cases2.Sum(p => Convert.ToInt32(p.cases.CaseClosed)) 

它連接和組數據庫表,然後嘗試投影結果的ClientSummary(類名是不同的,但是這是因爲上面是輸出類的簡化圖)。我完全失敗時,我一直鑽到Cases表,我發現我真的不知道如何執行agregate函數。他們似乎只在IGrouping<K, T> s上可用,似乎我剛剛感到困惑。

我還需要確保總結是計算服務器端,拉回數百萬的情況將是不好的。

有人可以幫我這個嗎?這甚至有可能嗎?

Regards,

James。

------- ### UPDATE 1個### -------

OK,一直致力於這個今天再次上。 我決定使用Linq2SQL來打包2D數據,然後使用Linq2Objects重新格式化它。

這是我開始的:

var sql = from clientnames in db.ClientNames 
     join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID 
     join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID 
     join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID } 
     group new { clientnames, debttypes, cases } by new 
     { 
      clientnames.ClientID, 
      clientnames.ClientCode, 
      clientnames.ClientName1, 
      debttypes.DebtTypeID, 
      debttypes.DebtTypeShortDesc, 
      debttypes.DebtTypeLongDesc, 
      cases.CurrentStateCode 
     } into g 
     orderby 
     g.Key.ClientID, 
     g.Key.DebtTypeID, 
     g.Key.CurrentStateCode 
     select new 
     { 
      Client = new Client{ ClientID = g.Key.ClientID, DisplayName = g.Key.ClientName1 }, 
      DebtType = new DebtType{ DebtTypeID = g.Key.DebtTypeID, DisplayName = g.Key.DebtTypeShortDesc, Description = g.Key.DebtTypeLongDesc }, 
      StateSummary = new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType() 
      { 
       StateCode = g.Key.CurrentStateCode, 
       OpenCasesCount = g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)), 
       ClosedCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)), 
       OnHoldCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)), 
       ReferredCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)), 
       TotalCasesCount = g.Count(p => p.cases.CaseID != null), 
       TotalAmountPaid = g.Sum(p => p.cases.CaseTotalPaid), 
       TotalAmountOutstanding = g.Sum(p => p.cases.CaseCurrentOutstandingAmount), 
       TotalAmountWrittenOff = g.Sum(p => p.cases.CaseTotalDebtWrittenOff), 
       TotalAmountCancelled = g.Sum(p => p.cases.CaseTotalDebtCancelled), 
      } 
     }; 
var res = sql.ToList(); 

output.Clients = (from results in res 
       group results by results.Client into resultsByClient 
       from resultsByDebtType in 
        (from results in resultsByClient 
        group results by results.DebtType) 
       group resultsByDebtType by resultsByClient.Key into resultsByDebtTypeByClient 
       select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
       { 
        Client = resultsByDebtTypeByClient.Key, 
        DebtTypes = (from resultsByDebtType in resultsByDebtTypeByClient 
           select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType() 
           { 
            DebtType = resultsByDebtType.Key, 
            StatesCodes = (from results in resultsByDebtType 
                let summary = results.StateSummary 
                select results.StateSummary).ToList() 
           }).ToList() 
       }).ToList(); 

運行,但會產生一個客戶機/ DebtType /摘要爲每一個結果集。所以即使在這種情況下只有一個客戶端,我最終會有1300個客戶端,全部完全相同。 我把它簡化爲如下:

output.Clients = (from results in res 
      group results by results.Client into resultsByClient 
      select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
      { 
        Client = resultsByClient.Key, 
        DebtTypes = null, 
      }).ToList(); 

這產生1300個客戶端。接下來我試過這個:

output.Clients = (from results in res 
      group results by results.Client.ClientID into resultsByClient 
      select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
      { 
        Client = new Client { ClientID = resultsByClient.Key }, 
        DebtTypes = null, 
      }).ToList(); 

而且這產生了一個客戶端(hurray!)。除非我失去所有的客戶信息(噓!) 猜測,因爲它是由refernce而不是由內容比較客戶端我寫了下面:

public partial class Client 
{ 
    public static bool operator ==(Client left, Client right) 
    { 
     return left.ClientID == right.ClientID; 
    } 

    public static bool operator !=(Client left, Client right) 
    { 
     return left.ClientID != right.ClientID; 
    } 

    public override int GetHashCode() 
    { 
     return ClientID; 
    } 
} 

那什麼也沒做。它反覆調用GetHashCode(),我欺騙它強制它爲任何匹配的ClientID返回相同的哈希碼,但它仍創建了1300個客戶端組。

Regards,

James。

------- ###更新2 ### -------

OK,我想我會一展身手,在使LINQ2SQL輸出僅用於簡單值通過分組:

g.Key.ClientID, 
g.Key.ClientName1, 
g.Key.DebtTypeID, 
g.Key.DebtTypeShortDesc, 
g.Key.DebtTypeLongDesc, 

然後使試驗Linq2Objects到:

output.Clients = (from results in res 
       group results by new { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient 
       select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
       { 
        Client = new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName }, 
        DebtTypes = null, 
       }).ToList(); 

工程。所以匿名類型比較我希望他們能夠通過內容不引用(顯然) 這並不方式:

output.Clients = (from results in res 
       group results by new SiDemClient { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient 
       select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType() 
       { 
        Client = resultsByClient.Key,//new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName }, 
        DebtTypes = null, 
       }).ToList(); 

這仍然產生1300組。

所以,匿名類型比較神奇的方式,我不明白。我怎樣才能讓我的Client類比較像一個匿名類型?

Regards,

James。

------- ###發現的解決方案### -------

------- ###非常感謝Enigmativity ### -------

我需要重寫Equals()方法而不是實現==運算符。 現在分組工作,我有一個美妙的Xml文檔來重新!

public partial class SiDemClient 
{ 
    public override bool Equals(object obj) 
    { 
     if (obj is SiDemClient) 
     { 
      return this.ClientID.Equals(((SiDemClient)obj).ClientID); 
     } 
     return false; 
    } 


    public override int GetHashCode() 
    { 
     return ClientID; 
    } 
} 

非常感謝,

詹姆斯。

回答

0

當您覆蓋GetHashCode時,您還必須覆蓋Equals== & !=運營商是無關緊要的。

試試這個:

public partial class Client 
{ 
    public override bool Equals(object obj) 
    { 
     if (obj is Client) 
     { 
      return this.ClientID.Equals(((Client)obj).ClientID); 
     } 
     return false; 
    } 

    public override int GetHashCode() 
    { 
     return this.ClientID.GetHashCode(); 
    } 
} 

看看是否有幫助。

+0

是的,這是做到了。分組我的「客戶」類時創建的一個組,就像使用int或匿名類型進行分組時一樣。 謝謝! – RoboJ1M

+0

我很高興這是一個簡單的。哦,我在'GetHashCode'實現中犯了一個錯誤,但我糾正了它。 – Enigmativity