What does UseDestinationValue do?
I am asking because I have a base and inherited class, and for the base class, I would love to have AutoMapper take existing values for me.
Will it do that? (I have looked and the only examples I can see for UseDestinationValue involve lists. Is it only for lists?
could I do this:
PersonContract personContract = new PersonContract {Name = 'Dan'};
Person person = new Person {Name = "Bob"};
Mapper.CreateMap<PersonContract, Person>()
.ForMember(x=>x.Name, opt=>opt.UseDestinationValue());
person = Mapper.Map<PersonContract, Person>(personContract);
Console.WriteLine(person.Name);
and have the output be bob?
I wrote this whole question up and then thought, DUH! just run it and see.
It works as I had hoped.
This is the code I ended up with:
class Program
{
static void Main(string[] args)
{
PersonContract personContract = new PersonContract {NickName = "Dan"};
Person person = new Person {Name = "Robert", NickName = "Bob"};
Mapper.CreateMap<PersonContract, Person>()
.ForMember(x => x.Name, opt =>
{
opt.UseDestinationValue();
opt.Ignore();
});
Mapper.AssertConfigurationIsValid();
var personOut =
Mapper.Map<PersonContract, Person>(personContract, person);
Console.WriteLine(person.Name);
Console.WriteLine(person.NickName);
Console.WriteLine(personOut.Name);
Console.WriteLine(personOut.NickName);
Console.ReadLine();
}
}
internal class Person
{
public string Name { get; set; }
public string NickName { get; set; }
}
internal class PersonContract
{
public string NickName { get; set; }
}
The output was:
Robert
Dan
Robert
Dan
I was brought here having a similar question, but in regards to nested classes and keeping the destination value. I tried above in any way I could, but it did not work for me, it turns out you have to use UseDestinationValue on parent object as well. I am leaving this here in case anyone else has the same issue I did. It took me some time to get it working. I kept thinking the issue lies within AddressViewModel => Address mapping.
In BidderViewModel class, BidderAddress is of type AddressViewModel. I needed the Address ID to be preserved in situations where it is not null.
Mapper.CreateMap<BidderViewModel, Bidder>()
.ForMember(dest => dest.BidderAddress, opt=> opt.UseDestinationValue())
.ForMember(dest => dest.ID, opt => opt.UseDestinationValue());
Mapper.CreateMap<AddressViewModel, Address>()
.ForMember(dest => dest.ID, opt => { opt.UseDestinationValue(); opt.Ignore(); });
Use (where viewModel is of type BidderViewModel as returned by the View in MVC):
Bidder bidder = Mapper.Map<BidderViewModel, Bidder>(viewModel, currentBid.Bidder)
Related
I'm using Dapper Extensions and have defined my own custom mapper to deal with entities with composite keys.
public class MyClassMapper<T> : ClassMapper<T> where T : class
{
public MyClassMapper()
{
// Manage unmappable attributes
IList<PropertyInfo> toIgnore = typeof(T).GetProperties().Where(x => !x.CanWrite).ToList();
foreach (PropertyInfo propertyInfo in toIgnore.ToList())
{
Map(propertyInfo).Ignore();
}
// Manage keys
IList<PropertyInfo> propsWithId = typeof(T).GetProperties().Where(x => x.Name.EndsWith("Id") || x.Name.EndsWith("ID")).ToList();
PropertyInfo primaryKey = propsWithId.FirstOrDefault(x => string.Equals(x.Name, $"{nameof(T)}Id", StringComparison.CurrentCultureIgnoreCase));
if (primaryKey != null && primaryKey.PropertyType == typeof(int))
{
Map(primaryKey).Key(KeyType.Identity);
}
else if (propsWithId.Any())
{
foreach (PropertyInfo prop in propsWithId)
{
Map(prop).Key(KeyType.Assigned);
}
}
AutoMap();
}
}
I also have this test case to test my mapper:
[Test]
public void TestMyAutoMapper()
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(MyClassMapper<>);
MySubscribtionEntityWithCompositeKey entity = new MySubscribtionEntityWithCompositeKey
{
SubscriptionID = 145,
CustomerPackageID = 32
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
var result = connection.Insert(entity);
var key1 = result.SubscriptionID;
var key2 = result.CustomerPackageID;
}
}
Note that I set the default mapper in the test case.
The insert fails and I notive that my customer mapper is never called. I have no documentation on the github page on the topic, so I'm not sure if there's anything else I need to do to make dapper extensions use my mapper.
Thanks in advance!
Looking at your question, you are attempting to write your own defalut class mapper derived from the existing one. I never used this approach; so I do not know why it is not working or whether it should work.
I explicitly map the classes as below:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
}
public sealed class CustomerMapper : ClassMapper<Customer>
{
public CustomerMapper()
{
Schema("dbo");
Table("Customer");
Map(x => x.CustomerID).Key(KeyType.Identity);
AutoMap();
}
}
The AutoMap() will map rest of the properties based on conventions. Please refer to these two resources for more information about mapping.
Then I call SetMappingAssemblies at the startup of the project as below:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { Assembly.GetExecutingAssembly() });
The GetExecutingAssembly() is used in above code because mapping classes (CustomerMapper and other) are in same assembly which is executing. If those classes are placed in other assembly, provide that assembly instead.
And that's it, it works.
To set the dialect, I call following line just below the SetMappingAssemblies:
DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();
Use your preferred dialect instead of SqlServerDialect.
Apparently, the solution mentioned here may help you achieve what you are actually trying to. But, I cannot be sure, as I said above, I never used it.
We have an Department model (domain-driven design). Each department has its child departments, so domain model looks like
public class Department
{
int Id { get; set; }
...
ICollection<Department> Children { get; set; }
}
At the API domain models of the same hierarchy path, coming from repository, it will transforms to DTO trough AutoMapper and does not include children by default.
public class DepartmentDto
{
int Id { get; set; }
...
ICollection<DepartmentDto> Children { get; set; } // Empty set.
}
Does it a good way to add [NotMapped] bool HasChildren property to the Department domain model to show or hide expand arrows at the client? For lazy load.
This field smells strange: can be filled or can be not (depends on query).
Repository returns a collection of departments, belongs to parent Id (may become Null to root nodes):
ICollection<Department> GetDepartments(int? parentId = null);
So, based on Lucian Bargaoanu comments, I've found the solution:
IDepartmentRepository.cs
IQueryable<Department> GetDepartmentsQuery(int? parentId = null);
DepartmentsController.cs (API):
[HttpGet]
public async Task<ActionResult<ICollection<DepartmentDto>>> GetRootDepartments()
{
var dtoItems = await _repository.GetDepartmentsQuery()
.ProjectTo<DepartmentDto>(_mapper.ConfigurationProvider)
.ToListAsync();
return Ok(dtoItems);
}
AutoMapper configuration:
CreateMap<Department, DepartmentDto>()
.ForMember(x => x.HasChildren,
opts => opts.MapFrom(x => x.Children.Any()))
.ForMember(x => x.Children,
opts => opts.Ignore());
I have 2 classes, Class1 should be mapped to Class2. I do mapping with AutoMapper. I'd like to test my configuration of the mapper and for this purposes I'm using AutoFixture. Source class Class1 has property of type IList<>, destination class Class2 has a similar property but of type IEnumerable<>. To simplify test preparation I'm using AutoFixture (with AutoMoqCustomization) to initialize both source and destination objects. But after initializing property of type IEnumerable<> with AutoFixture, AutoMapper can't map the property.
Error text:
Error mapping types.
Mapping types: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Type Map configuration: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Property: Items
Could anybody help me to configure either AutoMapper or AutoFixture to make the mapping work? As a workaround I can assign null to the destination property, but I do not want to do this in the each test.
Simplified example of code:
public class AutoMapperTests
{
public static void TestCollectionsProperty()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var fixture = new Fixture();
var dst = fixture.Create<Class2>();
Mapper.Map(src, dst); //Error at this line of code
}
}
public class Class1
{
public IList<ItemClass1> Items { get; set; }
}
public class Class2
{
public IEnumerable<ItemClass2> Items { get; set; }
}
public class ItemClass1
{
public string Text { get; set; }
}
public class ItemClass2
{
public string Text { get; set; }
}
It's not really an AutoFixture issue per se. You can reproduce it without AutoFixture by instead creating dst like this:
var dst = new Class2();
dst.Items = Enumerable.Range(0, 1).Select(_ => new ItemClass2());
This will produce a similar error message:
Unable to cast object of type 'WhereSelectEnumerableIterator2[System.Int32,Ploeh.StackOverflow.Q45437098.ItemClass2]' to type 'System.Collections.Generic.IList1[Ploeh.StackOverflow.Q45437098.ItemClass2]'
That ought to be fairly self-explanatory: WhereSelectEnumerableIterator<int, ItemClass2> doesn't implement IList<ItemClass2>. AutoMapper attempts to make that cast, and fails.
The simplest fix is probably to avoid populating dst:
var dst = new Class2();
If you must use AutoFixture for this, you can do it like this:
var dst = fixture.Build<Class2>().OmitAutoProperties().Create();
Unless the Class2 constructor does something complex, however, I don't see the point of using AutoFixture in that scenario.
If, on the other hand, you do need dst to be populated, you just need to ensure that dst.Items is convertible to IList<ItemClass2>. One way to do that would be like this:
var dst = fixture.Create<Class2>();
dst.Items = dst.Items.ToList();
You could create a Customization to make sure that this happens automatically, but if you need help with that, please ask a new question (if you don't find one that already answers that question).
Here is a working example for your problem. As #Mark Seemann already told, Mapper.CreateMap has been deprecated, so this example is using the new structure.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var dest = Mapper.Map<Class1, Class2>(src);
AM requires IList because you're mapping to an existing list and that works by calling IList.Add.
I have few classes and they have multiple list items like below:
public class Request1
{
public List<AdditionalApplicantData> AdditionalApplicantData { get; set;}
public List<ApplicantData> ApplicantData { get; set; }
}
public class Request2
{
public List<ApplicantDetails> ApplicantData { get; set; }
}
I want to map Request1 to Request2 but List of ApplicantData has to be mapped from multiple sources like List of ApplicantData & List of AdditionalApplicantData but not sure how to achieve it can someone please help me here?
You can use function below with createMap() function. Source: https://github.com/AutoMapper/AutoMapper/wiki/Before-and-after-map-actions
.AfterMap((src, dest) => {
dest.ApplicantData = /*your logic here*/
});
And you should mark ApplicantData as don't map because you have a variable named ApplicantData at the source class. You should implement the logic yourself.
EDIT:
When you are initializing mapper, you create map for each object. So for your case it would be like:
Mapper.Initialize(cfg => {
cfg.CreateMap<Request1, Request2>()
.ForMember(x => x.ApplicantData, opt => opt.Ignore()) //You want to implement your logic so ignore mapping
.AfterMap((src, dest) =>
{
dest.ApplicantData = /*implement your logic here*/
});
});
public class ApplicantDetailsResolver : IValueResolver<Request1, Request2, List<ApplicantDetails>>
{
public List<ApplicantDetails> Resolve(Request1 source, Request2 destination,List<ApplicantDetails> destMember, ResolutionContext context)
{
destination.ApplicantDetails = context.Mapper.Map<List<ApplicantDetails>>(source.ApplicantData);
for (int i = 0; i < destination.ApplicantDetails.Count(); i++)
{
context.Mapper.Map(source.AdditionalApplicantData.ElementAt(i), destination.ApplicantDetails.ElementAt(i));
}
return destination.ApplicantDetails;
}
}
I have written above custom value resolver for mapping list from multiple sources and its working fine but problem, is it can't match properties which are differently named, is there way I can handle this scenario as well?
This code works fine.
using (ContextDB db = new ContextDB())
{
var custAcct = (from c in db.CustAccts
select new
{
c.AcctNo,
c.Company,
c.UserName
}).ToList();
But this one doesn't
public class CustAcct
{
public int AcctNo { get; set; }
public string Company { get; set; }
public string UserName { get; set; }
}
....
....
....
using (ContextDB db = new ContextDB())
{
CustAcct custAcct = (from c in db.CustAccts
select new
{
c.AcctNo,
c.Company,
c.UserName
}).ToList();
It returns this error:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'EMailReader.Models.CustAcct'. An explicit conversion exists (are you missing a cast?)
I used Google, found many related topics but still couldn't put it to work using the available solutions
I just need to return data to a strong typed model.
EDITED:
After more research I found this solution bellow, but I wonder why I cannot retrieve directly in the list from LinqToSql.
List<CustAcct> temp = new List<CustAcct>();
IEnumerable<dynamic> items = custAcct;
foreach (var item in items)
{
temp.Add(new CustAcct()
{
AcctNo = item.AcctNo,
Company = item.Company,
UserName = item.UserName,
});
}
You are re defining those properties by creating new Class. And this will override LINQ2SQL generated class.
Just change "public class CustAcct" to "public partial class CustAcct".
This will solve your problem, and you do not need to define those properties again. Remove those from your class. Those will be automatically create for you.
If you can just post your class, and I will change it for you.
//Shyam