将AutoMapper与EntityFramework类模型一起使用时遇到错误。错误是:" ObjectContext实例已被处理,不能再用于需要连接的操作。" 我知道这是由于导航属性延迟加载除非我在查询数据库时特别急切地用Include()
加载它们。
以下是我目前正在处理的一些代码,以便更好地了解我的内容,然后我会更详细地解释我的问题。
public partial class AddressInfo //EF Model
{
public int Id { get; set; }
public int PersonalInfoId { get; set; }
public Nullable<int> AddressTypeId { get; set; }
public string Address { get; set; }
public Nullable<int> ServiceCityId { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public virtual ListItem AddressType { get; set; }
public virtual PersonalInfo PersonalInfo { get; set; }
public virtual ServiceCity ServiceCity { get; set; }
}
public class AddressInfoProfile : Profile
{
protected override void Configure()
{
CreateMap<AddressInfo, AddressInfo>();
}
}
public class AddressesViewModel : ViewModel, IBasePersonalInfoViewModel //These aren't really important to the example
{
private IMapper _mapper;
public override IMapper Mapper => _mapper ??
(_mapper = new MapperConfiguration(cfg => { cfg.AddProfile<AddressInfoProfile>(); }).CreateMapper());
public AddressInfo SelectedAddressInfo { get; set; }
private void EditAddress()
{
if (SelectedAddressInfo == null) return;
try
{
var addressClone = Mapper.Map<AddressInfo, AddressInfo>(SelectedAddressInfo); //Error is thrown here
//Do stuff with cloned address
}
catch (Exception ex)
{
Errors.Add(ex.Message);
}
}
}
我的问题是如何利用AutoMapper足够智能来确定它是否是一个未填充的导航属性,然后它会跳过克隆该属性?我不能映射所有虚拟属性,因为有时它们被包含在内,有时我确实需要它们。我也知道,如果我事先知道将包含/排除哪些属性,那么我可以做类似的事情:
public class AddressesViewModel : ViewModel, IBasePersonalInfoViewModel //These aren't really important to the example
{
private IMapper _mapper;
public override IMapper Mapper => _mapper ??
(_mapper = new MapperConfiguration(cfg => { cfg.AddProfile( new AddressInfoProfile(x => x.ServiceCity, x => x.AddressType)); }).CreateMapper());
public AddressInfo SelectedAddressInfo { get; set; }
private void EditAddress()
{
if (SelectedAddressInfo == null) return;
try
{
//Error won't be thrown here because SelectedAddressInfo.PersonalInfo will be populated and the others will be ignored
var addressClone = Mapper.Map<AddressInfo, AddressInfo>(SelectedAddressInfo);
//Do stuff with cloned address
}
catch (Exception ex)
{
Errors.Add(ex.Message);
}
}
}
public class AddressInfoProfile : Profile
{
private readonly Expression<Func<AddressInfo, object>>[] _ignoreExpressions;
public AddressInfoProfile(params Expression<Func<AddressInfo, object>>[] ignoresExpressions)
{
_ignoreExpressions = ignoresExpressions;
}
protected override void Configure()
{
CreateMap<AddressInfo, AddressInfo>().IgnoreAll(_ignoreExpressions);
}
}
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> IgnoreAll<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map,
IEnumerable<Expression<Func<TDestination, object>>> selectors)
{
foreach (var expression in selectors)
{
map.ForMember(expression, config => config.Ignore());
}
return map;
}
}
最终如果我必须使用这种方法,但是在每种情况下都不必指定要忽略的所有属性是非常好的。让自动映射器意识到如果没有加载引用属性会更容易,那么它就不应该尝试映射它。关于如何实现这一点的任何想法?
修改 我不能仅使用属性修饰导航属性,并让它忽略具有该属性的所有属性,因为在某些特定情况下会填充属性并且我需要使用它们。
我正在调查的一个可能的解决方案是在尝试获取值时使用自定义值解析器并执行try / catch。如果捕获到指定已放置db上下文的错误,则忽略它。我会做一个关于我所说的内容的模型示例,但请记住,我还没有让它工作。关于如何使其发挥作用的建议将不胜感激:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class NavigationPropertyAttribute : Attribute
{
}
public partial class AddressInfo //EF Model (I modified the .tt file to have it generate the attributes on the navigation properties
{
public int Id { get; set; }
public int PersonalInfoId { get; set; }
public Nullable<int> AddressTypeId { get; set; }
public string Address { get; set; }
public Nullable<int> ServiceCityId { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[NavigationProperty]
public virtual ListItem AddressType { get; set; }
[NavigationProperty]
public virtual PersonalInfo PersonalInfo { get; set; }
[NavigationProperty]
public virtual ServiceCity ServiceCity { get; set; }
}
public class AddressInfoProfile : Profile
{
protected override void Configure()
{
CreateMap<AddressInfo, AddressInfo>()
.TryCatchNavigationProperties();
}
}
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> TryCatchNavigationProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map)
{
var sourceType = typeof(TSource);
foreach (PropertyInfo p in sourceType.GetProperties().Where(x => x.GetCustomAttributes<NavigationPropertyAttribute>().Any()))
map.ForMember(p.Name, x => x.ResolveUsing(typeof(SkipMapIfNullResolver)).FromMember(p.Name));
return map;
}
}
public class AddressesViewModel : ViewModel, IBasePersonalInfoViewModel //These aren't really important to the example
{
private IMapper _mapper;
public override IMapper Mapper => _mapper ??
(_mapper = new MapperConfiguration(cfg => { cfg.AddProfile<AddressInfoProfile>(); }).CreateMapper());
public AddressInfo SelectedAddressInfo { get; set; }
private void EditAddress()
{
if (SelectedAddressInfo == null) return;
try
{
var addressClone = Mapper.Map<AddressInfo, AddressInfo>(SelectedAddressInfo); //Error is thrown here
//Do stuff with cloned address
}
catch (Exception ex)
{
Errors.Add(ex.Message);
}
}
}