2013-07-22 7 views
9

Nel mio ultimo progetto ho utilizzato Entity Framework 5 Code First. Ho completato il mio progetto ma ho sofferto molto durante il processo di sviluppo.Aggiunta e aggiornamento di entità con Entity Framework

Ho cercato di spiegare il mio dolore di seguito:

Ho avuto diverse classi di dati nel mio accesso ai dati strato di logica come prodotto, ProductCategory, Ordine, Company, Produttore ecc ... Ogni classe ha alcuni riferimenti ad alcune altre classi . Ad esempio, un'istanza Product ha una proprietà ProductCategory.

Dentro Aggiungi e aggiorna i metodi delle mie classi di accesso ai dati Le classi di oggetti impostano gli stati di ciascuna proprietà (diversa dai tipi primitivi) su Invariato o Modificato nel contesto. Di seguito è riportata una parte di un metodo di aggiornamento di alcune classi dao:

context.Entry(entity).State = System.Data.EntityState.Modified; 
if (entity.Category != null) 
    context.Entry(entity.Category).State = System.Data.EntityState.Unchanged; 

if (entity.Manufacturer != null) 
    context.Entry(entity.Manufacturer).State = System.Data.EntityState.Unchanged; 

foreach (var specificationDefinition in entity.SpecificationDefinitions) 
{ 
    context.Entry(specificationDefinition).State = System.Data.EntityState.Unchanged; 
    foreach (var specificationValue in specificationDefinition.Values) 
    { 
     context.Entry(specificationValue).State = System.Data.EntityState.Unchanged; 
    } 
} 

Questo codice è simile a questo. Alcuni dei miei metodi di aggiunta o aggiornamento sono circa 30 righe di codice. Penso che sto facendo qualcosa di sbagliato, l'aggiunta o l'aggiornamento di un'entità non dovrebbe essere così doloroso, ma quando non si impostano gli stati degli oggetti ottengo un'eccezione o voci duplicate nel database. Devo davvero impostare lo stato di ogni proprietà che esegue il mapping al database?

+0

È possibile aggiungere il codice in cui si creano i dati? Se i tuoi riferimenti nelle classi sono corretti, EF dovrebbe creare tutte le istanze una sola volta. – Nullius

+0

Sì, crea tutte le istanze contemporaneamente. Questo è il problema in realtà. Lascia che ti spieghi di nuovo il problema. Supponiamo di avere un'istanza di prodotto che contiene un'istanza ProductType al suo interno. Supponiamo che l'istanza del prodotto sia già persistente in passato. Quando provo ad aggiornare l'istanza di Product, EF crea un'istanza ProductType duplicata (che è un caso che non desidero) se non si imposta lo stato dell'attributo ProductType su Unchanged. – Furkan

risposta

11

È possibile sostituire il codice:

context.Products.Attach(entity); 
context.Entry(entity).State = System.Data.EntityState.Modified; 

Il motivo per cui questo è lo stesso (a meno che le entità correlate fossero già associate al contesto in un altro stato rispetto a prima) è che Attach mette entityincluso tutte le entità correlate nel grafico oggetto nel contesto nello stato Unchanged. Impostando lo stato super entity, viene modificato solo lo stato del prodotto (non le entità correlate) da Unchanged a Modified.

+0

Questo non funzionerà se l'entità è stata recuperata da un altro contesto (non ancora disposto). Avrai un'eccezione a riguardo. –

+0

@MichaelLogutov: hai ragione, ma sono quasi sicuro che lo scenario nella domanda è uno scenario distaccato (cioè un contesto precedente è già disponibile). Altrimenti è molto più facile in ogni caso, dal momento che normalmente non devi smanettare con nessuno stato di entità, perché il rilevamento delle modifiche tiene traccia automaticamente degli stati corretti. – Slauma

3

Ok, stai solo facendo qualcosa di sbagliato allora. Oltre al mio commento, ho creato un esempio per il tuo che mostra che EF non crea duplicati per impostazione predefinita.

ho due classi:

public class Product 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public ProductCategory Category { get; set; } 
    public decimal Price { get; set; } 
} 

public class ProductCategory 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

