2012-09-02 10 views
7

Obtengo los resultados que se muestran en el gráfico a continuación. Me pregunto si hay algo que pueda hacer para aumentar la previsibilidad. No estoy usando SqlBulkCopy ya que necesito aprovechar las capacidades de validación de EFv5.¿Por qué EF5 produce estos picos en SaveChanges() veces?

Sería genial si alguien pudiera verificar/refutar mis hallazgos. Mi objetivo es deshacerme de ambos tipos de picos. Proporciono el código fuente a continuación para que sea rápido para usted. Todo lo que necesita es un proyecto de biblioteca de clase en VS, con referencias a EFv5 y NUnit, ambos disponibles a través de NuGet. Simplemente pegue este código en Class1, modifique la cadena de conexión y ejecútelo. Puede usar el script sql a continuación para recrear la tabla.

Estoy usando .Net 4.5, EF 5, NUnit 2.6.1, ejecutando el código en modo Release, sin un depurador conectado. El db es SqlServer 2008 R2. Ejecuto la prueba con NUnit.exe en modo de 64 bits, que muestra 'Net 4.0' como la versión de marco.

EFv5 simple entity insert. 1000 batches of 100 entities

El eje X es el número de lote (1000 lotes en total), y el eje Y es milisegundos. Puede ver que el primer lote tarda unos 30 segundos, lo que se espera porque el dbContext está 'frío'. Cada lote guarda 100 entidades.

Tenga en cuenta que esta pregunta está buscando un poco de información que falta en this answer, siendo la fluctuación en EF guarda.

Aquí está el código que estoy usando:

La tabla:

CREATE TABLE [dbo].[Entity1](
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [IntField] [int] NOT NULL, 
    [StrField] [nvarchar](50) NOT NULL, 
    [DateField] [datetimeoffset](7) NOT NULL, 
CONSTRAINT [PK_Entity1] PRIMARY KEY CLUSTERED 
(
    [Id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
    ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

Las clases:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Diagnostics; 
using NUnit.Framework; 

namespace ClassLibrary1 
{ 
    public class Entity1 
    { 
     public int Id { get; protected set; } 
     public int IntField { get; set; } 
     public string StrField { get; set; } 
     public DateTimeOffset DateField { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public MyContext(string connStr) : base(connStr) { } 
     public virtual DbSet<Entity1> Entities { get { return base.Set<Entity1>(); } } 
    } 

    [TestFixture] 
    public class Class1 
    { 
     [Test] 
     public void EfPerf() 
     { 
      var entities = buildEntities(100000); 
      int batchSize = 100; 
      var batchTimes = new List<Stopwatch>(); 

      for (int i = 0; i < entities.Length; i += batchSize) 
      { 
       var sw = Stopwatch.StartNew(); 
       using (var ctx = buildCtx()) 
       { 
        for (int j = i; j < i + batchSize; j++) 
         ctx.Entities.Add(entities[j]); 
        ctx.SaveChanges(); 
       } 
       sw.Stop(); 
       batchTimes.Add(sw); 
      } 

      batchTimes.ForEach(sw => Console.Out.WriteLine("Elapsed ms: " + 
       sw.ElapsedMilliseconds)); 
     } 

     private MyContext buildCtx() 
     { 
      var cs = "Data Source=your db server;" + 
        "Initial Catalog=your db;" + 
        "Persist Security Info=True;" + 
        "User ID=your user;" + 
        "Password=your pwd"; 
      var ctx = new MyContext(cs); 
      //ctx.Configuration.ProxyCreationEnabled = false; 
      return ctx; 
     } 

     private Entity1[] buildEntities(int count) 
     { 
      var entities = new Entity1[count]; 
      for (int i = 0; i < count; i++) 
       entities[i] = new Entity1 { IntField = i, StrField = "str" + i, 
        DateField = DateTimeOffset.UtcNow }; 
      return entities; 
     } 
    } 
} 
+0

¿Puede exagerar el problema aumentando el tamaño del lote? Eso podría permitirle interrumpir el depurador. También podría iniciar un temporizador después de cada lote llamando a Debugger.Break después de 8 s en el lote. – usr

+0

También ejecute el Analizador de SQL para ver si los picos suceden en el nivel de SQL. Mira la columna de duración. – usr

+1

A propósito no adjunto el depurador para asegurarme de que no sea un factor. Además, sí ejecuté Sql Profiler y todas las inserciones tienen una duración de '0', y solo una en algunas docenas toma más que '0' cpu (y solo alrededor de '15' en ese caso). – esegura

Respuesta

1

Tengo la sensación de que está ejecutando en un problema con deadlocking en tu DB. EF coloca todas las llamadas a saveChanges() en una transacción. De forma predeterminada, las transacciones en SQL Server se ejecutan como Read Committed, que es altamente restrictivo. Quizás pueda intentar cambiar el nivel de aislamiento de la siguiente manera:

using (var scope = new TransactionScope(TransactionScopeOption.Required, new 
2: TransactionOptions { IsolationLevel= IsolationLevel.Snapshot })) 
3: { 
4: // do something with EF here 
5: scope.Complete(); 
6: } 
Cuestiones relacionadas