2014-07-23 6 views
2

J'ai un backend .Net qui me permet d'interroger des éléments via l'API client correspondante par leurs propriétés .Id et .Revision et mieux encore, en vrac en fournissant une liste de ces combos.Le moyen le plus efficace de filtrer une liste qui contient (presque) des doublons dans .Net

Cependant, chaque .id ne peut apparaître une fois par requête, mais l'entrée ne contiennent certaines entrées avec les mêmes Ids plusieurs fois, mais avec des valeurs de .Revision différentes, par exemple:

.Id | .Revision 
1 | 1 
1 | 2 
2 | 1 (unique .Id) 
3 | 3 
3 | 5 
4 | 2 (unique .Id) 
5 | 1 (unique .Id) 

. Donc, les entrées avec .Id 1 et 3 posent des problèmes et je me demande quel serait le moyen le plus efficace (= le moins de requêtes) pour récupérer tous les combos. Dans le pire des cas, l'exécution la plus simple consisterait à récupérer tous les combos un par un en ignorant les mécanismes de bulk/batchs potentiels, mais même si cela retourne logiquement le bon ensemble d'éléments, c'est évidemment lent.

Comment puis-je obtenir le plus grand ensemble de combinaisons .Id/.Revision uniques et combiner les multiples .Id-uns restants ensemble en un minimum de lots ... de manière efficace?

+0

Quelle est la technologie sous-jacente qui est LINQ mappage? –

+0

@CapTec il n'y a pas de fournisseur Linq en place pour interroger le backend, les objets antérieurs à la requête sont purement en mémoire et d'un type personnalisé et fondamentalement l'API client prend uniquement int [] pour les valeurs .Id et .Revision . Donc, le lot de ces lots dans le moins de lots se passe en mémoire, localement. –

+0

Vous pouvez produire un arbre à partir des identifiants uniques et sous chaque branche avoir des noeuds pour chaque révision. De cette façon, vous pouvez interroger votre API au fur et à mesure des besoins pour chaque nœud. (Pensez à diffuser votre requête d'une manière de parler). Je peux mal comprendre votre question si. J'ai fait similaire avec XML afin de garder son empreinte mémoire faible dans la mémoire en cours d'exécution, le même principal peut travailler pour interroger une API. –

Répondre

2

Vous devriez être capable de le faire relativement facilement avec quelques expressions LINQ dans une boucle.

Par exemple, disons que vous avez une classe Item comme ceci:

public class Item 
{ 
    public int Id { get; set; } 
    public int Rev { get; set; } 
} 

Et une liste de ceux-ci: List<Item> Items; que vous souhaitez interroger par lots. Dans un lot, aucun Id peut se produire plusieurs fois.

Vous pouvez obtenir la première requête assez facilement avec Distinct:

var queryItems = Items.Distinct(new ItemIdComparer()).ToList(); 

Et votre comparateur:

public class ItemIdComparer: IEqualityComparer<Item> 
{ 
    public int Equals(Item x, Item y) 
    { 
     return x.Id == y.Id; 
    } 

    public int GetHashCode(Item x) 
    { 
     return x.Id; 
    } 
} 

Mais maintenant, vous avez besoin des éléments qui sont laissés sur. Pour cela, vous aurez besoin d'un comparateur d'égalité qui prend la révision en compte aussi:

public class ItemComparer: IEqualityComparer<Item> 
{ 
    public int Equals(Item x, Item y) 
    { 
     return x.Id == y.Id && x.Rev == y.Rev; 
    } 

    public int GetHashCode(Item x) 
    { 
     // not the best hash code, but should work okay. 
     return x.Id^x.Rev; 
    } 
} 

Et pour obtenir la liste des éléments qui se trouvent dans la liste initiale, mais pas dans la liste distincte, vous appelez Enumerable.Except:

var leftover = Items.Except(queryItems, new ItemComparer()).ToList(); 

Si vous mettez cela dans une boucle, vous pouvez le faire à plusieurs reprises jusqu'à ce que la liste leftover est vide:

var workingItems = Items.ToList(); 
while (workingItems.Count > 0) 
{ 
    var queryItems = workingItems.Distinct(new ItemIdComparer()).ToList(); 
    var leftover = workingItems.Except(queryItems, new ItemComparer()).ToList(); 
    DoQuery(queryItems); 
    workingItems = leftover; 
} 

en utilisant cet algorithme, vous pourriez o obtenir l'information pour tous vos articles avec seulement deux requêtes. Le premier recevrait les articles 1.1, 2.1, 3.3, 4.2 et 5.1. La deuxième requête obtiendrait 1.2 et 3.5.

+0

'GetHashCode' devrait vraisemblablement être' x.Id^x.Rev', pas 'x.Id^y.Id'. – porges

+0

@Porges: Merci. Fixé. –

+0

Nice, merci @JimMischel - simple et élégant! –

1

donné une liste des entrées de ce format:

public class Entry 
{ 
    public int Id { get; set; } 
    public int Version { get; set; } 
} 

Que diriez-vous regroupement par Id, puis la projection d'une nouvelle liste d'éléments avec l'ID, la version et le classement pour chaque entrée étiquetée comme un numéro de lot? Le rang sera parmi toutes les entrées avec le même ID.Vous pouvez ensuite regrouper toutes les entrées avec le même numéro de lot et soumettre un lot à la fois.

Voici mon expression:

var entries = GenerateEntries(); 

    var result = entries 
     .GroupBy(e => e.Id) 
     //project new entries with a batch number 
     .SelectMany(g => g.Select((e, i) => new { Id = e.Id, Version = e.Version, Batch = i })) 
     .GroupBy(e => e.Batch); 
+0

Bien que cela fonctionne, il faudrait 4 requêtes distinctes pour obtenir les informations pour les éléments de son exemple. Cela peut être fait en deux. –

+0

Je ne suis pas comment cela conduirait à plus de requêtes à la base de données. Pouvez-vous expliquer un peu? Toute cette logique fonctionne sur sa liste de mémoire, avant d'exécuter des requêtes. – gerrard00

+0

J'ai écrit une application rapide pour vérifier cela et il conduit à deux lots. Le premier a 1.1, 2.1, 3.3, 4.2 et 5.1. Le deuxième lot a 1.2 et 3.5. N'est-ce pas la même chose que votre code? – gerrard00

Questions connexes