web-dev-qa-db-fra.com

LINQ: Ajouter une colonne de numéro de ligne

Comment la requête ci-dessous peut-elle être modifiée pour inclure une colonne avec le numéro de ligne (c'est-à-dire: index de résultats basé sur une base)?

var myResult = from currRow in someTable
               where currRow.someCategory == someCategoryValue
               orderby currRow.createdDate descending
               select currRow;

EDIT1: Je cherche les résultats pour être {idx, col1, col2...col-n} pas {idx, row}.

EDIT2: le numéro de la ligne doit correspondre aux lignes de résultat et non aux lignes de la table.

EDIT3: Je DataBind ces résultats en GridView. Mon objectif était d’ajouter une colonne de numéro de ligne à la variable GridView. Peut-être une approche différente serait mieux.

19
Steven

Utilisez la méthode-syntaxe où Enumerable.Select a une surcharge avec l'index:

var myResult = someTable.Select((r, i) => new { Row = r, Index = i })
    .Where(x => x.Row.someCategory == someCategoryValue)
    .OrderByDescending(x => x.Row.createdDate);

Notez que cette approche suppose que vous voulez l'index d'origine de la ligne dans la table et non dans le résultat filtré puisque je sélectionne l'index avant i filtrer avec Where.

EDIT: Je cherche les résultats pour être {idx, col1, col2 ... col-n} pas {idx, rangée}. Le numéro de ligne doit correspondre aux lignes de résultat non les lignes de la table.

Puis sélectionnez le type anonyme avec toutes les colonnes dont vous avez besoin:

var myResult = someTable.Where(r => r.someCategory == someCategoryValue)
        .OrderByDescending(r => r.createdDate)
        .Select((r, i) => new { idx = i, col1 = r.col1, col2 = r.col2, ...col-n = r.ColN });
29
Rango

Utilisez cette méthode Select :

Projette chaque élément d'une séquence dans une nouvelle forme en incorporant l'index de l'élément.

Exemple:

var myResult = someTable.Where(currRow => currRow.someCategory == someCategoryValue)
                        .OrderByDescending(currRow => currRow.createdDate)
                        .Select((currRow, index) => new {Row = currRow, Index = index + 1});

En réponse à votre modification: 

Si vous voulez obtenir une DataTable comme résultat, vous pouvez utiliser la méthode non-Linq en utilisant simplement une DataView et ajouter une colonne supplémentaire par la suite.

someTable.DefaultView.RowFilter = String.Format("someCategory = '{0}'", someCategoryValue);
someTable.DefaultView.Sort = "createdDate";
var resultTable = someTable.DefaultView.ToTable();
resultTable.Columns.Add("Number", typeof(int));
int i = 0;
foreach (DataRow row in resultTable.Rows)
    row["Number"] = ++i;
5
sloth

Juste pour le plaisir, voici une alternative à Select avec deux arguments:

var resultsWithIndexes = myResult.Zip(Enumerable.Range(1, int.MaxValue - 1),
                                      (o, i) => new { Index = i, Result = o });
3
Jon

qu'en est-il de?

int i;
var myResult = from currRow in someTable
           where currRow.someCategory == someCategoryValue
           orderby currRow.createdDate descending
           select new {Record = i++, currRow};
2
Carlos Martinez T

Selon votre édition 1. NO, VOUS NE POUVEZ PAS Linq renvoie la table telle quelle. Vous pouvez créer chaque colonne, mais vous perdez le pouvoir des entités mappées. 

Cela a déjà été demandé plusieurs fois auparavant: Comment ajouter un champ d'index aux résultats Linq

2
Carlos Martinez T

Il n’existe aucun moyen simple de conserver une liste non hiérarchique de colonnes (par exemple, Edit2 de OP) et de rechercher une solution générique qui fonctionne avec n’importe quel IEnumerable sans que vous ayez besoin de lister l’ensemble des colonnes attendues.

Cependant, il existe un moyen détourné de s'y atteler: vider les résultats de la requête dans un DataTable à l'aide de la méthode ToDataTable () de ici , puis ajouter une colonne RowNumber à cette table.

var table = query.ToList().ToDataTable();
table.Columns.Add("RowNum", typeof(int));
int i = 0;
foreach (DataRow row in table.Rows)
    row["RowNum"] = ++i;

Cela causerait probablement des problèmes de performances avec de grands ensembles de données, mais ce n’est pas non plus extrêmement lent. Sur ma machine, un jeu de données contenant environ 6 500 lignes nécessitait un temps de traitement de 33 ms.

Si votre requête d'origine a renvoyé un type anonyme, la définition de ce type sera perdue lors de la conversion, de sorte que vous perdrez le typage statique sur les noms de colonne du nom IEnumerable résultant lorsque vous appelez table.AsEnumerable (). En d'autres termes, au lieu de pouvoir écrire quelque chose comme table.AsEnumerable (). First (). RowNum, vous devez plutôt écrire table.AsEnumerable (). First () ["RowNum"]

Toutefois, si les performances ne vous intéressent pas et que vous souhaitez réellement revenir à la saisie statique, vous pouvez utiliser JSON.NET pour convertir DataTable en chaîne json, puis revenir à une liste basée sur le type anonyme du résultat de la requête d'origine. Cette méthode nécessite la présence d'un champ d'espace réservé RowNum dans les résultats de la requête d'origine.

var query  = (from currRow in someTable
            where currRow.someCategory == someCategoryValue
            orderby currRow.createdDate descending
            select new { currRow.someCategory, currRow.createdDate, RowNum = -1 }).ToList();
var table = query.ToDataTable();
//Placeholder RowNum column has to already exist in query results
//So not adding a new column, but merely populating it
int i = 0;
foreach (DataRow row in table.Rows)
    row["RowNum"] = ++i;
string json = JsonConvert.SerializeObject(table);
var staticallyTypedList = JsonConvert.DeserializeAnonymousType(json, query);
Console.WriteLine(staticallyTypedList.First().RowNum);

Cela a ajouté environ 120 ms au temps de traitement de mon jeu de données de 6500 éléments. 

C'est fou, mais ça marche.

1
ivanatpr

Je sais que je suis en retard à la fête, mais je voulais montrer ce qui a fonctionné pour moi.

J'ai une liste d'objets et l'objet a une propriété entière pour "numéro de ligne" ... ou dans ce cas, "numéro de séquence". Voici ce que j'ai fait pour renseigner ce champ:

myListOfObjects = myListOfObjects.Select((o, i) => { o.SequenceNumber = i; return o; }).ToList();

J'ai été surpris de voir que cela fonctionnait.

0
Joe