代码之家  ›  专栏  ›  技术社区  ›  Muhammad Rehan Saeed

使用实体框架保存AutoMapper映射的实体集合

  •  6
  • Muhammad Rehan Saeed  · 技术社区  · 11 年前

    我有以下实体框架实体:

    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
    }
    

    我使用AutoMapper将这些映射到以下ViewModel:

    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; }
    }
    

    我想使用AutoMapper将ViewModel转换为实体,以便保存新的区域。这是我的映射代码:

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

    这会导致在存储库中添加区域时出现异常,因为它还试图创建一个名称为空的Country的新实例。一种解决方案是改变 Add 方法将国家状态对象设置为未更改。

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

    另一种替代解决方案是使用更复杂的翻译逻辑,该逻辑使用另一个存储库来获取真实的国家/地区对象。这种方法的性能较慢,因为它必须对数据库进行额外的调用,但您也会得到一个更完整的Region对象。

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

    我倾向于第一种方法,但正确的方法是什么?

    2 回复  |  直到 11 年前
        1
  •  4
  •   Gert Arnold    11 年前

    第一个方法,以及将状态设置为 UnChanged ,绝对是最好的。它很轻,因为您不会不必要地获取 Country s从数据库中删除。相反,通过映射器部分。。。

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

    …您创建 存根实体 即充当真实事物占位符的不完整实体。这是一个常见的 recommended approach to reduce network traffic .

    将状态设置为 未更改 是连接存根的几种方法之一 这与上下文有关。你必须在打电话之前附上它们 base.Add(region) (我假设这将区域添加到 Regions 因为 Add 将对象图中的所有实体从添加的实体标记为新实体( Added )当他们还没有依附于上下文时。

        2
  •  1
  •   cchdev    11 年前

    嗯,我认为将实体图附加到DbContext不是正确的方法,因为它迫使您编写大量代码来修复实体状态,以防止EF复制您的实体。

    IMO的一种更安全、更简单的方法是从DbContext加载Region实体,然后从Countrys集合添加/删除Country实体,再调用SaveChanges。

    您可以编写一个通用集合映射方法,例如(未测试):

    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);
            }
        }
    }
    

    更新

    我假设Countrys集合包含可编辑的Country视图模型。 但实际上它包含了国家的ID。 在这种情况下,您需要应用相同的添加/删除模式:

    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();
    
    推荐文章