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

0 ответов

Другие вопросы по тегам