AutoMapper unmapped Property by using expression mapping - c#

I try to map business objects to persistence objects using expression mapping.
I was able to simplify the problem to this simple usage of AutoMapper.
Classes to map:
public class X
{
public X(string name) => this.Name = name;
public string Name { get; }
}
public class Y
{
public string Name { get; set; }
}
These classes are very simple but shows the problem. X is immutable. That's no problem for AutoMapper. I created a map like:
var mapperConfig = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<X, Y>().ReverseMap();
});
var mapper = mapperConfig.CreateMapper();
This works as expected in this case:
var yInstances = new List<Y> { new Y { Name = "Foo" } }; // Just to have some instances.
var xInstances = yInstances.Select(mapper.Map<X>).ToList();
As expected I got a List of X-Instances.
But I tried to use it as IQueryable<T> using expression mapping:
Expression<Func<X, bool>> filter = item => item.Name == "Foo";
// this line thows exception
yInstances.AsQueryable().Where(mapper.MapExpression<Expression<Func<Y, bool>>>(filter)).ToList();
The last line throws an exception containing this message:
Unhandled exception. AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
=======
Y -> X (None member list)
Y -> X (None member list)
Unmapped properties:
Name
There is no unmapped property because mapper.Map<X> is working.
I tried to add .ForCtorParam("name", opt => opt.MapFrom(src => src.Name)). But this doesn't work - not surprising.
What am I doing wrong? I'm pretty sure that this is only a single step but I'm unable to find it. Sorry.
The Code is on .Net Fiddle that illustrates the problem.

Related

Automapper: validation exception ignoring destination property

I have troubles trying to ignore a destination property
Source class:
public class ClassDto
{
public int Id { get; set; }
}
Destination class:
public class ClassModel
{
public int Id { get; set; }
public IList<string> ListString { get; set; }
}
Example:
public class Program
{
static void Main(string[] args)
{
var dto = new ClassDto { Id = 1 };
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClassDto, ClassModel>().
ForMember(i => i.ListString, opt => opt.DoNotUseDestinationValue());
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var model = mapper.Map<ClassDto, ClassModel>(dto);
}
}
Unfortunately config.AssertConfigurationIsValid(); rises an exception:
$exception {"\nUnmapped members were found. Review the types and members below.\nAdd a custom mapping expression, ignore, add a custom
resolver, or modify the source/destination type\nFor no matching
constructor, add a no-arg ctor, add optional arguments, or map all of
the constructor
parameters\n===================================================\r\nClassDto
-> ClassModel (Destination member list)\r\nAutoMapperFoo.ClassDto -> AutoMapperFoo.ClassModel (Destination member list)\r\n\r\nUnmapped
properties:\r\nListString\r\n"} AutoMapper.AutoMapperConfigurationException
I can't undesrtand why, I explicit to ignore ListString by DoNotUseDestinationValue
Thanks in advance
By default, AutoMapper tries to map all the properties from the source
type to the destination type when both source and destination type
property names are the same. If you want some of the properties not to map
with the destination type property then you need to use the AutoMapper
Ignore Property in C#. Learn more AutoMapper Ignore Property in C#
Automapper gives the property Ignore which tells the mapper to not
take the value of a property from the source class. Ignore not only
ignore the mapping for the property, but also ignore the mapping of
all inner properties. It means that if the property is not a primitive
type, but another class, that if you are using Ignore, this class
properties won't be mapped. So, A.B.C would be A.Null.
So try using this:
.ForMember(x => x.ListString, opt => opt.Ignore())

Unable to resolve mapper issue using Automapper

I have two classes in my code except one data member everything is same. But, not getting how to resolve this issue using Automapper. Can someone please throw some light on this.
Class A{ public string Id; public List Values; };
Class B{ public string Id; public List Items; };
Class C{ public string Value; public string Name; public string Dept; };
CreateMap<A, B>(); - This is giving following error while doing unit test, Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
Explicit AutoMapper Property Mapping
CreateMap<A,B>()
.ForMember(dest => dest.Items,
opt => opt.MapFrom(src => src.Values));
Reference

Deep cloning via Automapper ignoring specific property from the hierarchy

I have fairly simple question regarding Automapper mapping definition. My intent is to deep clone an object via Automapper while ignoring 'Id' property, this is why i have chosen it to customize the mapping.
public interface IEntity<T>
{
T Id { get; }
}
public abstract class Entity : IEntity<Guid>
{
public Guid Id { get; set; }
}
All my entities are deriving from Entity class and i simply wants to ignore all Id property in the nested hierarchy of my object without being so explicit about the mapping definition.
So far i have come up with the following piece of code to do the cloning but how to ignore Id property mapping for the nested properties and not just for the root.
public static T AutomapperClone<T>(this T source)
where T : IEntity<Guid>
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<T, T>()
.ForMember(d => d.Id, o => o.Ignore());
});
// checking configuration validity
config.AssertConfigurationIsValid();
// creating mapper
var mapper = config.CreateMapper();
var copy = mapper.Map<T, T>(source);
return copy;
}
The idea is that all entities get their new Id instead of using the same mapped ones. Is it accomplishable via Automapper?
Appreciate your feedback.
I wouldn't use Automapper for this person, try AnyClone to do this. It does deep cloning and can ignore by property name which seems to be what you are looking for.

