我有以下实体框架实体:
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转换为实体,这样我就可以保存一个新的Region。这是我的映射代码:
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());
我倾向于第一个,但正确的方法是什么?
答案 0 :(得分:4)
第一种方法与将状态设置为UnChanged
的循环一起绝对是最好的方法。它是轻量级的,因为您不必从数据库中不必要地获取Country
。相反,由映射器部分......
y.Countries.Select(z => new Country() { CountryId = z })
...您创建存根实体,即用作真实事物占位符的不完整实体。这通常是recommended approach to reduce network traffic。
将状态设置为UnChanged
是将存根Country
附加到上下文的几种方法之一。您必须在调用base.Add(region)
之前附加它们(我假设将该区域添加到上下文的Regions
),因为Add
将添加的实体中的对象图中的所有实体标记为新的(Added
)当他们尚未附加到上下文时。
答案 1 :(得分:1)
好吧,我认为将实体图附加到DbContext不是正确的方法,因为它会强制您编写大量代码来修复实体状态,以防止EF复制您的实体。
更安全,更简单的方法IMO是从DbContext加载Region实体,然后从Countries集合中添加/删除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);
}
}
}
<强>更新强>
我假设Countries集合包含可编辑的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();