2015-03-18 18 views
5

ho le seguenti entità Entity Framework:risparmio automapper mappato Collezioni di entità utilizzando Entity Framework

public class Region 
{ 
    public int RegionId { get; set; } // Primary Key 
    public string Name { get; set; } 
    public virtual ICollection<Country> Countries { get; set; } // Link Table 
} 
public class Country 
{ 
    public int CountryId { get; set; } // Primary Key 
    public string Name { get; set; } 
    public int RegionId { get; set; } // Foreign Key 
} 

traccio questi utilizzando automapper alle seguenti ViewModels:

public class RegionViewModel 
{ 
    public int RegionId { get; set; } 
    public string Name { get; set; } 
    public virtual ICollection<int> Countries { get; set; } 
} 
public class CountryViewModel 
{ 
    public int CountryId { get; set; } 
    public string Name { get; set; } 
} 

voglio tradurre le mie ViewModels alle entità che utilizzano AutoMapper in modo da poter salvare una nuova regione. Questo è il mio codice di mappatura:

Mapper.CreateMap<RegionViewModel, Region>() 
    .ForMember(x => x.Countries, x => x.MapFrom(y => y.Countries.Select(z => new Country() { CountryId = z }).ToArray())); 

Questo fa sì un'eccezione quando si aggiunge la regione nel repository come si cerca anche di creare una nuova istanza di paese con un nome nullo. Una soluzione è cambiare il metodo Add nel mio repository per impostare lo stato degli oggetti Paese su Invariato.

public async Task Add(Region region) 
{ 
    foreach (Country country in region.Countries) 
    { 
     this.Context.Entry(country).State = EntityState.Unchanged; 
    } 
    await base.Add(region); 
} 

L'altra soluzione alternativa è quella di usare la logica traduzione più complessa che utilizza un altro repository per ottenere gli oggetti paese reale. Questo approccio ha prestazioni più lente perché deve effettuare una chiamata extra al database, ma si ottiene anche un oggetto Region più completo.

Mapper.CreateMap<RegionViewModel, Region>(); 
Mapper.CreateMap<int[], Country[]>().ConvertUsing(x => countryRepository.GetAll().Result.Where(y => x.Contains(y.CountryId)).ToArray()); 

Mi chino al primo ma qual è l'approccio corretto?

+0

Perché è necessario utilizzare l'automapper? Potresti usare l'entità come viewmodel? – stepandohnal

+1

Uso AutoMapper per risparmiare tempo durante la scrittura del codice di traduzione di boilerplate. Ogni se rimuovi AutoMapper dalla domanda. Penso che la domanda sia ancora valida. Qual è l'approccio corretto. –

risposta

3

Il primo metodo, insieme con il ciclo per impostare gli stati di UnChanged, è sicuramente il migliore. È leggero, perché non si preleva inutilmente il numero Country dal database. Invece, dalla parte mapper ...

y.Countries.Select(z => new Country() { CountryId = z }) 

... si crea entità stub, ossia entità incomplete che fungono da segnaposto per le cose reali. Questo è comunemente recommended approach to reduce network traffic.

L'impostazione degli stati su UnChanged è uno dei vari modi per collegare gli stub Country s al contesto.È necessario allegarli prima di chiamare base.Add(region) (che presumo aggiunge la regione allo Regions del contesto), perché Add contrassegna tutte le entità in un oggetto grafico fuori dall'entità aggiunta come nuova (Added) quando non sono ancora associate al contesto.

+0

Un'altra risposta di Gert aiuta anche http://stackoverflow.com/questions/26518508/two-different-objects-with-same-key-for-entity-framework-does-not-work –

+0

'{CountryId = z}' come si converte un'entità in int qui ?? –

+0

@AkmalSalikhov 'y.Countries' è un elenco di numeri interi. –

1

Bene, penso che l'associazione di un grafico di entità al DbContext non sia l'approccio corretto, perché obbliga a scrivere molto codice per correggere gli stati di entità per impedire a EF di duplicare le proprie entità.

Un approccio più sicuro e più semplice IMO è quello di caricare l'entità Regione da DbContext, quindi aggiungere/rimuovere le entità Paese dalla raccolta Paesi, quindi chiamare SaveChanges.

è possibile scrivere un metodo generico mappatura collezione, qualcosa di simile (non testato):

static class EfUtils 
{ 
    public static void SyncCollections<TEntity>(
     ICollection<TEntity> collectionFromDb, 
     IEnumerable<TEntity> collectionFromVm, 
     IEqualityComparer<TEntity> equalityComparer, 
     Action<TEntity, TEntity> syncAction) 
     where TEntity : class, new() 
    { 
     var dbToVmEntitiesMap = new Dictionary<TEntity, TEntity>(); 
     var newEntities = new List<TEntity>(); 

     foreach (var vmEntity in collectionFromVm) 
     { 
      var dbEntity = collectionFromDb.FirstOrDefault(x => equalityComparer.Equals(x, vmEntity)); 
      if (dbEntity == null) 
      { 
       dbEntity = new TEntity(); 
       newEntities.Add(dbEntity); 
      } 

      dbToVmEntitiesMap.Add(dbEntity, vmEntity); 
     } 

     var removedEntities = collectionFromDb.Where(x => !dbToVmEntitiesMap.ContainsKey(x)).ToList(); 

     foreach (var addedOrUpdatedEntityPair in dbToVmEntitiesMap) 
     { 
      syncAction(addedOrUpdatedEntityPair.Key, addedOrUpdatedEntityPair.Value); 
     } 

     foreach (var removedEntity in removedEntities) 
     { 
      collectionFromDb.Remove(removedEntity); 
     } 

     foreach (var newEntity in newEntities) 
     { 
      collectionFromDb.Add(newEntity); 
     } 
    } 
} 

UPDATE

ho assunto la collezione Paesi conteneva modificabili view-modelli paese. Ma in realtà contiene gli ID dei paesi. In questo caso si avrebbe bisogno di applicare lo stesso Aggiungi/Rimuovi modello:

var regionFromDb = dbContext.Set<Region>().Find(regionVm.RegionId); 
var countriesToRemove = regionFromDb.Countries.Where(x => !regionVm.Countries.Contains(x.CountryId)).ToList(); 
foreach (var country in countriesToRemove) 
{ 
    regionFromDb.Countries.Remove(country); 
} 

var countryIdsToAdd = regionVm.Countries.Where(x => !regionFromDb.Countries.Any(c => c.CountryId == x)).ToList(); 

// Load countries where CountryId in countryIdsToAdd collection 
var countriesToAdd = dbContext.Set<Country>().Where(x => countryIdsToAdd.Contains(x.CountryId)); 
foreach (var country in countriesToAdd) 
{ 
    regionFromDb.Countries.Add(country); 
} 

dbContext.SaveChanges(); 
+0

Questo è forse l'approccio più sicuro e più facile da capire. Tuttavia, devi eseguire query di database aggiuntive. –

Problemi correlati