Auto Mapper и EntityFramework Ленивая загрузка
Я сталкиваюсь с ошибкой при использовании 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;
}
}
В конечном счете, если мне придется, я буду использовать этот подход, но было бы ОЧЕНЬ хорошо, чтобы не указывать все свойства, которые следует игнорировать в каждом случае. Было бы намного проще заставить auto mapper понять, что если ссылочное свойство не загружено, оно не должно пытаться отобразить его. Любые идеи о том, как этого можно достичь?
Редактировать: я не могу просто украсить свойства навигации атрибутом, и он игнорирует их все с этим атрибутом, потому что есть некоторые определенные обстоятельства, когда свойства заполняются, и мне нужно их использовать.
Одно из возможных решений, которое я исследую, - это использование пользовательского преобразователя значений и попытка / получение при попытке получить значение. Если обнаружена ошибка, указывающая, что контекст базы данных был удален, просто проигнорируйте ее. Я приведу пример макета того, о чем я говорю, но имейте в виду, что я еще не заставил его работать. Предложения о том, как я могу сделать это будет очень цениться:
[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);
}
}
}