2010-06-22 6 views
1

J'utilise Linq-To-Sql pour remplir ma couche de gestion. Voici un extrait d'une requête, je travaille sur:Comment puis-je optimiser cette requête LINQ pour n'exécuter qu'une seule commande SQL?

fund.FundEntities = fundGroup.tFunds 
    .Select(fe => 
    { 
     var fundEntity = new FundEntity() 
     { 
      BankAccount = null, 
      CloseDate = fe.closeDate ?? new DateTime(), 
      Commitment = fe.commitment ?? 0, 
      CommitmentEndDate = fe.closeDate ?? new DateTime(), 
      Fund = fund 
     }; 
     fundEntity.CapitalCalls = fe.tCapitalCalls 
      .Select(cc => 
      { 
       return new CapitalCall() 
       { 
        Amount = cc.agrAmount ?? 0, 
        FundEntity = fundEntity 
       }; 
      } 
      ); 
     return fundEntity; 
    }); 

Quand je lance ce code, il exécute des requêtes pour CapitalCalls individuelles lors de l'exécution. Y at-il de toute façon que je puisse réorganiser cela pour conserver la même structure d'objet métier (IE- les relations de Fund -> FundEntity -> CapitalCall dans les objets métier), mais charger des tables complètes à la fois? Idéalement, il y aurait une seule requête SQL avec beaucoup de jointures qui se traduirait par un fonds entièrement rempli.

+0

Quel est le raisonnement derrière l'appel à ToList()? –

+0

parce que la propriété CapitalCall sur FundEntity est de type Liste Shawn

+0

Hrmm Je suppose que je pourrais les rendre IEnumberable, mais ils sont encore interrogés 1 par 1 au moment de l'exécution ... – Shawn

Répondre

1
var fund = GetFund(); 
var fundGroup = GetFundGroup(); 
var dataContest = GetDataContext(); 


List<int> feIds = fundGroup.tFunds.Select(fe => fe.FundEntityId).ToList(); 

//before: iterate through local collection fundGroup.tFunds 
// and issue one CapitalCall query per fundEntity. 
//after: send all the fundEntityIds into the database in one query 
// that also fetches related CapitalCalls. 
var query = 
    from fe in dataContext.tFunds 
    where feIds.Contains(fe.FundEntityId)) 
    let capitalCalls = fe.tCapitalCalls 
    select new {FundEntity = fe, CapitalCalls = capitalCalls.ToList() }; 

foreach(var record in query) 
{ 
    FundEntity fundEntity = new FundEntity() 
    { 
    CloseDate = record.fe.closeDate ?? new DateTime(), 
    ... 
    } 
    fundEntity.CapitalCalls = ... 
} 
3

Une solution au problème serait d'exécuter plusieurs requêtes en même temps et de lier ensemble leurs données résultantes. Bien que LINQ to SQL puisse le faire, il ne supporte pas cette fonctionnalité de manière native. PLINQO est une alternative open source et alternative à LINQ to SQL qui ajoute des fonctionnalités supplémentaires à l'infrastructure. En particulier, vous seriez intéressé par les requêtes par lots et les requêtes futures.

Check it out à http://www.plinqo.com

Hope that helps! -Tom DuPont (membre de l'équipe de développement PLINQO)

+0

La vraie solution au problème est de construire une seule requête qui fournit les données correctes, puis formez les résultats localement. –

0

Il me semble que vous allez en arrière au lieu d'avancer. Vous essayez d'aller Fund -> FundEntity -> CapitalCalls, mais en essayant de donner à chacun une référence à son conteneur.

Vous pouvez envisager d'effectuer une requête plus simple pour la base de données, en appelant ToList dessus, puis en la reprogrammant dans la structure souhaitée à l'aide de Linq to Objects.

Vous pouvez également envisager d'écrire la requête dans l'autre sens et d'utiliser l'option Grouper par. Conceptuellement, vous obtiendrez les appels de capital, et les regrouperez par entité de fonds, puis regrouperez ceux par fonds.

Si vous pensez que vous pouvez vivre sans les références de conteneur, la requête que vous avez peut être exprimée comme suit, qui devrait utiliser une jointure dans une requête.

fund.FundEntities = fundGroup.tFunds 
.Select(fe => 
{ 
    new FundEntity 
    { 
     BankAccount = null, 
     CloseDate = fe.closeDate ?? new DateTime(), 
     Commitment = fe.commitment ?? 0, 
     CommitmentEndDate = fe.closeDate ?? new DateTime(), 
     CapitalCalls = fe.tCapitalCalls 
     .Select(cc => 
     { 
      new CapitalCall 
      { 
       Amount = cc.agrAmount ?? 0 
      }; 
     } 
    } 
}); 
+0

Je ne peux pas parce que le modèle d'objet va en quelque sorte dans un cercle, et parfois je vais devoir traverser l'arbre. De toute façon, je n'utilise que des jeux de données – Shawn

0

Je voudrais aller avec quelque chose comme SteadyEddi (je FundEntity inclus), mais je pense que vous pouvez exprimer exactement comme vous voulez que cela (ou l'équivalent de manière non LINQ comme SteadyEddi):

fund.FundEntities = 
from fe in fundGroup.tFunds 
select new FundEntity() 
{ 
    BackAccount = null, 
    CloseDate = fe.closeDate ?? new DateTime(), 
    Commitment = fe.commitment ?? 0, 
    CommitmentEndDate = fe.closeDate ?? new DateTime(), 
    Fund = fund, 
    CapitalCalls = from cc in fe.tCapitalCalls 
        select new CapitalCall() 
        { 
         Amount = cc.agrAmount ?? 0, 
         FundEntity = fundEntity 
        } 
} 

Sinon, je pense que quelque chose se passe dans votre mappage de base de données qui devrait vraiment être recréé de toute façon :) Bad names etc. CloseDate == CommitmentEndDate. Le problème avec votre code est peut-être que vous construisez vous-même les CapitalCalls pour que LINQ-TO-SQL ne puisse pas décoder correctement l'arbre d'expression. Mais encore une fois, je n'ai évidemment pas testé mon code, donc je ne sais pas si mon code fonctionne même.

+0

oui qui n'était pas vraiment partie de la question. ces propriétés sont à compatibilité ascendante et sont marquées obsolètes/commentées – Shawn

+0

Assez juste :) Est-ce que mon code a fonctionné dans votre exemple? J'ai lu plus tard que LINQ-TO-SQL ne peut traiter que des expressions simples, juste pour confirmer mon commentaire "LINQ-TO-SQL peut ne pas être capable de décoder correctement l'arbre d'expression". Cordialement, Lasse –

Questions connexes