I seem to have a memory leak within the usage of AutoMapper and StructureMap which are used within my WCF REST service.
Under load test I can see the memory usage continually going up and when I examined it more closely using a memory profiler I can see that there are many instances of an object used by the MappingEngine building up (approx 9x amount of any other instance)
Class Name=Live Instances
Object=498,847
Int32[]=69,373
Object[]=68,116
ConcurrentDictionary<TKey, TValue>+Node<TypePair, IObjectMapper>=37,624
string=35,240
IObjectMapper[]=30,782
EventHandler<TypeMapCreatedEventArgs>=30,782
MappingEngine=30,781
ConcurrentDictionary<TypePair, IObjectMapper>=30,781
ConcurrentDictionary<TKey, TValue>+Node<TypePair, LambdaExpression>[]=30,781
ConcurrentDictionary<TKey, TValue>+Node<TypePair, IObjectMapper>[]=30,781
ConcurrentDictionary<TypePair, LambdaExpression>=30,781
and here is the a typical instance retention graph...
bootstrapper._configuration
-> AutoMapper.ConfigurationStore (TypeMapCreated)
-> System.EventHandler<TypeMapCreatedEventArgs> (multicast delegate)
-> System.Object[]
-> System.EventHandler<TypeMapCreatedEventArgs>
-> AutoMapper.MappingEngine (_objectMapperCache)
-> System.Collections.Concurrent.ConcurrentDictionary<Internal.typePair, IObjectMapper> (m_locks)
-> System.Object[]
-> System.Object
(sorry for rubbish formatting but SO wouldn't let me post the images)
Problem is that I'm not sure whether it is a problem with my code, StructureMap or AutoMapper.
Code samples...
Bootstrapper (called from Application_Start in global.asax) for automap...
public class BootStrapper
{
private static readonly IConfiguration _configuration = ObjectFactory.GetInstance<IConfiguration>();
public static void Initialize()
{
_configuration.AddProfile<AutoMapperProfile>();
}
public class AutoMapperProfile : Profile
{
protected override void Configure()
{
BootstrapAutoMapper();
}
}
public static void BootstrapAutoMapper()
{
_configuration.CreateMap<In.Account, Out.Accounts.Account>()
.ForMember(m => m.AccountNumber, o => o.MapFrom(s => s.AcctId))
.ForMember(m => m.Balances, o => o.Ignore())
.ForMember(m => m.AccountTypeDescription, o => o.MapFrom(s => s.TypeName))
.ForMember(m => m.AccountType, o => o.MapFrom(s => s.Type))
.ForMember(m => m.IsNoticeAccount, o => o.ResolveUsing(s => IsNoticeAccountMapper.Map(s.Type, ObjectFactory.GetInstance<IAccountTypeHelper>())));
// many other mappings excluded for brevity
}
}
which is configured within StructureMap with...
private void AutoMapperRegistration()
{
For<ConfigurationStore>().Singleton().Use<ConfigurationStore>().Ctor<IEnumerable<IObjectMapper>>().Is(AutoMapper.Mappers.MapperRegistry.AllMappers());
For<IConfigurationProvider>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
For<IConfiguration>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
For<ITypeMapFactory>().Use<TypeMapFactory>();
For<IMappingEngine>().Use<MappingEngine>();
Scan(scan =>
{
scan.AssemblyContainingType<AppRegistry>();
scan.AddAllTypesOf<Profile>();
});
}
Typical WCF Rest service usage...
public class AccountService : IAccountService
{
private readonly IMappingEngine _mappingEngine;
private readonly IAccountFacade _accountFacade;
public AccountService(IMappingEngine mappingEngine, IAccountFacade accountFacade)
{
_mappingEngine = mappingEngine;
_accountFacade = accountFacade;
}
public IEnumerable<Account> GetAccounts()
{
var ret = new List<Account>();
var entities = _accountFacade.GetAccounts().ToList();
foreach (var account in entities.Select(entity => _mappingEngine.Map<In.Account, Out.Account>(entity)))
{
ret.Add(account);
}
return ret;
}
}
It seems the problem was caused by the configuration of the MappingEngine - it needed to be set as a singleton otherwise instances were not cleared because it is IDisposable.
So config changed to..
For<IMappingEngine>().Singleton().Use<MappingEngine>();
Related
I have requirement to map two objects. The requirement is train reservation details which will have towardsjourney and return journey details.
public class ReservationSource
{
public string ReservationNumber{get;set;}
public TravelS towardsTravelS{get;set;}
public TravelS returnTravelS{get;set;}
}
This is the class which is in the ReservationSource class which is to capture towards and return journey details.
public class TravelS
{
public string travelId{get;set;}
public ICollection<JourneyS> Journeys{get;set;}
}
Above is the reservation source object. This source needs a mapping to the destination object. Destination object is given below.
public class ReservationDestination
{
public string ReservationNumber{get;set;}
public TravelD towardsTravelD{get;set;}
public TravelD returnTravelD{get;set;}
}
public class TravelD
{
public string travelId{get;set;}
public ICollection<JourneyD> Journeys{get;set;}
}
public class JourneyD
{
public string JourneyId{get;set;}
}
public class JourneyS
{
public string JourneyId{get;set;}
}
This is my destination object . Here i want to map my source to destination. How do i define mapping config and map .
var config = new mappingConfiguration(cfg=>
{
cfg.CreateMap<ReservationSource,ReservationDestination>()
});
Imapper map = config.CreateMapper();
This part of code maps only the reservationNumber to the destination object. Can someone help me to map all objects. That is towardsTravelS to towardsTravelD and returnTravelS to returnTravelD.
.net core version : 3.1
First of all you forgot to mention this but I assume there also is a class TravelS that looks like this:
public class TravelS
{
public string TravelId { get; set; }
}
There are a few things missing in your configuration. At the moment AutoMapper doesn't know it has to map properties with different names (TowardsTravelS => TowardsTravelD etc) so we have to define those aswell:
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
Here we tell AutoMapper that these properties that have different names need to be mapped.
Secondly TravelS and TravelD are different classes so we need to configure them for mapping as well:
cfg.CreateMap<TravelS, TravelD>();
So we now have something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
cfg.CreateMap<TravelS, TravelD>();
});
var mapper = config.CreateMapper();
var source = new ReservationSource
{
ReservationNumber = "9821",
ReturnTravelS = new TravelS
{
TravelId = "1"
},
TowardsTravelS = new TravelS
{
TravelId = "2"
}
};
var destination = mapper.Map<ReservationDestination>(source);
Console.WriteLine(JsonSerializer.Serialize(destination));
Output:
{"ReservationNumber":"9821","TowardsTravelD":{"TravelId":"2"},"ReturnTravelD":{"TravelId":"1"}}
Try it for yourself here: https://dotnetfiddle.net/FfccVR
Add this in your services in startup :
it's reusable and cleaner
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
add these interface and class in your project
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces()
.Any(i =>i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
and your source model be like this (map ReservationSource to ReservationSource):
public class ReservationSource : IMapFrom<ReservationSource>
{
public string Name { get; set; }
public string City { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<ReservationSource,ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
}
}
Using AutoMapper v6.1
Without having to hard code that value in lieu of the enum ((int)POStatusOptions.Ordered), how can you accomplish this map with Projection:
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(vm => vm.QtyOnPOs, opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)POStatusOptions.Ordered)
.Sum(pod => pod.QtyOrdered)
)))
My configuration for automapper is using profiles. So I have
My Config Class:
public static class AutoMapperConfiguration
{
public static void Configure()
{
//see https://github.com/AutoMapper/AutoMapper/wiki/Configuration
//see https://github.com/AutoMapper/AutoMapper/wiki/Configuration-validation
Mapper.Initialize(am =>
{
am.AddProfile<AutoMapperViewModelProfile>();
am.AddProfile<AutoMapperViewModelProfileAdmin>();
});
//uncomment this during testing to get a list of all errors in the browser when you run any page in otis
Mapper.AssertConfigurationIsValid();
}
}
Which is called in Application_Start() like:
AutoMapperConfiguration.Configure();
My profile class:
public class AutoMapperViewModelProfile : Profile
{
public AutoMapperViewModelProfile()
{
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(vm => vm.QtyOnPOs, opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)POStatusOptions.Ordered)
.Sum(pod => pod.QtyOrdered)
)))
}
In AutoMapper it's called Parameterization. Please see AutoMapper doc.
In your case it would be:
POStatusOption poStatusOption = POStatusOption.Whatever;
CreateMap<WorkOrderDetail, WorkOrderDetailsListViewModel>(MemberList.Destination)
.ForMember(
vm => vm.QtyOnPOs,
opt => opt.MapFrom(entity =>
entity.Item.PODetails
.Where(pod => pod.POHeader.StatusId >= (int)poStatusOption)
.Sum(pod => pod.QtyOrdered)
)
)
And you need to use it like this:
dbContext.WorkOrderDetails.ProjectTo<WorkOrderDetailsListViewModel>(Config, new { poStatusOption = POStatusOptions.Ordered });
I'm new with AutoMapper but I've read a few tutorials and decided to try it.
In those tutorials there was a good idea which I decided to adopt. The authir suggested that the mapping code for the view model should stay in the view model and not in the AutoMapper configuration. This will make it smaller and easily readable:
Here are the base files to do this using reflections, AutoMapperConfiguration:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
public class AutoMapperConfig
{
private Assembly assembly;
public AutoMapperConfig(Assembly assembly)
{
this.assembly = assembly;
}
public void Execute()
{
var types = this.assembly.GetExportedTypes();
LoadStandardMappings(types);
LoadCustomMappings(types);
}
private static void LoadStandardMappings(IEnumerable<Type> types)
{
var maps = from t in types
from i in t.GetInterfaces()
where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>) && !t.IsAbstract && !t.IsInterface
select new { Source = i.GetGenericArguments()[0], Destination = t };
foreach (var map in maps)
{
Mapper.CreateMap(map.Source, map.Destination);
}
}
private static void LoadCustomMappings(IEnumerable<Type> types)
{
var maps = from t in types
from i in t.GetInterfaces()
where typeof(IHaveCustomMappings).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface
select (IHaveCustomMappings)Activator.CreateInstance(t);
foreach (var map in maps)
{
map.CreateMappings(Mapper.Configuration);
}
}
}
public interface IMapFrom<T>
{
}
public interface IHaveCustomMappings
{
void CreateMappings(IConfiguration configuration);
}
The IMapFrom and IHaveCustom interfaces are here only to mark the mapping classes. Now we come to the interesting part. When I do for example a class like the following
public class BasicAddressViewModel : IHaveCustomMappings
{
public string Id { get; set; }
[Display(Name = "Name")]
public string Label { get; set; }
[Display(Name = "Number")]
public string Location { get; set; }
public void CreateMappings(IConfiguration configuration)
{
var map = configuration.CreateMap<Address, BasicAddressViewModel>();
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.Label, opt => opt.MapFrom(src => src.Label));
map.ForMember(dest => dest.Location, opt => opt.MapFrom(src => src.Location));
}
}
I'm setting the mappings in the CreateMappings method but if I decide to use this class as a parent those mappings will not be available to my child class and therefor I will have to reuse the same code for all my children classes:
public class IndexAddressViewModel : BasicAddressViewModel
{
public void CreateMappings(IConfiguration configuration)
{
var map = configuration.CreateMap<Address, IndexAddressViewModel >();
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.Label, opt => opt.MapFrom(src => src.Label));
map.ForMember(dest => dest.Location, opt => opt.MapFrom(src => src.Location));
}
}
In reality what I want to achieve is this
public class IndexAddressViewModel : BasicAddressViewModel
{
}
Thanks in advance for any suggestions.
You can create a protected method in your base class that creates the custom mappings
protected virtual IMappingExpression<BasicAddressViewModel, TDestination> CreateBaseMappings<TDestination>(IMappingExpression<BasicAddressViewModel, TDestination> mappingExpression)
where TDestination : BasicAddressViewModel
and make your CreateMappings(IConfiguration configuration) method virtual so you can override it in your derived class and call the base CreateBaseMappings method that returns IMappingExpression<BasicAddressViewModel, TDestination> which means you can add more mappings for other members
EDIT
I found a better solution. :)
Since AutoMapper 2.0 you can use the IncludeBase<BaseModel, BaseViewModel>() to call the mapping for the base type. So the new solution is to make your CreateMappings(IConfiguration configuration) method virtual in the base ViewModel class and override it in your derived class where you call:
configuration.CreateMap().IncludeBase();
I can't seem to get the FluentNHibernate overrides to run. Here's my configuration method
private static void Configure()
{
if (_configuration != null) return;
_configuration = new Configuration();
_configuration.Configure();
_configuration
.AddAssembly(typeof(IFoo).Assembly)
.AddAssembly(OtherDataAssembly);
var autoPersistenceModel = AutoMap
.AssemblyOf<IFoo>()
.AddEntityAssembly(OtherDataAssembly)
.Conventions.AddAssembly(OtherDataAssembly)
.Conventions.Add(DefaultCascade.None())
.UseOverridesFromAssemblyOf<IFoo>()
.UseOverridesFromAssembly(OtherDataAssembly)
.OverrideAll(map => map.IgnoreProperty("IsIgnored"))
.Where(IsTypeMatch);
_sessionFactory = Fluently
.Configure(Configuration)
.Mappings(m => m.AutoMappings
.Add(autoPersistenceModel))
.BuildSessionFactory();
}
Here's my override class
public class FooOverride : IAutoMappingOverride<IFoo>
{
public void Override(AutoMapping<IFoo> mapping)
{
mapping.Not.LazyLoad();
mapping.HasManyToMany(x => x.Bar).Table("FooBar");
}
}
Breakpoints I put in the Override() method are never hit when debugging, even when restarting IIS. How do I get this to run?
Overrides are only run on the exact same class, not classes which are assignable to the class in the generic type parameter of IAutoMappingOverride.
Update:
The only other options i can think of now are:
let FooOverride implement several IAuotMappingOverride<> for each class implementing IFoo
add the overides yourself using Reflection. untestet:
var overrideMethod = typeof(AutoPersistenceModel).GetMethod("Override");
foreach (var type in typeof(IFoo).Assembly)
{
if (typeof(IFoo).IsAssignableFrom(type))
{
overrideMethod.MakeGenericMethod(type).Invoke(new Action<IFoo>(m => m.HasMayToMany(...)));
}
}
I am trying out the code from this post on Event Driven Architecture (very interesting by the way). His IOC container is Unity though and I would like to do this using Structure map.
His code is:
public class EventSubscriptions : ISubscriptionService
{
public static void Add<T>()
{
var consumerType = typeof(T);
consumerType.GetInterfaces()
.Where(x => x.IsGenericType)
.Where(x => x.GetGenericTypeDefinition() == typeof(IConsumer<>))
.ToList()
.ForEach(x => IoC.Container.RegisterType(x,
consumerType,
consumerType.FullName));
}
public IEnumerable<IConsumer<T>> GetSubscriptions<T>()
{
var consumers = IoC.Container.ResolveAll(typeof(IConsumer<T>));
return consumers.Cast<IConsumer<T>>();
}
}
I have the following which does not seem to be working:
public class SubscriptionService : ISubscriptionService
{
public static void Add<T>()
{
var consumerType = typeof(T);
consumerType.GetInterfaces()
.Where(x => x.IsGenericType)
.Where(x => x.GetGenericTypeDefinition() == typeof (IConsumer<>))
.ToList().ForEach(x => ObjectFactory.Inject(consumerType, x));
}
public IEnumerable<IConsumer<T>> GetSubscriptions<T>()
{
var consumers = ObjectFactory.GetAllInstances(typeof(IConsumer<T>));
return consumers.Cast<IConsumer<T>>();
}
}
I am obviously not too familiar with Structure Map. Some links or explanation on what I am doing wrong would be really appreciated.
Update:
From Henning's answer, I ended up with -
public class SubscriptionService : ISubscriptionService
{
public IEnumerable<IConsumer<T>> GetSubscriptions<T>()
{
var consumers = ObjectFactory.GetAllInstances(typeof(IConsumer<T>));
return consumers.Cast<IConsumer<T>>();
}
}
And then in my bootstrapping class that is called on application startup I have:
public static void ConfigureStuctureMap()
{
ObjectFactory.Initialize(x =>
{
x.Scan(y =>
{
y.Assembly("Domain");
y.Assembly("Website");
y.AddAllTypesOf(typeof(IConsumer<>));
y.WithDefaultConventions();
});
});
}
Although I'm not a structuremap expert, I do believe you can do it in another way.
Structuremap has the ability to scan any given assembly for a given interface and automatically register all the implementations. We do that in my current project and it works really great.
I don't remember the exact code we use, but you can check out the documentation for assembly scanning
http://structuremap.sourceforge.net/ScanningAssemblies.htm
Build custom TypeScanner class that implement ITypeScanner interface.
public class EventSubConventionScanner : ITypeScanner
{
public void Process(Type type, PluginGraph graph)
{
Type interfaceType = type.FindInterfaceThatCloses(typeof(IConsumer<>));
if (interfaceType != null)
{
graph.AddType(interfaceType, type);
}
}
}
After, in registry or initialize routine write:
Scan(x =>
{
x.With<EventSubConventionScanner>();
});