2

I am running into an error when using AutoMapper with an EntityFramework class model. The error is: "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection." I know that this is due to navigational properties being lazy loaded unless I specifically eager load them with Include() when querying the database.

Here is some of the code that I'm currently working with to give a better idea of what I have, and then I will explain my question in more detail.

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

My question is how can I leverage AutoMapper to be smart enough to determine if it is an unpopulated navigation property, then it will skip cloning that property? I can't just not map all virtual properties, because sometimes they are included, and sometimes I do need them. I also know that if I know up front which properties will be included/excluded, then I can do something like:

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

Ultimately if I have to I will use this approach, but it would be VERY nice to not have to specify all properties to ignore in each circumstance. It would be a lot easier to make auto mapper realize that if a reference property isn't loaded, then it shouldn't try to map it. Any ideas on how this can be achieved?

Edit: I can't just decorate the navigation properties with an attribute and have it ignore all of them with that attribute because there are some certain circumstances where the properties are populated and I need to use them.

One possible solution that I'm investigating is making use of a custom value resolver and doing a try/catch when trying to get the value. If an error is caught specifying that the db context has been disposed, then just ignore it. I'll make a mockup example of what I am talking about, but keep in mind that I haven't gotten it to work yet. Suggestions on how I can make it work would be much appreciated:

[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);
        }
    }
}
Michael Rentmeister
  • 167
  • 1
  • 6
  • 24
  • Possible duplicate of [How to configure Automapper to automatically ignore properties with ReadOnly attribute?](http://stackoverflow.com/questions/28382811/how-to-configure-automapper-to-automatically-ignore-properties-with-readonly-att) – Eris Jun 24 '16 at 04:52
  • 1
    I don't think so, what Mike is asking is a bit different – akardon Jun 24 '16 at 04:53
  • What about [this](http://stackoverflow.com/questions/17825956/is-automapper-preventing-lazy-loading-with-ef) SO question? – Jeroen Heier Jun 24 '16 at 05:03
  • I don't really see a way for Automapper to detect a navigation property. Using an attribute is probably going to be the cleanest way to give Automapper the "hint". – Eris Jun 24 '16 at 06:26
  • Why don't you disable lazy loading? Or maybe I should ask: why do you depend on lazy loading instead of eager loading? – Gert Arnold Jun 24 '16 at 21:00

0 Answers0