We are working with AutoMapper 4.1.1 (will soon update to 6, but we need some time to change the project) and have this issue: in the source class we have a property "int? MyProp" while in the destination we have string "MyPropValue", which should not be mapped from MyProp. AutoMapper however does map it when the value of MyProp is not null (using MyProp.Value, flattened). We could use Ignore() to ignore the mapping but as we have dozens of such properties, we are looking for a way to do it with one configuration, without breaking the rest of the mapping.
Here is some example code:
class Program
{
static void Main(string[] args)
{
var map = Mapper.CreateMap<Dto, Model>();
var dto = new Dto() {MyProp = 10};
var model = Mapper.Map<Dto, Model>(dto);
Console.WriteLine(model.MyPropValue); // MyPropValue should be empty but it gets mapped
}
}
public class Dto
{
public int? MyProp { get; set; }
}
public class Model
{
public string MyPropValue { get; set; }
}
Mapper.CreateMap<SourceType, DestinationType>()
.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
Allows you to skip all nulls.
Automapper skip null values with custom resolver
Related
The ABP multi lingual mapping was not being called by AutoMapper ProjectTo. If it work, it suppose to map the translation entity to DTO's name property. It works with my old code without using ProjectTo. Please refer to the sample code below:
NOTE : I am using ABP version 4.8.1
NOTE : I have included the Translation DTO in ProductDTO to force auto mapper's projectTo to eagerly load Translation data when generating the IQueryable.
ProductAppService.cs
public async Task<ICollection<ProductListDto>> GetAll()
{
return await repository.GetAll().ProjectTo<ProductListDto>().ToListAsync();
}
Product.cs
public class Product : Entity, IMultiLingualEntity<ProductTranslation>
{
public ICollection<ProductTranslation> Translations { get; set; }
}
ProductTranslation.cs
public class ProductTranslation : Entity, IEntityTranslation<Product>
{
public string Name { get; set; }
}
ProductDto.cs
public class ProductListDto
{
// Mapped from ProductTranslation.Name
public string Name { get; set; }
// Purposely include the translations dto here to force automapper to eagerly load the translation
// data from DB
[JsonIgnore]
public ICollection<ProductTranslationDto> Translations { get; set; }
}
Module.cs
public override void PreInitialize()
{
Configuration.Modules.AbpAutoMapper().Configurators.Add(cfg =>
{
MultiLingualMapContext context = new MultiLingualMapContext(IocManager.Resolve<ISettingManager>());
cfg.DisableConstructorMapping();
cfg.AddCollectionMappers();
CustomDtoMapper.CreateMappings(cfg);
});
}
CustomDtoMapper.cs
cfg.CreateMultiLingualMap<Product, ProductTranslation, ProductListDto>(context);
could you check those links, it appears to be an old issue based on github
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3356
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/e0ded5d8702f389aa1f5947d3446f16aec845287/test/Abp.ZeroCore.Tests/Zero/MultiLingual/MultiLingual_Entity_Tests.cs
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/e0ded5d870/test/Abp.ZeroCore.SampleApp/Application/Shop/ProductAppService.cs#L48
And Here is an StackOverFlow Question about it.
Data localization in mapping in ASP.NET Zero
Please let me know if one of them works for you.
Although I am able to access the SchemaVersion using code below, I cannot access FormatDocID nested element.
Any ideas how can I easily get FormatDocID using ServiceStack and AutoQueryFeature (or similar)?
I put only relevant parts of code here
public override void Configure(Container container)
{
JsConfig.DateHandler = DateHandler.ISO8601;
SetupValidators(container);
SetupIOC(container);
SetupPlugins(container, log);
ContentTypes.Register("application/xml"
, CLXmlSerializer.Serialize, ServiceStack.Text.XmlSerializer.DeserializeFromStream);
SetupMetaDataRedirectionPath();
SetupGlobalResponseFilters();
}
Setup plugins
private void SetupPlugins(Container container)
{
Plugins.Add(new ValidationFeature());
Plugins.Add(new SwaggerFeature());
Plugins.Add(new AutoQueryFeature
{
MaxLimit = 1000,
EnableUntypedQueries = false,
IncludeTotal = true
});
Plugins.Add(new AutoQueryDataFeature {MaxLimit = 100}
.AddDataSource(ctx => ctx.MemorySource(new List<WordDocument>
{
new WordDocument()
{
SchemaVersion = "",
Format = new Word.DocumentFormat()
{
FormatDocID = 254
}
}
}))
);
typeof(RequestLogs).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(AssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(UnAssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(Authenticate).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
}
Serializable classes
public abstract class Document
{
public DocumentFormat Format;
public class DocumentFormat
{
[XmlAttribute] public int Version;
public int FormatDocID;
public string DocShortName;
}
}
public class WordDocument : Document
{
[XmlAttribute] public string SchemaVersion { get; set; } = "1.0";
}
Thanks in advance for the answers.
It's not clear what you're trying to achieve or why, AutoQuery creates Auto Queryable APIs where the Response is the API Response serialized in the specified Response Content Type.
If you want to intercept the Typed Response DTO before it's returned you can create a Custom AutoQuery Implementation and introspect the response that way, e.g:
public class MyQueryServices : Service
{
public IAutoQueryData AutoQuery { get; set; }
//Override with custom implementation
public object Any(MyQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
var response = AutoQuery.Execute(query, q);
return response;
}
}
But the AutoQuery Memory Data Source you're using lets you provide your own collection of Typed POCOs as the Data source so you already have access to them when you create it, but the source POCOs should be a flat Type with public properties (in contrast to your class with public fields and nested types) - it's not possible to query nested object graph values.
This is an example of a POCO that doesn't use nested classes, or public fields:
public abstract class Document
{
public int Version { get; set; }
public int FormatDocID { get; set; }
public string DocShortName { get; set; }
}
So the solution if you want to use AutoQuery would be to change your Data Source to use Flat POCOs with public properties otherwise you'd need to create the impl of your Service yourself.
I often have Models and Dtos like this:
// DTO class
public class OrderDto
{
public int Id { get; set; }
public DateTime Date { get set; }
public int ProductId { get; set; }
// ...
}
// Model class
public class Order : BaseObject
{
public DateTime Date { get set; }
public Product Product { get; set; }
// ...
}
// Model class
public class Product : BaseObject
{
// ...
}
In order to map my OrderDto to the Order class I have to configure AutoMapper for this particular "association" like so:
CreateMap<OrderDto, Order>()
.ForMember(m => m.Product, d => d.ResolveUsing((d, m) =>
{
return m.Session.GetObjectByKey<Product>(dto.ProductId);
}))
This is quite cumbersome to do this for each case like this. Therefore I was looking into generalizing this behaviour by using a custom TypeConverter class:
public class IntBaseObjectTypeConverter : ITypeConverter<int, BaseObject>
{
private UnitOfWork uow;
// ...
public BaseObjectConvert(int source, BaseObject destination, ResolutionContext context)
{
return uow.Session.GetObjectByKey(typeof(destination), source);
}
}
However, this will fail of course if the destination is null. Unfortunately the ResolutionContext does not give me any clue about the specific type of the destination property.
Now my question is, if there is another way to achieve with AutoMapper what I would like to do?
Please note that I am not using Entity Framework which of course would solve this issue on the model level with foreign key and navigational properties. I use XPO from DevExpress which does not allow foreign key and navigational properties like in Entity Framework.
I have a few properties that I don't have a direct mapping in the database for, so I'm using the convention of having another variable that is mapped to the database, and a public variable that will be used to do all of my actual work. The common one is [mapping a boolean property to a char column][1], but I also have a StatusID property whose C# enum is different based on the derived type.
My public property has the [NotMapped] attribute on it, and my internal property has the [Column] attribute. I think there's something that because the public property isn't mapped, it's keeping the other property from being mapped as well.
In my project, I start with an abstract base Message class:
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
internal string dbIsSample { get; set; }
[Column("msgStatusID")]
internal int? dbStatusId { get; set; }
[NotMapped]
public bool IsSample {
get {
return dbIsSample.ToUpper() == "Y";
}
set {
dbIsSample = value ? "Y" : "N";
}
}
public Message() {
this.IsSample = false;
this.dbStatusId = null;
}
}
Right now I only have a single class implementing the base class, Request:
public class Request : Message {
[NotMapped]
public int Status {
get {
return this.dbStatusId.HasValue ? this.dbStatusId.Value : 1;
}
set {
this.dbStatusId = value;
}
}
public Request()
: base() {
this.Status = 1;
}
}
Here is my context:
public class MyContext : DbContext {
public DbSet<Message> Messages { get; set; }
static MyContext() {
Database.SetInitializer<MyContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Message>()
.Map<Request>(m => m.Requires("msgTypeID").HasValue(1));
}
}
Is this something that anyone else has run across? I haven't been able to find anything about why this isn't working, even though this looks like the accepted convention until the EF team adds additional custom mapping. Someone else has to have run across this issue.
When I try to execute this code, I get a DbUpdateException saying that it can't insert a NULL into column "msgIsSample" due to my having set that in the table creation script. This doesn't make any sense because the msgIsSample is defaulted to have a "N".
Instead of making it internal, make it protected internal.
At runtime, EF will subclass your entity dynamically. These extended classes are called dynamic proxies.
EF cannot set your property because it does not have access. To give EF access to your property, it must have either public or protected access. You can still have internal properties, but give subclasses access by adding the protected modifier.
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
public string dbIsSample { get; protected internal set; }
[Column("msgStatusID")]
public int? dbStatusId { get; protected internal set; }
How can I create a Map with Automapper when in the underlying destination type a property not yet has initialized?
Example:
public class UserAccount
{
public string name { get; set; }
public Dictionary<string,string> properties { get; set; }
}
public class UserAccountOtherType
{
public string name { get; set; }
public string Property1 {get;set; }
}
public static UserAccount CustomMap(UserAccountOtherType type2)
{
AutoMapper.Mapper.CreateMap<UserAccount,UserAccountOtherType>()
.ForMember(dest => dest.properties["Property1", opt => opt.MapFrom(src => (string)src.Property1));
return AutoMapper.Mapper.Map<UserAccount,UserAccountOtherType>(type2);
}
When I try to execute this code it fails because the Dictionary in UserAccount is not yet initialized. I cannot initialize the Object by myself because the UserAccount Class is a Datacontract of a WCF Serviceinterface.
I have to create a Dicationary by myself and assign it to the property.
UserAccount b = new UserAccount();
Dictionary<string,string> properties = new Dictionary<string,string>();
b.properties = properties;
How can I solve this with Automapper? Or is my approach not senseful?
Only way I can think of to do something like this is to write a class that implements AutoMapper.IValueResolver. Then opt.MapFrom... becomes opt.ResolveUsing....FromMember... Probably at that point your destination is the entire dictionary (I think if you leave out the FromMember you get the whole object into your resolver
in your implementation of IValueResolver.Resolve, try breaking into the debugger and checking out the ResolutionContext in source.Context, then once you have your dictionary built, return source.New(myDictionary)