2 votes

Erreur de mémoire LINQ

Je interroge 200k enregistrements et utilise toute la mémoire du serveur (sans surprise). Je suis nouveau sur LINQ donc j'ai trouvé le code suivant qui devrait m'aider mais je ne sais pas comment l'utiliser :

public static IEnumerable> Batch(this IEnumerable collection, int batchSize)
{
    List nextbatch = new List(batchSize);
    foreach (T item in collection)
    {
        nextbatch.Add(item);
        if (nextbatch.Count == batchSize)
        {
            yield return nextbatch;
            nextbatch = new List(batchSize);
        }
    }
    if (nextbatch.Count > 0)
        yield return nextbatch;
}

Source: http://goo.gl/aQZIj

Voici mon code qui crée l'erreur "hors mémoire". Comment puis-je incorporer la nouvelle fonction Batch dans mon code ?

var crmMetrics = _crmDbContext.tpm_metricsSet.Where(a => a.ModifiedOn >= lastRunDate);

foreach (var crmMetric in crmMetrics)
{
    metric = new Metric();                                
    metric.ProductKey = crmMetric.tpm_Product.Id;
    dbContext.Metrics.Add(metric);
    dbContext.SaveChanges();
}

2voto

Gert Arnold Points 27642

C'est une méthode d'extension, donc si elle fait partie d'une classe statique et qu'il y a une référence à l'espace de noms de la classe dans votre code, vous pourriez faire :

var crmMetricsBatches = _crmDbContext.tpm_metricsSet
                        .Where(a => a.ModifiedOn >= lastRunDate)
                        .AsEnumerable() // !!
                        .Batch(20);

Sauf que cela ne servirait à rien. Avec .AsEnumerable(), vous récupérez toujours toutes les données en mémoire mais maintenant par morceaux de 20. C'est parce que vous ne pouvez pas utiliser la méthode directement contre IQueryable : Entity Framework essayera de le traduire en SQL mais bien sûr ne sait pas comment le faire.

Comme l'a dit TGH, Skip et Take sont plus adaptés pour cela :

var crmMetricsPage = _crmDbContext.tpm_metricsSet
                        .Where(a => a.ModifiedOn >= lastRunDate)
                        .OrderBy(a => a.??) // une propriété que vous choisissez
                        .Skip(pageNo * pageSize)
                        .Take(pageSize);

pageNo compte de 0 au nombre de pages (- 1) dont vous avez besoin. Skip et Take sont des expressions, et EF sait comment convertir cela en SQL. Le OrderBy est requis pour qu'EF sache où commencer à sauter.

Dans ce processus, appelé pagination, vous obtenez toujours pageSize enregistrements à la fois. Le nombre de requêtes est plus élevé, mais les ressources sont préservées. Une condition est que vous puissiez déterminer un pageSize à l'avance. Je ne sais pas si cela correspond à votre logique.

Si vous ne pouvez pas utiliser la pagination, vous devriez essayer de restreindre le filtre (Where(a => a.ModifiedOn >= lastRunDate)), par exemple, essayer d'obtenir les données par lots d'un jour ou d'une semaine.

1voto

TGH Points 15623

Je vais utiliser Skip et Take de Linq pour obtenir les lots

Regardez ceci:

http://www.c-sharpcorner.com/UploadFile/3d39b4/take-and-skip-operator-in-linq-to-sql/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X