I have a Person and a PersonViewModel. I created a map from Person => PersonViewModel. The problem is that PersonViewModel's only constructor needs an argument (it has a dependency that I want to be injected) and AutoMapper is complaining because it says it needs a parameterless constructor.
To fix it, I used the ConstructServicesUsing method, but I haven't been successful with it :(
To illustrate the case, I created a test for you to see what I'm doing. It's pretty simple:
[TestMethod]
public void TestConstructServicesUsing()
{
Mapper.Initialize(configuration =>
{
configuration.ConstructServicesUsing(FactoryMethod);
configuration.CreateMap<Person, PersonViewModel>();
});
Mapper.AssertConfigurationIsValid();
var person = new Person();
var personViewModel = Mapper.Map<Person, PersonViewModel>(person);
}
private object FactoryMethod(Type type)
{
throw new NotImplementedException();
}
}
The rest of the code is the classes and interface definitions. They are almost empty.
public class SomeyDependency : ISomeDependency
{
}
public class PersonViewModel
{
private readonly ISomeDependency service;
public PersonViewModel(ISomeDependency service)
{
this.service = service;
}
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
}
public interface ISomeDependency
{
}
As you see, I provide AutoMapper with a FactoryMethod, but it never get called.
When it reaches the last line of the test (Mapper.Map<...>()) it throws an excepton saying:
AutoMapper.AutoMapperMappingException:
Mapping types:
Person -> PersonViewModel
MappingWithContainerTests.Person -> MappingWithContainerTests.PersonViewModel
Destination path:
PersonViewModel
Source value:
MappingWithContainerTests.Person ---> System.ArgumentException: Type needs to have a constructor with 0 args or only optional args
Parameter name: type
What's the problem?
Why isn't the FactoryMethod being called?
As #khorvat mention where is missing .ConstructUsingServiceLocator(), for concrete mapping.
Also you can set constructor directly by
.ConstructUsing(source => Method(source.anySourceOptions))
Or as exception said:
PersonViewModel, must have a constructor with 0 args or only optional
args. You have only one constructor with 1 not optional argument
you may create one more constructor without args:
public PersonViewModel()
{
this.service = new SomeDependency();
}
I'm using .NET Core 3.1 and Automapper.Extensions.Microsoft.DependencyInjection.
This does not work for me (Same error as yours):
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Context, MainViewModel>()
.ReverseMap()
.ConstructUsingServiceLocator();
}
}
But this does work:
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Context, MainViewModel>()
.ConstructUsingServiceLocator()
.ReverseMap();
}
}
I still do not fully understand the cause.
Related
iam trying to map just a long field coming from my url route to create a Query Object from my controller, can i use auto mapper
CreateMap(MemberList.None);
Source :-long id
Destination:-
public class GetPlanQuery : IRequest<PlanDto>
{
public long Id { get; }
public GetPlanQuery(long id)
{
Id = id;
}
internal sealed class GetPlanQueryHandler : IRequestHandler<GetPlanQuery, PlanDto>
{
//Logic will go here
}
}
Map i am using is as below
CreateMap<long, GetPlanQuery>(MemberList.None);
i am getting an exception while executing as
System.ArgumentException:
needs to have a constructor with 0 args or only optional args.'
As Lucian correctly suggested you can achieve this kind of custom mapping by implementing ITypeConverter:
public class LongToGetPlanQueryTypeConverter : ITypeConverter<long, GetPlanQuery>
{
public GetPlanQuery Convert(long source, GetPlanQuery destination, ResolutionContext context)
{
return new GetPlanQuery(source);
}
}
then specify it's usage in AutoMapper configuration:
configuration.CreateMap<long, GetPlanQuery>()
.ConvertUsing<LongToGetPlanQueryTypeConverter>();
EDIT
Alternatively, you can just use a Func:
configuration.CreateMap<long, GetPlanQuery>()
.ConvertUsing(id => new GetPlanQuery(id));
I have taken the MixedType example code that comes with the java stream client (https://github.com/GetStream/stream-java) and added a update step using updateActivities. After the update the activity stored in stream loses the 'type' attribute. Jackson uses this attribute when you get the activities again and it is deserialising them.
So I get:
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:60016', transport: 'socket'
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'null' into a subtype of [simple type, class io.getstream.client.apache.example.mixtype.MixedType$Match]
at [Source: org.apache.http.client.entity.LazyDecompressingInputStream#29ad44e3; line: 1, column: 619] (through reference chain: io.getstream.client.model.beans.StreamResponse["results"]->java.util.ArrayList[1])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.unknownTypeException(DeserializationContext.java:849)
See here where I have updated the example:
https://github.com/puntaa/stream-java/blob/master/stream-repo-apache/src/test/java/io/getstream/client/apache/example/mixtype/MixedType.java
Any idea what is going on here?
The issue here is originated by Jackson which cannot get the actual instance type of an object inside the collection due to the Java type erasure, if you want to know more about it please read this issue: https://github.com/FasterXML/jackson-databind/issues/336 (which also provides some possible workarounds).
The easiest way to solve it, would be to manually force the value of the property type from within the subclass as shown in the example below:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = VolleyballMatch.class, name = "volley"),
#JsonSubTypes.Type(value = FootballMatch.class, name = "football")
})
static abstract class Match extends BaseActivity {
private String type;
public String getType() {
return type;
}
}
static class VolleyballMatch extends Match {
private int nrOfServed;
private int nrOfBlocked;
public VolleyballMatch() {
super.type = "volley";
}
public int getNrOfServed() {
return nrOfServed;
}
public void setNrOfServed(int nrOfServed) {
this.nrOfServed = nrOfServed;
}
public void setNrOfBlocked(int nrOfBlocked) {
this.nrOfBlocked = nrOfBlocked;
}
public int getNrOfBlocked() {
return nrOfBlocked;
}
}
static class FootballMatch extends Match {
private int nrOfPenalty;
private int nrOfScore;
public FootballMatch() {
super.type = "football";
}
public int getNrOfPenalty() {
return nrOfPenalty;
}
public void setNrOfPenalty(int nrOfPenalty) {
this.nrOfPenalty = nrOfPenalty;
}
public int getNrOfScore() {
return nrOfScore;
}
public void setNrOfScore(int nrOfScore) {
this.nrOfScore = nrOfScore;
}
}
I'm sure I am missing something simple. First, I'll show all the code I have written to wire up the plumbing, then I'll show the exception message. Then, I'll set out what I have tried to fix it.
LicenceTrackerProfile
public class LicenceTrackerProfile : Profile
{
const string LicenceTrackerProfileName = "LicenceTrackerProfile";
public override string ProfileName
{
get { return LicenceTrackerProfileName; }
}
protected override void Configure()
{
// initialize mappings here
new ViewModelMappings(this).Initialize();
}
}
MapperBootstrapper
public class MapperBootstrapper
{
public void Configure()
{
var profile = new LicenceTrackerProfile();
AutoMapper.Mapper.Initialize(p => p.AddProfile(profile));
}
}
MappingBase
public abstract class MappingBase
{
private readonly Profile _profile;
protected MappingBase(Profile profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public Profile Profile
{
get { return _profile; }
}
}
UniversalMapper
public class UniversalMapper : IUniversalMapper
{
private readonly IMappingEngine _mappingEngine;
public UniversalMapper(IMappingEngine mappingEngine)
{
_mappingEngine = mappingEngine;
}
public virtual TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return _mappingEngine.Map(source, destination);
}
}
ViewModelMappings
public class ViewModelMappings : MappingBase, IMappingInitializer
{
private readonly Profile _profile;
public ViewModelMappings(Profile profile) : base(profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public void Initialize()
{
// data to domain mappings
Profile.CreateMap<EFDTO.Enums.FileTypes, Domain.FileTypes>();
Profile.CreateMap<EFDTO.Licence, Domain.Licence>();
Profile.CreateMap<EFDTO.LicenceAllocation, Domain.LicenceAllocation>();
Profile.CreateMap<EFDTO.Person, Domain.Person>();
Profile.CreateMap<EFDTO.Software, Domain.Software>();
Profile.CreateMap<EFDTO.SoftwareFile, Domain.SoftwareFile>();
Profile.CreateMap<EFDTO.SoftwareType, Domain.SoftwareType>();
}
}
Note, the initialize method and Configure method are being called, so they're not being "missed".
Exception
Missing type map configuration or unsupported mapping.
Mapping types: Software -> Software LicenceTracker.Entities.Software
-> LicenceTracker.DomainEntities.Software
Destination path: Software
Source value: LicenceTracker.Entities.Software
Troubleshooting
Ignoring columns. I planned to ignore columns, starting with all and then eliminating them by un-ignoring them 1 by 1 until I found the problem columns. However, to my surprise, the error occurs when I ignore all columns:
Profile.CreateMap<EFDTO.Software, Domain.Software>()
.ForMember(software => software.Licences, e => e.Ignore())
.ForMember(software => software.Name, e => e.Ignore())
.ForMember(software => software.SoftwareFiles, e => e.Ignore())
.ForMember(software => software.Type, e => e.Ignore())
.ForMember(software => software.Description, e => e.Ignore())
.ForMember(software => software.Id, e => e.Ignore())
.ForMember(software => software.TypeId, e => e.Ignore()
.ForMember(software => software.ObjectState, e => e.Ignore());
The Domain entities have [DataContract] (at class level) and [DataMember] (at method level) attributes. I added each of those attributes to the EF entities as well.
Other than that, I am out of ideas. It all seems to be wired up correctly.
What did I miss?
I'm back to heroically answer my question.
The problem was in the Service which created the UniversalMapper object (forgive the sloppy code, it is not final yet):
public class LicenceTrackerService : ILicenceTrackerService, IDisposable
{
LicenceTrackerContext context = new LicenceTrackerContext();
private MapperBootstrapper mapperBootstrapper;
private IUniversalMapper mapper = new UniversalMapper(Mapper.Engine);
private IUnitOfWork unitOfWork;
public LicenceTrackerService()
{
unitOfWork = new UnitOfWork(context, new RepositoryProvider(new RepositoryFactories()));
mapperBootstrapper = new MapperBootstrapper();
mapperBootstrapper.Configure();
Database.SetInitializer(new LicenceTrackerInitializer());
context.Database.Initialize(true);
}
public int GetNumber()
{
return 42;
}
public List<LicenceTracker.DomainEntities.Software> GetSoftwareProducts()
{
var productsRepo = unitOfWork.Repository<Software>();
var list = productsRepo.Query().Select().ToList();
var softwareList = new List<LicenceTracker.DomainEntities.Software>();
foreach (var software in list)
{
var softwareProduct = new LicenceTracker.DomainEntities.Software();
softwareList.Add(Mapper.Map(software, softwareProduct));
}
return softwareList;
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
I'm still not sure why, but initializing the mapper outside of the constructor (default value style) was not happy. By moving that instantiation into the constructor of the service, it worked:
private IUniversalMapper mapper;
public LicenceTrackerService()
{
mapper = new UniversalMapper(Mapper.Engine);
...
}
There's obviously something about static properties (Mapper.Engine) and default instantiations that I'm not understanding.
Anyway, no big deal as I was planning to inject the UniversalMapper into the service anyway.
Edit
I've actually figured out the problem for real now. It is an ordering thing. With Automapper, I had to initialize the mapper with the Profile before inserting the Mapper.Engine into the UniversalMapper.
Obviously, the Get aspect of the Mapper.Engine property is not just a memory reference to an object. And yes, a quick glance at the code inside Automapper confirms that.
So, assigning the result of the Get property to the _mappingEngine field of the UniversalMapper must happen after that engine has been configured.
I've been bumbling along with EF5 but I cant seem to get two domain classes to map to a single database table.
The error I get is:
Message: "The type 'Basd.Erp.Wms.Purchasing.SupplierProfile' has already been configured as an entity type. It cannot be reconfigured as a complex type."
This is my DbContext:
public class PurchasingContext : DisconnectedEntityContext
{
public DbSet<SupplierCard> Suppliers { get; set; }
public DbSet<PurchaseCategory> PurchaseCategories { get; set; }
public PurchasingContext() : this("Basd.Erp.Wms") { }
public PurchasingContext(string connectionStringName) : base(connectionStringName) { }
public static PurchasingContext GetInstance(EfDataProvider provider) { return new PurchasingContext(provider.ConnectionStringName); }
}
}
These are my classes:
namespace Basd.Erp.Wms.Purchasing
{
public class SupplierCard : ContactCard, ISupplierCard
{
private ICollection<PurchaseCategory> _purchaseCategories;
public ICollection<PurchaseCategory> PurchaseCategories
{
get { return _purchaseCategories; }
set { SetNotifyField(ref _purchaseCategories, value, () => PurchaseCategories); }
}
public SupplierProfile Profile { get; protected set; }
private SupplierCard()
{
this.Profile = new SupplierProfile();
this.PurchaseCategories = new Collection<PurchaseCategory>();
}
public SupplierCard(long id, string alf, string name)
: this(id, alf, new SimpleNameHolder(name), new Collection<IPhysicalAddress>(), new DigitalAddresses()) { }
public SupplierCard(long id, string alf, INameHolder nameHolder,
ICollection<IPhysicalAddress> physicalAddresses, IDigitalAddresses digitalAddresses)
: this(id, alf, nameHolder, physicalAddresses, digitalAddresses, null) { }
public SupplierCard(long id, string alf, INameHolder nameHolder,
ICollection<IPhysicalAddress> physicalAddresses, IDigitalAddresses digitalAddresses, IValidatableObject validator)
: base(id, alf, nameHolder, physicalAddresses, digitalAddresses, validator)
{
this.Profile = new SupplierProfile();
this.PurchaseCategories = new Collection<PurchaseCategory>();
}
}
}
public class SupplierProfile : AbstractAspect
{
private TradingEntity _incType;
public TradingEntity BusinessType
{
get { return _incType; }
set
{
if (_incType != null) { this.DeregisterSubPropertyForChangeTracking(this.BusinessType); }
_incType = value; this.OnPropertyChanged("TradingType");
this.RegisterSubPropertyForChangeTracking(this.BusinessType);
}
}
private bool _emailOk;
private bool _smailOk;
public bool MarketingEmailOk
{
get { return _emailOk; }
set { _emailOk = value; this.OnPropertyChanged("MarketingEmailOk"); }
}
public bool MarketingSmailOk
{
get { return _smailOk; }
set { _smailOk = value; this.OnPropertyChanged("MarketingSmailOk"); }
}
public SupplierProfile()
: base()
{
this.BusinessType = new TradingEntity(ContactLegalType.Limited);
}
}
}
These are my configuration classes:
[Export(typeof(IEntityConfiguration))]
public class SupplierCardConfiguration
: EntityTypeConfiguration<SupplierCard>, IEntityConfiguration
{
public SupplierCardConfiguration()
{
this.ToTable("SupplierCard", "erp_wms");
HasKey(u => u.Id);
Property(u => u.Id).HasColumnName("SupplierId");
Ignore(u => u.UsePropertyNotifications);
Property(u => u.Profile.MarketingEmailOk).HasColumnName("MarketingEmailOk");
HasMany(i => i.PurchaseCategories)
.WithMany(c => c.Suppliers)
.Map(mc =>
{
mc.MapLeftKey("CategoryId");
mc.MapRightKey("SupplierId");
mc.ToTable("SupplierPurchaseCategory", "erp_wms");
});
}
public void AddConfiguration(ConfigurationRegistrar registrar)
{
registrar.Add(this);
}
}
[Export(typeof(IEntityConfiguration))]
public class SupplierProfileConfiguration
: EntityTypeConfiguration<SupplierProfile>, IEntityConfiguration
{
public SupplierProfileConfiguration()
{
this.ToTable("SupplierCard", "erp_wms");
Ignore(u => u.UsePropertyNotifications);
Property(u => u.MarketingEmailOk).HasColumnName("MarketingEmailOk");
}
public void AddConfiguration(ConfigurationRegistrar registrar)
{
registrar.Add(this);
}
}
UPDATE:
Ok so Ive tried ignoring SupplierProfile as per suggestion that changed nothing. I then tried removing the configuration class for Supplier Profile and left
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<SupplierProfile>();
base.OnModelCreating(modelBuilder);
}
and that generated an error:
{"The property 'Profile' is not a declared property on type
'SupplierCard'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property."}
[System.InvalidOperationException]: {"The property 'Profile' is not a declared property on type 'SupplierCard'. Verify that the
property has not been explicitly excluded from the model by using the
Ignore method or NotMappedAttribute data annotation. Make sure that it
is a valid primitive property."}
I then tried removing the
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<SupplierProfile>();
base.OnModelCreating(modelBuilder);
}
while leaving out the configuration class for SupplierProfile and that generates the error:
Message: "Invalid column name
'Profile_BusinessType_ContactLegalType'.\r\nInvalid column name
'Profile_BusinessType_TradingSince'.\r\nInvalid column name
'Profile_BusinessType_State'.\r\nInvalid column name
'Profile_BusinessType_UsePropertyNotifications'.\r\nInvalid column
name 'MarketingEmailOk'.\r\nInvalid column name
'Profile_MarketingSmailOk'.\r\nInvalid column name
'Profile_State'.\r\nInvalid column name
'Profile_UsePropertyNotifications'.\r\nInvalid column name
'OwnerId'.\r\nInvalid column name 'State'."
So like I said, just **bumbling** along ;)
After reading this I think it might have something to do with your relationship in your SupplierCard class.
public class SupplierCard : ContactCard, ISupplierCard
{
public SupplierProfile Profile { get; protected set; }
}
I'm guessing it registering as a complex type when SupplierCard is mapped.
A suggested way to fix it is to ignore it.
modelBuilder.Ignore<SupplierProfile>();
I've never run into this problem myself, so not sure if this'll help.
So after a lot of mucking around it turns out the underlying problem is a bug in Entity Framework 5. This bug has been fixed in EF6 beta. All other errors were in fact just masking this underlying error.
The following explaination is not terribly good as I dont fully understand it myself.
Short answer is: Use EF6 or otherwise modify EF5 source code.
Turns out that if you have a class in assembly B, that has a property of a type of enum defined in Assembly A, EF5 gets confused and thinks the enum is missing or somehow unavailable and sets about trying to generate the type itself.
So I had:
Assembly A containing enum type AA.
Assembly B referencing Assembly A so a contained class BB could have a property of type AA.
An EF5 data layer Assembly referencing both Assembly A & B.
An EF5 configuration layer Assembly referencing both Assembly A & B.
And it failed.
But if I "simply" move enum type AA into Assembly B then everything works.
This is of course is completely useless because then I set up all kinds of dependencies on Assembly B for any Assembly that has a member who needs an enum AA. But that is the test.
To top it off there also appears to be some particular set of circumstances in which everything I just said does not apply due to the order assemblies are loaded at runtime. The order of this loading cannot be forced i.e. it's non-determinant so it's pot luck.
This is my test
[TestClass]
public class RepositoryTests
{
private APurchaseOrderRepository _repository;
[TestInitialize]
public void TestInitialize()
{
_repository = new FakePurchaseOrderRepository();
}
[TestMethod]
public void RepositoryGetPurchaseOrdersForStoreCallsValidatePurchaseOrders()
{
var store = new Store();
var mockRepo = new Mock<APurchaseOrderRepository>();
mockRepo.Protected().Setup("ValidatePurchaseOrders", ItExpr.IsAny<List<PurchaseOrder>>());
_repository.GetPurchaseOrders(store);
mockRepo.Protected().Verify("ValidatePurchaseOrders", Times.Once(), ItExpr.IsAny<List<PurchaseOrder>>());
}
}
APurchaseOrderRepository and it's interface look like this
public interface IPurchaseOrderRepository
{
List<PurchaseOrder> GetPurchaseOrders(Store store);
}
public abstract class APurchaseOrderRepository : IPurchaseOrderRepository
{
public abstract List<PurchaseOrder> GetPurchaseOrders(Store store);
protected virtual bool ValidatePurchaseOrders(List<PurchaseOrder> purchaseOrders)
{
return true;
}
}
And my Fake
public class FakePurchaseOrderRepository : APurchaseOrderRepository
{
public override List<PurchaseOrder> GetPurchaseOrders(Store store)
{
var purchaseOrders = new List<PurchaseOrder>();
ValidatePurchaseOrders(purchaseOrders);
return purchaseOrders;
}
}
However, my test fails with:
Test method
PreSwapTests.RepositoryTests.RepositoryGetPurchaseOrdersForStoreCallsValidatePurchaseOrders
threw exception: Moq.MockException: Expected invocation on the mock
once, but was 0 times: mock =>
mock.ValidatePurchaseOrders(It.IsAny())
Configured setups: mock =>
mock.ValidatePurchaseOrders(It.IsAny()), Times.Never No
invocations performed.
What am I doing wrong?
Notes:
Moq.4.0.10827
Update:
I think it is this line mockRepo.Protected().Setup("ValidatePurchaseOrders");, because I need to add the parameters to it as a second argument, but I can't seem to get it correct.
Update 2:
Made some modifications, now it compiles, but isn't counting correctly...or something, error message and code are both updated above.
Realized I was doing this all wrong, changed my objects to work with this test
[TestMethod]
public void RepositoryGetPurchaseOrdersForStoreCallsValidatePurchaseOrders()
{
var store = new Store();
var mockPurchaseOrderProvider = new Mock<IPurchaseOrderProvider>();
var mockPurchaseOrderValidator = new Mock<IPurchaseOrderValidator>();
var purchaseOrderRepository = new PurchaseOrderRepository(mockPurchaseOrderProvider.Object, mockPurchaseOrderValidator.Object);
mockPurchaseOrderValidator.Setup(x => x.ValidatePurchaseOrders(It.IsAny<List<PurchaseOrder>>()));
purchaseOrderRepository.GetPurchaseOrders(store);
mockPurchaseOrderValidator.Verify(x => x.ValidatePurchaseOrders(It.IsAny<List<PurchaseOrder>>()), Times.Once());
}
This is a much better structure now I think.
It's because ValidatePurchaseOrders is not in your IPurchaseOrderRepository interface.
The repository is declared as private IPurchaseOrderRepository _repository; so it can only see what is in the interface.