Je convertis en lignes en colonnes en utilisant la fonction ci-dessous, mais je ne peux faire pivoter que 4 lignes en 1 seconde. C'est trop lent pour mon application
var items = row.Values.Cast<object>().ToList();
J'ai réalisé que les lignes suivantes prenaient 250ms pour se terminer pour chaque ligne. C'est le problème principal.
public static DataTable ToPivotTable<T, TColumn, TRow, TData>(
this IEnumerable<T> source,
Func<T, TColumn> columnSelector,
Expression<Func<T, TRow>> rowSelector,
Func<IEnumerable<T>, TData> dataSelector)
{
DataTable table = new DataTable();
var rowName = ((MemberExpression)rowSelector.Body).Member.Name;
table.Columns.Add(new DataColumn(rowName.ToString(), typeof(DateTime)));
var columns = source.Select(columnSelector).Distinct();
foreach (var column in columns)
table.Columns.Add(new DataColumn(column.ToString(), typeof(double)));
var rows = source.GroupBy(rowSelector.Compile())
.Select(rowGroup => new
{
Key = rowGroup.Key,
Values = columns.GroupJoin(
rowGroup,
c => c,
r => columnSelector(r),
(c, columnGroup) => dataSelector(columnGroup))
});
foreach (var row in rows)
{
var dataRow = table.NewRow();
var items = row.Values.Cast<object>().ToList();
items.Insert(0, row.Key);
dataRow.ItemArray = items.ToArray();
table.Rows.Add(dataRow);
}
return table;
}
Comment puis-je améliorer les performances de cette ligne?
3 Réponses :
Ce code est inefficace:
foreach (var row in rows)
{
var dataRow = table.NewRow();
var items = row.Values.Cast<object>();
items = new[] {row.Key}.Concat(items);
dataRow.ItemArray = items.ToArray();
table.Rows.Add(dataRow);
}
Vous créez une liste (une opération O (N)), puis insérez un élément au début de celle-ci (une autre opération O (N)) puis en le transformant plus tard en tableau (encore une autre opération O (N)).
Vous pouvez l'améliorer comme suit:
foreach (var row in rows)
{
var dataRow = table.NewRow();
var items = row.Values.Cast<object>().ToList();
items.Insert(0, row.Key);
dataRow.ItemArray = items.ToArray();
table.Rows.Add(dataRow);
}
Cela signifie que il n'y a qu'une seule opération O (N) - l'appel à items.ToArray().
(Sans pouvoir compiler ça, je ne sais pas si c'est exactement à droite - vous pourriez avoir besoin de items = new object [] {row.Key} .Concat (items); .)
Merci, cependant maintenant 'dataRow.ItemArray = items.ToArray ();' cette ligne a pris 250 ms.
@ S.Doe Avez-vous même besoin de le convertir en objet ? Combien de temps faut-il si vous omettez le .Cast? Si cela prend encore environ 250 ms, vous ne pouvez pas faire grand-chose. (Donc, si vous chronométrez row.Values.ToArray () , combien de temps cela prend-il?). De plus, comment le chronométrez-vous?
Je soupçonne qu'il faudra du temps au Linq pour créer chaque élément de la séquence rows . Il fait un GroupJoin () pour chaque élément.
Je ne veux que convertir des lignes en colonnes. Je ne sais pas si j'ai besoin d'un casting pour m'opposer ou non. Il est possible d'utiliser sans objet? J'utilise le débogage ligne par ligne et il montre le temps de réponse sur la synchronisation de la ligne précédente.
@ S.Doe Je ne pense pas que ce soit le casting maintenant, je pense que c'est le GroupJoin () qui est fait pour chaque ligne.
Est-il possible de changer la logique de groupjoin ()?
Pour être honnête, je ne sais pas vraiment ce que fait ce code, donc je ne peux pas vraiment commenter.
Cette partie du code sélectionne simplement le datatable par clé primaire. ce qui rend la datation transposée.
var rows = source.GroupBy(rowSelector.Compile())
.Select(rowGroup => new
{
Key = rowGroup.Key,
Values = columns.GroupJoin(
rowGroup,
c => c,
r => columnSelector(r),
(c, columnGroup) => dataSelector(columnGroup))
}).ToList();
Mais alors, comment dois-je changer la partie suivante qui commence par la boucle foreach; foreach (var row in rows) {
Cette partie de votre code me semble trop générique:
Values = rowGroup.GroupBy(r => columnSelector(r))
.Orderby(rg => rg.Key)
.Select(rg => dataSelector(rg))
colonnes est (effectivement) une IEnumerable distincte que vous calculez pour chaque rowGroup - vous devez le précalculer sous la forme la plus utile (mais une ToList ne serait pas un mauvais début).
L'utilisation de GroupJoin pour sélectionner l'occurrence dataSelector de chaque valeur possible de columnSelector (r) semble exagérée.
Et si vous faisiez
var columns = source.Select(columnSelector).Distinct().Orderby(c => c.ToString()).ToList();
Forcer un ordre explicite (alphabétique) sur le ItemArray.
Alors vous pouvez faire p>
Values = columns.GroupJoin(
rowGroup,
c => c,
r => columnSelector(r),
(c, columnGroup) => dataSelector(columnGroup))
Avec la suggestion de @ MatthewWatson.
Belle fonction. Beaucoup plus générique que celui que j'ai écrit.