Search code examples
c#automappercastle-activerecord

Auto mapper is failing to map a Castle Active Record Proxy to multiple destination types


This question appears to be related to my previous question.

I have now finished implementing AutoMapper across my entire application and I am down to one map that in very specific circumstances does not work.

I have put together a unit test with some fake classes that is able to reproduce the problem every time, the unit test itself has some setup that configures ActiveRecord to run against an in memory database that I have not included here (let me know if it may be relevant).

Here is the unit test:

[Test]
public void SandBox()
{
    Mapper.CreateMap<TestDbParty, TestCustomer>();
    Mapper.CreateMap<TestDbParty, TestLazyCustomer>();

    string customerId;
    using (Transaction.CreateParticipant())
    {
        var dbCustomer = new TestDbParty();
        dbCustomer.Save();
        customerId = dbCustomer.Id;

    }
    using (var uow = Transaction.CreateParticipant())
    {
        var dbCustomer = TestDbParty.Find(customerId);
        var customer = Mapper.Map<TestCustomer>(dbCustomer);
        var lazyCustomer = Mapper.Map<TestLazyCustomer>(dbCustomer);
    }
}

//Fake classes for unit test

public class TestCustomer 
{}

public class TestLazyCustomer : TestCustomer
{}

[ActiveRecord("PARTY", Lazy = true)]
public class TestDbParty : DbActiveRecord<TestDbParty>
{
    [PrimaryKey(PrimaryKeyType.Sequence, "ID", SequenceName = "PARTY_ID_SQ")]
    public virtual string Id { get; set; }
}

Transaction.CreateParticipant is a wrapper to setup the SessionScope, here is the implementation:

return new ActiveRecordTransactionParticipant((TransactionScope)SessionScope.Current 
?? new TransactionScope())

The last line of the test will throw the following exception:

System.InvalidCastException : Unable to cast object of type 'MyProject.TestCustomer' 
to type 'MyProject.TestLazyCustomer'.

Now if the test is changed to only map to the LazyCustomer it works as expected or if the map calls are moved into the first transaction they both work, reordering the two lines so that the lazy customer is mapped first also works.

My guess here is that the Castle proxying is somehow causing this but I can't figure out how or how I can reliably fix this.

Edit: I put a break point on the failing line and ran dbCustomer.GetType() in the immediate window, this returned Castle.Proxies.TestDbPartyProxy so that appears to confirm my suspicion at least partially, the mapping would work fine when they are in the same transaction because the object is infact an instance of TestDbParty and not a proxy.

This is definitely the problem, after removing the Lazy=true on the TestDbParty class the test works fine.


Solution

  • This turned out to be a bug in Auto Mapper the exact criteria to reproduce are as follows:

    Maps need to map with a source type that the runtime object derives from, in the case of the example is is the Active Record Proxy type that is used at runtime.

    There needs to be at least two possible destination types one of which should inherit the other.

    Finally the issue will only occur when mapping first to the base destination class and then to the derived destination and it is because of the map caching mechanism that is used.

    A git hub issue has been created for this.

    I am unaware of any possible work-arounds for this issue.