un contesto:

public class MyContext : DbContext 
{ 
    public DbSet<Product> Products { get; set; } 
    public DbSet<ProductCategory> ProductCategories { get; set; } 

    public MyContext() 
     : base("name=MyContext") 
    { 
    } 

    public MyContext(string nameOrConnectionString) 
     : base(nameOrConnectionString) 
    { 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     Database.SetInitializer<MyContext>(null); 

     // Table mappings 
     modelBuilder.Entity<Product>().ToTable("Product"); 
     modelBuilder.Entity<ProductCategory>().ToTable("ProductCategory"); 

     base.OnModelCreating(modelBuilder); 
    } 
} 

Poi una classe di inizializzazione (questo potrebbe ereditare da altre strategie se si vuole):

public class InitDb<TContext> : DropCreateDatabaseAlways<TContext> 
    where TContext : DbContext 
{ 
} 

I programma principale:

static void Main(string[] args) 
    { 
     var prodCat = new ProductCategory() 
     { 
      Name = "Category 1" 
     }; 

     var prod = new Product() 
     { 
      Name = "Product 1", 
      Category = prodCat, 
      Price = 19.95M 
     }; 

     using (var context = new MyContext()) 
     { 
      var initializer = new InitDb<MyContext>(); 
      initializer.InitializeDatabase(context); 

      Console.WriteLine("Adding products and categories to context."); 
      context.ProductCategories.Add(prodCat); 
      context.Products.Add(prod); 

      Console.WriteLine(); 
      Console.WriteLine("Saving initial context."); 
      context.SaveChanges(); 
      Console.WriteLine("Context saved."); 

      Console.WriteLine(); 
      Console.WriteLine("Changing product details."); 
      var initProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1); 
      PrintProduct(initProd); 
      initProd.Name = "Product 1 modified"; 
      initProd.Price = 29.95M; 
      initProd.Category.Name = "Category 1 modified"; 
      PrintProduct(initProd); 

      Console.WriteLine(); 
      Console.WriteLine("Saving modified context."); 
      context.SaveChanges(); 
      Console.WriteLine("Context saved."); 

      Console.WriteLine(); 
      Console.WriteLine("Getting modified product from database."); 
      var modProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1); 
      PrintProduct(modProd); 

      Console.WriteLine(); 
      Console.WriteLine("Finished!"); 
      Console.ReadKey(); 
     } 


    } 

    static void PrintProduct(Product prod) 
    { 
     Console.WriteLine(new string('-', 10)); 
     Console.WriteLine("Id  : {0}", prod.Id); 
     Console.WriteLine("Name : {0}", prod.Name); 
     Console.WriteLine("Price : {0}", prod.Price); 
     Console.WriteLine("CatId : {0}", prod.Category.Id); 
     Console.WriteLine("CatName : {0}", prod.Category.Name); 
     Console.WriteLine(new string('-', 10)); 
    } 

Il risultato è il seguente output della console:

Adding products and categories to context. 

Saving initial context. 
Context saved. 

Changing product details. 
---------- 
Id  : 1 
Name : Product 1 
Price : 19,95 
CatId : 1 
CatName : Category 1 
---------- 
---------- 
Id  : 1 
Name : Product 1 modified 
Price : 29,95 
CatId : 1 
CatName : Category 1 modified 
---------- 

Saving modified context. 
Context saved. 

Getting modified product from database. 
---------- 
Id  : 1 
Name : Product 1 modified 
Price : 29,95 
CatId : 1 
CatName : Category 1 modified 
---------- 

Finished! 

Inoltre, quando si cerca in SQL Server Management Studio, questa soluzione ha creato solo (e aggiornato) un prodotto e una categoria.

Ovviamente, dovresti lavorare con gli archivi per recuperare, aggiornare ed eliminare i tuoi dati e un'unità di lavoro. Questi sono stati lasciati fuori dall'esempio.

Quindi, se non pubblicare alcun codice, non possiamo fare molto di più :-)

+0

Quanto sopra il tuo codice è il primo approccio al codice EF? – Thomas

+0

@Thomas: È davvero il codice prima. – Nullius