There are two classes
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
I would like to map the ProductionDivision property to one of properties of class B depending on the condition - null(map to IsNullProductionDivision) or not null(map to ProductionDivisionValue) of the source property value. I can achieve it like as below.
CreateMap<A, B>()
.ForMember(g => g.IsNullProductionDivision, m =>
{
m.PreCondition(s => s.ProductionDivision == null);
m.MapFrom(b => true);
})
.ForMember(g => g.ProductionDivisionValue, m =>
{
m.PreCondition(s => s.ProductionDivision != null);
m.MapFrom(b => b.ProductionDivision);
});
If the value of {source property name} is null then the value of IsNull{source property name} is true.
Else if the value of {source property name} is not null then the value of {source property name}Value is the value of {source property name}.
I have many properties that respond this mapping rule. So, I don't want to write mapping rule for each properties like above. I want to configurate a rule for such mapping globally.
How can I configure AutoMapper so that it can handle such complex mapping?
I have found solution. The solution is pretty simple and clear. It turns out as follow:
Full code:
public class Program
{
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
public class StrinToBoolCustomResolver
: IValueConverter<string, bool>
{
public bool Convert(string sourceMember, ResolutionContext context)
{
return sourceMember == null;
}
}
public class MyAutoMapperProfile
: Profile
{
public MyAutoMapperProfile()
{
// add post and pre prefixes to add corresponding properties in the inner property map
RecognizeDestinationPostfixes("Value");
RecognizeDestinationPrefixes("IsNull");
// add mapping for "value" property
this.ForAllPropertyMaps(map => map.SourceMember.Name + "Value" == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return sMem != null;
});
expression.MapFrom(map.SourceMember.Name);
});
// add mapping for "IsNull" property
this.ForAllPropertyMaps(map => "IsNull" + map.SourceMember.Name == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return (bool)sMem;
});
//expression.MapFrom(map.SourceMember.Name);
expression.ConvertUsing<string, bool>(new StrinToBoolCustomResolver(), map.SourceMember.Name);
});
CreateMap<A, B>();
//.ForMember(g => g.IsNullProductionDivision, m =>
//{
// m.PreCondition(s => s.ProductionDivision == null);
// m.MapFrom(b => true);
//});
//.ForMember(g => g.ProductionDivisionValue, m =>
//{
// m.PreCondition(s => s.ProductionDivision != null);
// m.MapFrom(b => b.ProductionDivision);
//});
}
}
public static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg => {
cfg.AddProfile(new MyAutoMapperProfile());
});
var mapper = new Mapper(configuration);
mapper.ConfigurationProvider.AssertConfigurationIsValid();
var a = new A()
{
ProductionDivision = null
};
// b._productionDivisionCase will be equal IsNullproductionDivision
var b = mapper.Map<B>(a);
var c = new A()
{
ProductionDivision = "dsd"
};
// d._productionDivisionCase will be equal ProductionDivisionValue
var d = mapper.Map<B>(c);
}
}
Clarification:
add (post/pre)fixes to add corresponding properties to inner property map. It need to do here because our properties should be catched by AutoMapper. Otherwise properties will be abandoned and mapper configuration will be failed.
After that, we configurate how these properties need to be mapping. We call ForAllPropertyMaps method, filtering all properties and setting a rule to mapping properties suitable with our filter. When the mapper object is creating, the execution plan will be built taking look the specified filters.
For "Value" property we add a condition to check whether the source property is not null. If it is null, then mapping will be missed.
For "IsNull" property First of all, we add a converter to convert string type to bool type. The converter just compares the source string property with null value. If the source property equals null, then the converter returns true. So, the condition receives a true value, returns true, and mapping will be done. Otherwise the converter returns false, so the condition returns false and mapping will be missed.
Thus, the mapping of source property will occur to different destination properties depending on whether the source property is null value. Besides that, corresponding set methods of corresponding destination properties will be not called if it not must to be called.
Related
How can you map Value object that has different data type in constructor than property type.
public class Information : ValueObject
{
private const string Delimiter = ",";
public Information(string value)
{
Value = SetValueAsReadOnlyList(value);
}
public ReadOnlyCollection<int> Value { get; }
private ReadOnlyCollection<int> SetValueAsReadOnlyList(string value)
{
var collection = value
.Split(Delimiter)
.Select(x =>
{
if(int.Parse(x, out var result))
{
return result;
}
throw new ParseStringToIntDomainException(x);
}).ToList();
return collection.AsReadOnly();
}
}
Mongo map will look like this, which is not working because x.Value is of type string and constructor is expecting ReadOnlyCollection
public class InformationMap : IBsonClassMap
{
public void Map()
{
if (BsonClassMap.IsClassMapRegistered(typeof(Information)))
{
return;
}
BsonClassMap.RegisterClassMap<Information>(map =>
{
map.MapCreator(x => new Information(x.Value));
map.MapProperty(x => x.Value);
});
}
}
I don't think it's possible. I can wrap Value to another nested data type with it's own mapping. But I would need to define transformation inside InformationMap class.
I would like to have a generic way to map properties only if they are changed and not the default value.
So let's say you have these two classes
public class Source
{
public string Foo { get; set; } = "Foo";
public string Bar { get; set; } = "Bar";
}
public class Destination
{
public string Foo { get; set; } = "Foo of Destination";
public string Bar { get; set; } = "Bar of Destination";
}
Now I want to map Source to Destination and if the property of Source.Foo is still "Foo" it should change to "Foo of Destination", but when the value of Source.Foo is different from "Foo" (the default) it should really map the property.
I could not find a way to do this with automapper.
[TestMethod]
public void ChangeAllProperties()
{
var source = new Source();
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, defaultDestination.Bar); // Should be the Default Value of "Destination"
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void ChangeDefaultProperty()
{
var source = new Source();
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void AlLChanged()
{
var source = new Source();
source.Foo = "Custom Foo!";
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, source.Foo); // Should be Value of Source
}
Check out Automapper conditional mapping - link
Basically, you can do something like this (from the samples):
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
opt.Condition() makes sure your condition is met before actually doing any mapping of that particular member.
The opt.Condition() idea from Plamen Yordanov was the right path. I was able to create something more generic, so i dont need to define this for every member and checks the default value of the properties.
This is the solution i came up with. It only checks the default value if the destination member also exists in the source with the same name and type.
public class FooBarProfile: Profile
{
public FooBarProfile()
{
CreateMap<Source, Destination>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
// And if you need reversed...
CreateMap<Destination, Source>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
}
private static bool OnlyIfChanged<TSource>(TSource src, MemberInfo destinationMember) where TSource : new()
{
// Check if DestinationMember exists in TSource (same Name & Type)
var sourceMember = typeof(TSource).GetMembers().FirstOrDefault(e => e.Name == destinationMember.Name && e.MemberType == destinationMember.MemberType);
if (sourceMember != null)
{
var defaultOfSource = new TSource();
// Map only if Value of Source is differnent than the default soruce value
return (GetValue(sourceMember,src)?.Equals(GetValue(sourceMember,defaultOfSource)) == false);
}
return true;
}
// Helper-Method to get Value from a MemberInfo
private static object GetValue(MemberInfo memberInfo, object forObject)
{
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberInfo).GetValue(forObject);
case MemberTypes.Property:
return ((PropertyInfo)memberInfo).GetValue(forObject);
default:
throw new ArgumentException("Member is not Field or Property.", nameof(memberInfo));
}
}
}
I am using AutoMapper and I need to ignore members where an Attribute is not defined. Then, if the Member is not being ignored, I need to map only where values are defined. I have managed to achieve these two separately, but ForAllMembers/ForAllOtherMembers seems to be overriding the first rule.
Let's say I have this class:
public class Foo
{
[MyCustomAttribute]
public string Name { get; set; }
public string IgnoreMe { get; set; }
[MyCustomAttribute]
public int? DontIgnoreNumber { get; set; }
}
I want to ignore IgnoreMe regardless. Then, for Name and DontIgnoreNumber, I want to map them only if they have a value. How can I achieve this?
I have tried this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, Foo>()
.IgnoreAllNonAttributedEntities()
.ForAllOtherMembers(opts =>
{
opts.Condition((src, dest, srcMember) =>
{
// Check if source is a default value
return srcMember != null;
});
});
});
I have checked that the ForAllOtherMembers rule is working. And I, separately, have checked that the IgnoreAllNonAttributedEntities is working. When I try to combine them, the ForAllOtherMembers seems to be taking priority.
IgnoreAllNonAttributedEntities is defined as:
public static IMappingExpression<TSource, TDestination> IgnoreAllNonAttributedEntities<TSource, TDestination>
(this IMappingExpression<TSource, TDestination> expression)
{
var flags = BindingFlags.Public | BindingFlags.Instance;
//var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
foreach(var prop in destinationType.GetProperties(flags))
{
var attr = ReflectionHelpers.GetAttribute<MyCustomAttribute>(prop);
if (attr == null)
{
expression.ForMember(prop.Name, opt => opt.Ignore());
}
}
return expression;
}
I've just run your code and it works as expected. However, maybe what bothers you is the default value of value types in c# (cuz you only check for nulls). Here is my fix for value types:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, Foo>()
.IgnoreAllNonAttributedEntities()
.ForAllOtherMembers(opts =>
{
opts.Condition((src, dest, srcMember) =>
{
var srcType = srcMember?.GetType();
if (srcType is null)
{
return false;
}
return (srcType.IsClass && srcMember != null)
|| (srcType.IsValueType
&& !srcMember.Equals(Activator.CreateInstance(srcType)));
});
});
});
I've recreated your scenerio using latest version of automapper available on NuGet (8.0.0.0).
I'm trying to setup a T4 template that will loop through entity objects and ignore certain navigation properties based on an custom attribute I setup in the class' metdata data.
Here's the setup of the metadata tag
[MetadataType(typeof(ApplicationIntegrationMetadata))]
public partial class ApplicationIntegration
{
}
public class ApplicationIntegrationMetadata
{
[NotToCrud]
public ICollection<Profile> Profiles { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class NotToCrud : System.Attribute
{
}
I've created an extension method based that checks if the class has a specific attribute on the property
public static bool CheckIfPropertyIsAnnotated<T>(this object ctx, string propName)
{
var returnValue = false;
if (!string.IsNullOrWhiteSpace(propName))
{
var thisType = ctx.GetType();
var metadataTypes = thisType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
var metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
var properties = metadata.MetadataClassType.GetProperties();
var found = properties.FirstOrDefault(d => d.Name == propName);
if (found != null)
{
returnValue = Attribute.IsDefined(found, typeof(T));
}
}
}
return returnValue;
}
I've created two separate unit tests to find my issue.
This one works.
[TestCase("Profiles", Result = true, TestName = "Valid property")]
public bool HasAttribute(string propName)
{
var test = new ApplicationIntegration();
var actual = test.CheckIfPropertyIsAnnotated<NotToCrud>(propName);
return actual;
}
This one returns false
[Test]
public void HasAttributeWithAssembly()
{
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (var item in myTypes)
{
var actual = item.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
Console.WriteLine($"{item.Name} - {actual.ToString()}");
Assert.AreEqual(true, actual);
}
}
the problem seems to be with (var thisType = ctx.GetType())
On the second test, it returns
object.GetType returned
{Name = "RuntimeType" FullName = "System.RuntimeType"} System.RuntimeType
instead of
{Name = "ApplicationIntegration" FullName = "Apm.Model.ApplicationIntegration"}
Any clue how to get around this?
Because item is a Type (with value typeof(ApplicationIntegration)), not an ApplicationIntegration. You can create an object with Activator.CreateInstance. Like:
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (Type type in myTypes)
{
object obj = Activator.CreateInstance(type);
var actual = obj.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
I started playing with roslyn few days ago and i am trying to write an extension method which tells if an IPropertySymbol has a backing field, so i thought a property has a backing field if and only if the following does not apply(as far as i am concerned):
IF its Abstract
IF its Extern
IF its ReadOnlyProperty
IF the Getter or the Setter has no Body or Empty body
so i came up with
public static bool HasBackingField(this IPropertySymbol property)
{
return !(property.IsAbstract || property.IsExtern || property.IsReadOnly);
}
My questions are
Did i miss any condition?
How do i check for the last condition? i found GetMethod and SetMethodproperties in IPropertySymbol but i don't know to check if they have a body
example to start up with
var code =
#"class XYZ
{
public int x => 4; //HasBacking field : false IsReadOnly
public int m { get { return 0;}} //HasBacking field : false IsReadOnly
public int y { get; set; } //HasBacking field : false Null body for setter or getter
public int z { get { return 0; } set { } } //HasBacking field : false Empty body for setter or getter
private int _g;
public int g //HasBacking field : true Getter and Setter has no empty Bodies
{
get { return _g; }
set { _g = value; }
}
}";
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("xxx").AddSyntaxTrees(syntaxTree);
var classSymbol = compilation.GetTypeByMetadataName("XYZ");
var propSymbols = classSymbol.GetMembers().OfType<IPropertySymbol>();
var results = propSymbols.Select(ps => ps.HasBackingField()); //should be [false false false false true]
I decided to look at the syntax representation rather than the actual symbol -- the syntax is at a lower level than the symbol and contains the raw information we're interested in: looking at individual statements.
This seems to do what you're interested in:
internal static bool HasBackingField(this PropertyDeclarationSyntax property)
{
var getter = property.AccessorList?.Accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.GetAccessorDeclaration));
var setter = property.AccessorList?.Accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.SetAccessorDeclaration));
if (setter?.Body == null || getter?.Body == null)
{
return false;
}
bool setterHasBodyStatements = setter.Body.Statements.Any();
bool getterHasBodyStatements = getter.Body.Statements.Any();
return setterHasBodyStatements && getterHasBodyStatements;
}
Note that I'm not convinced this is reliable enough to conclude that there is a backing field available, but it follows the idea you had by checking if there is a body or not.
I haven't added the other checks you had in mind but these can trivially be added (either use the symbol as you already do or look through the PropertyDeclarationSyntax its modifiers/attributes).
---
Full code to test it out yourself:
public static void Execute()
{
var code =
#"class XYZ
{
public int x => 4; //HasBacking field : false IsReadOnly
public int m { get { return 0;}} //HasBacking field : false IsReadOnly
public int y { get; set; } //HasBacking field : false Null body for setter or getter
public int z { get { return 0; } set { } } //HasBacking field : false Empty body for setter or getter
private int _g;
public int g //HasBacking field : true Getter and Setter has no empty Bodies
{
get { return _g; }
set { _g = value; }
}
}";
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();
foreach (var prop in root.DescendantNodes().OfType<PropertyDeclarationSyntax>())
{
Console.WriteLine(prop.HasBackingField());
}
}
}
internal static class Extensions
{
internal static bool HasBackingField(this PropertyDeclarationSyntax property)
{
var getter = property.AccessorList?.Accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.GetAccessorDeclaration));
var setter = property.AccessorList?.Accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.SetAccessorDeclaration));
if (setter?.Body == null || getter?.Body == null)
{
return false;
}
bool setterHasBodyStatements = setter.Body.Statements.Any();
bool getterHasBodyStatements = getter.Body.Statements.Any();
return setterHasBodyStatements && getterHasBodyStatements;
}
}