Linq update issue with lambda

I am trying to write some code in Linq with lambda.This is my first code using lambda and i am facing an issue while updating Record.
My code is:
using (DataClasses1DataContext db = new DataClasses1DataContext())
{
Table<NOTIF_RECIP> NOTIF_RECIP_alias = db.GetTable<NOTIF_RECIP>();
Table<NOTIF_SCHED> NOTIF_SCHED_alias = db.GetTable<NOTIF_SCHED>();
Table<mainframe_replication> mainframe_replication_alias = db.GetTable<mainframe_replication>();
var ids = NOTIF_SCHED_alias.Select(x => x.NOTIF_RPT_ID).ToArray();
foreach (string notif_sched_data in ids)
{
var repljoinmf = mainframe_replication_alias
.Join(NOTIF_RECIP_alias,
mfr => mfr.RPT_ID,
nr => nr.NOTIF_RECIP_ID,
(mfr, nr) => new
{
ReportId=mfr.RPT_ID,
Reportversion=mfr.RPT_VERS,
ReportBytes= mfr.RPT_BYTES.ToString(),
ReportDate=mfr.REPL_DTM.ToString(),
NotifId= mfr.NOTIF_ID,
RecipAdd=nr.NOTIF_RECIP_ADDR
});
foreach(var repljoinmf_data in repljoinmf)
{
//DO STUFF
repljoinmf_data.NotifId = "Changedxyz";
//db.SubmitChanges();
}
}
}
I am getting Error in repljoinmf_data.NotifId = "Changedxyz";
Error says: Error 2 Property or indexer 'AnonymousType#3.NotifId' cannot be assigned to -- it is read only
Can someone please help me in this.I think it is because I am using var which is anonymous but how to solve the problem.Any help is appreciated.
Thanks
As the error suggests, anonymous class instances cannot be modified once they have been projected.
Although you could switch to a strong typed class, and then reassign the member properties, however, you have an opportunity to project the desired result in the preceding LINQ statement into the same anonymous class:
var repljoinmf = mainframe_replication_alias
.Join(NOTIF_RECIP_alias, mfr => mfr.RPT_ID, nr => nr.NOTIF_RECIP_ID,
(mfr, nr) => new // Anon Class projection
{
ReportId=mfr.RPT_ID,
Reportversion=mfr.RPT_VERS,
ReportBytes= mfr.RPT_BYTES.ToString(),
ReportDate=mfr.REPL_DTM.ToString(),
NotifId= "Changedxyz", // *** No need to mutate this afterwards
RecipAdd=nr.NOTIF_RECIP_ADDR
});
Edit, Update isn't trivial assignment, suggested alternatives
Option #1 : Strongly typed Class with mutation after projection
Add a new class (I've guessed some types)
public class MyPoco
{
public int ReportId {get; set;}
public string Reportversion {get; set;}
public byte[] ReportBytes {get; set;}
public DateTime ReportDate {get; set;}
public int NotifId {get; set;}
public string RecipAdd {get; set;}
}
Which you can then project into (just specify the class name instead of anonymous):
(mfr, nr) => new MyPoco // Not anonymous
{
ReportId=mfr.RPT_ID,
...
And then do modification afterwards:
foreach(var repljoinmf_data in repljoinmf)
{
repljoinmf_data.NotifId = "SomeNewValue"
Option #2 - Create a method (or Func) which does the complex logic
Since you seem to have already materialized all the data, you are free to use complex functions in the property projections. Any of the available local variables (closure) are available to pass to thus function, as are the join lambda parameters (mfr, nr)
So for example, write a function to calculate your NotifId = "Changedxyz" replacement:
private string DoIntensiveLogic(mainframe_replication mfr, NOTIF_RECIP nr)
{
// Do Stuff
}
Which you can then use in your original anonymous projection:
(mfr, nr) => new // Anon Class projection
{
ReportId=mfr.RPT_ID,
Reportversion=mfr.RPT_VERS,
ReportBytes= mfr.RPT_BYTES.ToString(),
ReportDate=mfr.REPL_DTM.ToString(),
NotifId= DoIntensiveLogic(mfr, nr), // Call the function each row
RecipAdd=nr.NOTIF_RECIP_ADDR
});
Anonymous types are immutable and hence created cannot be changed you have to create a new type.
To solve your issue you have to create your own type and avoid the use of anonymous type when a future update is needed.
your type may look like this
public class ReportInfo
{
public int Id{get; set;}
//the same thing for others properties
}
and your query will look like this
new ReportInfo() {
Id = mfr.RPT_ID,
Reportversion = mfr.RPT_VERS,
ReportBytes = mfr.RPT_BYTES.ToString(),
ReportDate = mfr.REPL_DTM.ToString(),
NotifId = mfr.NOTIF_ID,
RecipAdd = nr.NOTIF_RECIP_ADDR
})
than you can update easily your property
foreach(var repljoinmf_data in repljoinmf)
{
//DO STUFF
repljoinmf_data.NotifId = "Changedxyz";
//db.SubmitChanges();
}
More about anonymous Types
what the compiler is actually doing. When you write a line of code like this:
var o = new { property1 = expression1, ..., propertyN = expressionN };
the compiler infers the type of each expression, creates private fields of these inferred types, creates
public read-only properties for each of the fields, and creates a constructor that accepts all these
expressions. The constructor’s code initializes the private read-only fields from the expression results
passed in to it. In addition, the compiler overrides Object’s Equals, GetHashCode, and ToString
methods and generates code inside all these methods.
if you want to change 'NotifId' later, you can find a record by id and change the property.
Example:
var alias = mainframe_replication_alias.SingleOrDefault(mfr => mfr.NOTIF_ID == repljoinmf_data.NotifId);
if(alias != null)
alias.NOTIF_ID = "Changedxyz";

AutoMapper include all properties of a custom List<T>

class SomeObject
{
public string name {get;set;}
}
class CustomCollection : List<SomeObject>
{
public int x {get;set;}
public string z {get;set;}
}
class A
{
public CustomCollection collection { get ; set; }
}
class B
{
public CustomCollection collection { get ; set; }
}
// Creating mapping
Mapper.CreateMap<A, B>();
When I Map A to B, all properties get mapped correctly except X and Z in CustomCollection.
CustomCollection correctly gets the List of SomeObject initialized and SomeObject.Name is also mapped correctly.
Only the custom properties X, Z that I've declared in the collection do not get mapped.
What am I doing wrong?
Only way I've found is to do an after mapping like below, but then it kinda defeats the purpose of using automapper and it breaks everytime I add a new property to CustomCollection.
Mapper.CreateMap<A, B>().AfterMap((source, destination) => {
source.x = destination.x;
source.z = destination.z ;
});
Your current mapping configuration does create a new CustomCollection but the SomeObject items inside are references to the objects in the source collection. If that's not an issue you can use the following mapping configuration:
CreateMap<CustomCollection, CustomCollection>()
.AfterMap((source, dest) => dest.AddRange(source));
CreateMap<A, B>();
If your are also fine with b.collection referencing to a.collection you could use the following mapping configuration:
CreateMap<CustomCollection, CustomCollection>()
.ConstructUsing(col => col);
CreateMap<A, B>();
AutoMapper is not designed for cloning so if you need that you must write your own logic for that.

Categories