I'm getting grief from Automapper.
My map is:
private void CreateEntityToDomainMaps()
{
//Mapper.CreateMap<Model.Customer,Data.Customer>();
Mapper.CreateMap<Data.Customer, Model.Customer>()
.ForMember(x => x.CompanyName, o => o.MapFrom(z => z.CompanyName));
}
My mapper is:
public T Map<T>(Data.Customer type)
{
return AutoMapper.Mapper.Map<Data.Customer, T>(type);
}
My Model class is:
public class Customer
{
public string CompanyName { get; set; }
}
My entity class is an EF entity of the good old Northwind Customer table.
The exception is:
AutoMapper.AutoMapperMappingException was unhandled by user code Message=Trying to map NorthwindEFAccess.Customer to
Model.Customer.Exception of type
'AutoMapper.AutoMapperMappingException' was thrown. Source=AutoMapper
StackTrace: at
AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext
context) at AutoMapper.MappingEngine.Map(Object source, Type
sourceType, Type destinationType) at
AutoMapper.MappingEngine.Map[TSource,TDestination](TSource source)
at AutoMapper.Mapper.Map[TSource,TDestination](TSource source)
at Model.Mapper.CustomerMapper.Map[T](Customer type) in
D:\programming\MVC\Projects\BestPracticeNWTelerikMVC\Model.Mapper\CustomerMapper.cs:line
10 at Services.CustomerService.GetAllCustomers() in
D:\programming\MVC\Projects\BestPracticeNWTelerikMVC\Services\CustomerService.cs:line
28 at UITests.CustomerServiceTest.GetAllCustomersTest() in
D:\programming\MVC\Projects\BestPracticeNWTelerikMVC\UITests\CustomerServiceTest.cs:line
81 InnerException: AutoMapper.AutoMapperMappingException
Message=Trying to map NorthwindEFAccess.Customer to
Model.Customer.Missing type map configuration or unsupported
mapping.Exception of type 'AutoMapper.AutoMapperMappingException' was
thrown. Source=AutoMapper StackTrace: at
AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext
context) InnerException:
I am baffled.
Related
I am working on a for me rather large blazor web app and use Automapper to simplify mapping. After upgrade to VS 2019 and update to newest nuget packages I get this error:
avsweb.ApplicationTests.Mappings.MappingTests.ShouldHaveValidConfiguration
Source: MappingTests.cs line 69
Duration: 1 ms
Message:
System.ArgumentException : Method T GetValue[T]() is a generic method definition (Parameter 'method')
Stack Trace:
Expression.ValidateMethodInfo(MethodInfo method, String paramName)
Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
Expression.Call(Expression instance, MethodInfo method)
<>c.<MemberAccesses>b__3_0(Expression inner, MemberInfo getter)
Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
ExpressionFactory.MemberAccesses(IEnumerable`1 members, Expression obj)
TypeMapPlanBuilder.Chain(IMemberMap memberMap, Type destinationType)
TypeMapPlanBuilder.BuildValueResolverFunc(IMemberMap memberMap, Expression destValueExpr, Expression defaultValue)
TypeMapPlanBuilder.CreatePropertyMapFunc(IMemberMap memberMap, Expression destination, MemberInfo destinationMember)
TypeMapPlanBuilder.CreateAssignmentFunc(Expression destinationFunc)
TypeMapPlanBuilder.CreateMapperLambda(HashSet`1 typeMapsPath)
TypeMap.CreateMapperLambda(IConfigurationProvider configurationProvider, HashSet`1 typeMapsPath)
TypeMap.Seal(IConfigurationProvider configurationProvider)
MapperConfiguration.Seal()
MapperConfiguration.ctor(MapperConfigurationExpression configurationExpression)
<>c.<AddAutoMapperClasses>b__12_2(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngineScope.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
<>c.<AddAutoMapperClasses>b__12_3(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngine.GetService(Type serviceType)
ServiceProvider.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
MappingTests.ctor(ITestOutputHelper _outputHelper) line 65
Automapper is registerd using the ServiceCollection Extention. All ValuResolvers are registered through custom service extension.
I have at the moment no glue where to look at. Does anyone hav a hint?
For anyone coming across this post later and "removing the generic method" is not an answer: AutoMapper has a feature to map Pre/PostFixes.
By default it knows the prefix "Get" and tries to map the source method GetValue[T]()to the destination property Value. However, it doesn't know how to map generic methods by default, thus the mapping fails.
To fix this, there are two options:
Rename the method to not include the "Get"
As stated in the documentation below, clear the prefixes using: cfg.ClearPrefixes()
This behaviour is defined briefly here:
https://docs.automapper.org/en/stable/Configuration.html#recognizing-pre-postfixes
OK, found the reasen for problem. To hande key/value pairs I added following class to project:
public class KeyValuePair
{
public string Key { get; set; }
public string Value { get; set; }
public T GetValue<T>()
{
var value = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(Value);
return value;
}
public void SetValue<T>(T value)
{
Value = value.ToString();
}
}
The method GetValue should provide a way to get the string value converted to object of type T. Through some automation process a mapping profile hase been defined for this class. This finally leads to the Argument exception either when validating mapping or when instantiating automapper (IMapper). I don't know why this error exactly happens, but mapping is for this class not useful anyway. so removing it from profile solved the case.
The hard part of it was to find the failing class out of 50+ classes / dto's. Luckily I create the Automapper profile dynamically during startup from methods in the class definitions. So it was possible to implement a testmethod with xUnit and Memberdata creating a mappingprofile for each single class and verify each single configuration. This did show the failing mappings.
Here a snippet how the map is created:
public partial class SettingDto : IMapperBase<Setting>
{
public void MappingBase(Profile profile)
{
// Generated Mapping
profile.CreateMap<Setting, SettingDto>()
.IncludeAllDerived()
.ForMember(m => m.Id, s => s.MapFrom(id => id.SettingId))
;
}
}
Class Setting derived from KeyValuePair class shown above.
Here a snippet for testing the single map:
[Theory]
[InlineData(typeof(ReychSettingDto),"MappingBase")]
public void SingleClassProfileMapperTest(Type type, string method)
{
// Arrange
logger.Information("Processing {#Name}", type.Name);
var profile = new MappingProfile(type, method) as Profile;
var config = new MapperConfiguration(cfg => { cfg.AddProfile(profile); });
// Act
//Action act = () => config.AssertConfigurationIsValid();
//act.Should().Throw<AutoMapperConfigurationException>();
try
{
config.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException aex)
{
logger.Error(aex, "Config");
}
// Assert
}
public static IEnumerable<object[]> GetProfiles(Type mapType, string method, int skip, int count)
{
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "avsweb.Application");
var allObjects = assembly.GetExportedTypes().Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == mapType))
.Select(o => new object[] { o, method })
.ToList();
if (count < 0)
{
return allObjects;
}
return allObjects.Skip(skip).Take(count);
}
}
public partial class MappingProfile : Profile
{
public MappingProfile(Type type, string method)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod(method);
methodInfo?.Invoke(instance, new object[] { this });
}
}
Hopefuly it helps somebody
Getting error
junit.framework.AssertionFailedError: Exception occured : java.lang.ClassCastException: us.ny.state.ij.safeact.ask.persistency.impl.CchResponsePersistenceImpl$$EnhancerByMockitoWithCGLIB$$d62edc51 incompatible with
here is stubbing
private PartnerPersistenceImpl mockPartnerPersistenceImpl;
#Before
public void setUp() throws Exception {
mockPartnerPersistenceImpl = mock(PartnerPersistenceImpl.class);
}
PowerMockito.doReturn(mockPartnerPersistenceImpl).when(PersistenceFactory.class,"getService",any(RecordTypeEnum.class),any(EntityManager.class));
service.processRequest("DUmmy data");
The return type of PersistenceFactory method is
public static ADRBasePersistenceImpl<?, Long> getService(){}
In service class we are type casting to different class to return
PartnerPersistenceImpl parnerPeristenceImpl = (PartnerPersistenceImpl) PersistenceFactory.getService(RecordTypeEnum.SELLERPARTNER, entityManager);
I don't know why mockito not accepting that cast?
Is there any way to get around an InvalidOperationException when creating an error response from a HttpError that contains a collection?
It only seems to occur when the response is serialized to XML, serializing to JSON works absolutely fine.
The problem seems to be related to the fact that HttpError stores data to be serialized in a Dictionary<string, object>. If I add an array of a custom class (FieldError) it then doesn't serialize that to XML properly. However, when I directly serialize a class with a property that's an array of that FieldError class the array does get serialized to XML correctly.
Is there any way I can get the collection to serialize as part of a HTTP error as well?
The full response I get when the exception is thrown is:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>The 'ObjectContent`1' type failed to serialize the response body for content type 'text/xml; charset=utf-8'.</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace />
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>Xml type 'xdt:untypedAtomic' does not support a conversion from Clr type 'FieldError' to Clr type 'String'.</ExceptionMessage>
<ExceptionType>System.InvalidCastException</ExceptionType>
<StackTrace>at System.Xml.Schema.XmlUntypedConverter.ChangeListType(Object value, Type destinationType, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlUntypedConverter.ChangeTypeWildcardDestination(Object value, Type destinationType, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlUntypedConverter.ToString(Object value, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlListConverter.ListAsString(IEnumerable list, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlListConverter.ChangeListType(Object value, Type destinationType, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlUntypedConverter.ChangeListType(Object value, Type destinationType, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlUntypedConverter.ChangeTypeWildcardDestination(Object value, Type destinationType, IXmlNamespaceResolver nsResolver)
at System.Xml.Schema.XmlUntypedConverter.ToString(Object value, IXmlNamespaceResolver nsResolver)
at System.Xml.XmlWriter.WriteValue(Object value)
at System.Web.Http.HttpError.System.Xml.Serialization.IXmlSerializable.WriteXml(XmlWriter writer)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteIXmlSerializable(XmlWriterDelegator xmlWriter, Object obj, XmlSerializableWriter xmlSerializableWriter)
at System.Runtime.Serialization.XmlDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph)
at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)
at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
</StackTrace>
</InnerException>
</Error>
I have the web api set up to serialize responses both via JSON and via XML:
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter { SerializerSettings = { NullValueHandling = NullValueHandling.Ignore } });
config.Formatters.Add(new XmlMediaTypeFormatter { UseXmlSerializer = false });
I have an ExceptionFilterAttribute that converts any of the custom exceptions my API throws into an HttpError and returns an error response:
public override void OnException(HttpActionExecutedContext context)
{
var apiException = context.Exception as ApiException;
if (apiException != null)
{
context.Response = context.Request.CreateErrorResponse((HttpStatusCode)apiException.ReasonCode, apiException.ToHttpError());
return;
}
base.OnException(context);
}
The base class of my custom API exceptions convert to HttpErrors like so:
internal virtual HttpError ToHttpError()
{
var httpError = new HttpError(this.Message) { { "ExceptionType", this.TypeName } };
var innerApiError = this.InnerException as ApiException;
if (innerApiError != null)
{
httpError.Add("InnerError", innerApiError.ToHttpError());
}
else if (this.InnerException != null)
{
httpError.Add("InnerError", new HttpError(this.InnerException.Message) { { "ExceptionType", this.InnerException.GetType().Name } });
}
return httpError;
}
Then I have a derived exception class with an array which overloads ToHttpError:
public FieldError[] Errors { get; private set; }
internal override HttpError ToHttpError()
{
var error = base.ToHttpError();
error.Add("Errors", this.Errors);
return error;
}
The FieldErrors property is a populated array and the FieldError class is declared as:
[DataContract(Namespace = "")]
public class FieldError
{
[DataMember]
public string Field
{
get;
set;
}
[DataMember]
public string Error
{
get;
set;
}
[IgnoreDataMember]
public ApiErrorTypes ErrorType
{
get;
set;
}
}
When I tell the API to return JSON the response I get is this:
{
"Message":"The supplied parameters are invalid.",
"ExceptionType":"InvalidRequestDataException",
"Errors":[
{
"Field":"CollectionDate",
"Error":"Collection date is not in a valid date format"
}
]
}
The only way I've found to get around this is to create a new class that uses a DataContract and call context.Request.CreateResponse() to create a response from that rather than using HttpError and context.Request.CreateErrorResponse()
I got AutoMapperMappingException exception
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown. ---> System.InvalidCastException: Invalid cast from 'DummyTypes' to 'System.Nullable`1[[System.Int32, ...
when
public enum DummyTypes : int
{
Foo = 1,
Bar = 2
}
public class DummySource
{
public DummyTypes Dummy { get; set; }
}
public class DummyDestination
{
public int? Dummy { get; set; }
}
[TestMethod]
public void MapDummy()
{
Mapper.CreateMap<DummySource, DummyDestination>();
Mapper.AssertConfigurationIsValid();
DummySource src = new DummySource()
{
Dummy = DummyTypes.Bar
};
Mapper.Map<DummySource, DummyDestination>(src);
}
Should not AutoMapper map this implicitly without any extra explicit rule?
P.S. I cannot change the definition of DummyDestination.Dummy to enum. I have to deal with such interfaces.
It looks like no, it won't take care of this automatically for you. Interestingly, it will map an enum to a regular int.
Looking at AutoMapper's source, I think the problematic line is:
Convert.ChangeType(context.SourceValue, context.DestinationType, null);
Assuming context.SourceValue = DummyTypes.Foo and context.DestinationType is int?, you would end up with:
Convert.ChangeType(DummyTypes.Foo, typeof(int?), null)
which throws a similar exception:
Invalid cast from 'UserQuery+DummyTypes' to
'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0
So I think really the question is why can't we cast a variable of type enum to int? That question has already been asked here.
This seems like a bug in AutoMapper. Anyway the workaround is to map the property manually:
Mapper.CreateMap<DummySource, DummyDestination>()
.ForMember(dest => dest.Dummy, opt => opt.MapFrom(src => (int?)src.Dummy));
Just in case if anyone want to try using a type converter
Mapper.CreateMap<int?, DummyTypes.Foo?>().ConvertUsing(new FooTypeConverter());
public class FooTypeConverter: TypeConverter<int?, DummyTypes.Foo?>
{
protected override DummyTypes.Foo? ConvertCore(int? source)
{
return source.HasValue ? (DummyTypes.Foo?)source.Value : null;
}
}
Cheers
I have an Entity Framework model (some properties have been excluded to keep it simple):
public class Media
{
public int MediaID { get; set; }
public ICollection<Track> Tracks { get; set; }
public ICollection<RelatedMedia> RelatedMedias { get; set; }
}
I then have my DbContext:
public class MediaServiceContext : DbContext
{
public DbSet<Media> Medias { get; set; }
}
I can then retrieve data using the following, and it works great:
public Media Media_Get(int id)
{
using (MediaServiceContext mc = new MediaServiceContext())
{
return mc.Medias.Include("Tracks").Include("RelatedMedias").Single(m => m.MediaID == id);
}
}
My question is, I may not want to load one or both of the related entities in some cases, depending on which part of my application is calling this code; how can I make the Includes dynamic?
I have tried this:
public Media Media_Get(int id, bool includeRelated, bool includeTracks)
{
using (MediaServiceContext mc = new MediaServiceContext())
{
IQueryable<Media> query = mc.Medias;
if (includeRelated)
query = query.Include("RelatedMedias");
if (includeTracks)
query = query.Include("Tracks");
return query.Single(m => m.MediaID == id);
}
}
...but I get a 'Specified cast in not valid' exception.
I have also tried this proposed solution, but it produces a 'unable to cast DbQuery to ObjectQuery' exception. Changing the extension method in the linked solution from '(ObjectQuery)source' to '(DbQuery)source' then causes the same 'Specified cast in not valid' exception.
I have hunted high and low for a solution on this but with no luck. Any help would be much appreciated.
Amendment - Here's the stack trace:
at System.Data.SqlClient.SqlBuffer.get_Int64()
at lambda_method(Closure , Shaper )
at System.Data.Common.Internal.Materialization.Coordinator.HasNextElement(Shaper shaper)
at System.Data.Common.Internal.Materialization.Shaper`1.RowNestedResultEnumerator.MoveNext()
at System.Data.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.TryReadToNextElement()
at System.Data.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
at API.Areas.V1.Models.RetailerManager.Media_Get(Int32 id, String retailerKey, Boolean includeLicenses, Boolean includeProperties, Boolean includeRelated, Boolean includeTracks) in C:\Users\garth\Documents\Development\WebApplications\api\Areas\V1\Models\RetailerManager.cs:line 28
at API.Areas.V1.Controllers.RetailerController.Media(Nullable`1 id, String httpVerb, Boolean includeLicenses, Boolean includeProperties, Boolean includeRelated, Boolean includeTracks) in C:\Users\garth\Documents\Development\WebApplications\api\Areas\V1\Controllers\RetailerController.cs:line 25
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
Your stack trace shows that .SingleOrDefault() caused this exception, but I don't see .SingleOrDefault() in your code.
I do see this:
return query.Single(m => m.MediaID == id);
Is it possible that Media.MediaID is a long and not an int?
Update
As another alternative to answer your original question, I answered a question a couple of weeks ago in relation to this. The sample code in my answer has to do with dynamic order by, but we use a very similar pattern for dynamic eager loading (see the first comment after my answer).
Instead of a method signature like this:
public Media Media_Get(int id, bool includeRelated, bool includeTracks)
Your signature would look more like this:
public Media Media_Get(MediaGetter mediaGetter)
...and you would use it like this:
var media = someInstance.Media_Get(
new MediaGetter { ID = id, }
.EagerLoad(m => m.Tracks)
.EagerLoad(m => m.RelatedTracks)
);