Automapper Random Errors

2019-06-06 01:54发布

问题:

2 months ago I have asked about this problem! and the problem still exists.

I am not going to copy/paste the same problem here because I found out that the error is not for a specific Entity-DTO mapping but for any Entity-DTO which is first in a controller.

I mean if the program flow hits to a Country-CountryDto, the error says:

Missing type map configuration or unsupported mapping.

Mapping types: 
Country -> CountryDTO 
MyApp.Domain.BoundedContext.Country -> MyApp.Application.BoundedContext.CountryDTO

Destination path: 
List`1[0]

Source value: 
MyApp.Domain.BoundedContext.Country

Or if there is an account check at first hand, the error says:

Missing type map configuration or unsupported mapping.

Mapping types:
Account -> AccountDTO
MyApp.Domain.BoundedContext.Account -> MyApp.Application.BoundedContext.AccountDTO

Destination path:
AccountDTO

Source value:
MyApp.Domain.BoundedContext.Account

I also found that the error is gone whenever I rebuild the presentation layer (in this case it is an MVC 3 project) of my N-Layer solution. And then, at a random time, it happens again.

If this problem happened only in development environment it wouldn't be a big deal but after publishing the problem was still there so I am in big trouble.

I have searched through Google, Stackoverflow, Automapper Forums/Groups with no success.

I have also tested the mappings with Mapper.AssertConfigurationIsValid() and everything was fine.

My project is an MVC 3 project with Automapper 2.2 and Unity IoC..

Again, I will appreciate any idea, advice or solution.

Edit: OK, now I have a clue.. I have a profile called ManagementProfile where all my mappings are done. In the AutomapperTypeAdapterFactory() I have a code like:

public AutomapperTypeAdapterFactory()
    {
        //Scan all assemblies to find an Auto Mapper Profile
        var profiles = AppDomain.CurrentDomain.GetAssemblies()
                                .SelectMany(a => a.GetTypes())
                                .Where(t => t.BaseType == typeof(Profile));

        Mapper.Initialize(cfg =>
        {
            foreach (var item in profiles)
            {
                if (item.FullName != "AutoMapper.SelfProfiler`2")
                    cfg.AddProfile(Activator.CreateInstance(item) as Profile);
            }
        });
    }

I found that, normally, the profiles variable holds ManagementProfile but sometimes it couldn't get the information and says "Enumeration yielded no results" and I got the exception mentioned in this question.

With further investigation I see that when everything is fine the AppDomain.CurrentDomain.GetAssemblies() loads 85 assemblies and on the other hand when I get the exception it has loaded only 41 assemblies and it was obvious that one of the missing assemblies was the one that holds the DTO mappings.

回答1:

Ok, I finally figured it out. The:

AppDomain.CurrentDomain.GetAssemblies()

piece of my code sometimes does not get my mapping assembly so while it is missing I get an error. Replacing this code by forcing the app to find all assemblies solved my problem.

Thanks for your replies.

Edit: As Andrew Brown asked about the code of the solution, I realized that I have not included the source code snippet. Here is the AssemblyLocator:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Compilation;

public static class AssemblyLocator
{
    private static readonly ReadOnlyCollection<Assembly> AllAssemblies;
    private static readonly ReadOnlyCollection<Assembly> BinAssemblies;

    static AssemblyLocator()
    {
        AllAssemblies = new ReadOnlyCollection<Assembly>(
            BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList());

        IList<Assembly> binAssemblies = new List<Assembly>();

        string binFolder = HttpRuntime.AppDomainAppPath + "bin\\";
        IList<string> dllFiles = Directory.GetFiles(binFolder, "*.dll",
            SearchOption.TopDirectoryOnly).ToList();

        foreach (string dllFile in dllFiles)
        {
            AssemblyName assemblyName = AssemblyName.GetAssemblyName(dllFile);

            Assembly locatedAssembly = AllAssemblies.FirstOrDefault(a =>
                AssemblyName.ReferenceMatchesDefinition(
                    a.GetName(), assemblyName));

            if (locatedAssembly != null)
            {
                binAssemblies.Add(locatedAssembly);
            }
        }

        BinAssemblies = new ReadOnlyCollection<Assembly>(binAssemblies);
    }

    public static ReadOnlyCollection<Assembly> GetAssemblies()
    {
        return AllAssemblies;
    }

    public static ReadOnlyCollection<Assembly> GetBinFolderAssemblies()
    {
        return BinAssemblies;
    }
}

Hence, instead of using AppDomain.CurrentDomain.GetAssemblies(), I am calling the GetAssemblies() method of the provided helper class like:

//Scan all assemblies to find an Auto Mapper Profile
//var profiles = AppDomain.CurrentDomain.GetAssemblies()
//                        .SelectMany(a => a.GetTypes())
//                        .Where(t => t.BaseType == typeof(Profile));
var profiles = AssemblyLocator.GetAssemblies().
                               SelectMany(a => a.GetTypes()).
                               Where(t => t.BaseType == typeof(Profile));


回答2:

I had a similar issue recently. Turned out there were multiple calls to AutoMapper.Initialize in code that was running in the same AppDomain (this was in a WCF service). The calls would register different mappings.

AutoMapper.Initialize clears out any previously registered mappings, so when multiple threads were running concurrently the configurations would compete and throw errors every now and then while functioning perfectly ok in unit and integrations tests.

So, scan your code base for calls to AutoMapper.Initialize (and AutoMapper.Reset) and make sure there is only a single call. If you need to configure AutoMapper in multiple steps use AutoMapper.Configure for subsequent steps.

More info here



回答3:

For me this error had to do with where I put my CreateMap<>() call. I had put it in the static initializer for my DTO. When I moved the CreateMap<>() call to somewhere less cute, everything worked fine.