Automapper: Mapping using attributes for non-matching properties - automapper

I have a scenario in which I'm creating a mapping utility through which we can map DataReader to entities.
Mapper.CreateMap<IDataReader, T>();
List<T> tList = Mapper.Map<List<T>>(reader);
return tList;
This is working fine if the entity has same columns as in the stored procedure's output. However, I'm working on an some old code where column names are not matching. So I created an attribute through which I could specify corresponding column from sprocs. I specify them using code like this
[DBColumn('EmployeeName')]
public string EmpName { get; set; }
Is there any way I can achieve this using generics/reflection? Note that I have multiple such instances and I'm building an utility. So I can not use ForMember method to achieve that.
UPDATE: I tried creating a custom converter like this. However, my convert function is not getting called at all.
Custom converter
public class CustomConverter<T> : ITypeConverter<System.Data.IDataReader, T>
{
private ResolutionContext _Context = null;
private Dictionary<string, string> _CustomProps { get; set; }
public T Convert(ResolutionContext context)
{
_Context = context;
if (_Context.SourceValue != null &&
!(_Context.SourceValue is System.Data.IDataReader))
{
string message = "Value supplied is of type {0} but expected {1}.\n" +
"Change the type converter source type, or redirect " +
"the source value supplied to the value resolver using FromMember.";
throw new AutoMapperMappingException(_Context, string.Format(
message, typeof(System.Data.IDataReader), _Context.SourceValue.GetType()));
}
_CustomProps = new Dictionary<string, string>();
foreach (PropertyInfo propInfo in context.DestinationType.GetProperties())
{
if (propInfo.CustomAttributes.Any(attr => attr.AttributeType == typeof(DBColumn)))
{
DBColumn propertyValue = (DBColumn)propInfo.GetCustomAttribute(typeof(DBColumn));
_CustomProps.Add(propertyValue.ColumnName, propInfo.Name);
}
}
//return base.Convert(context);
return ConvertCore((System.Data.IDataReader)context.SourceValue);
}
protected T ExistingDestination
{
get
{
if (_Context == null)
{
string message = "ResolutionContext is not yet set. " +
"Only call this property inside the 'ConvertCore' method.";
throw new InvalidOperationException(message);
}
if (_Context.DestinationValue != null &&
!(_Context.DestinationValue is T))
{
string message = "Destination Value is of type {0} but expected {1}.";
throw new AutoMapperMappingException(_Context, string.Format(
message, typeof(T), _Context.DestinationValue.GetType()));
}
return (T)_Context.DestinationValue;
}
}
protected T ConvertCore(System.Data.IDataReader source)
{
T obj = ExistingDestination;
if (obj != null)
{
foreach (KeyValuePair<string, string> keyValuePair in _CustomProps)
{
PropertyInfo prop = obj.GetType().GetProperty(keyValuePair.Key, BindingFlags.Public | BindingFlags.Instance);
if (null != prop && prop.CanWrite)
{
prop.SetValue(obj, System.Convert.ChangeType(source[keyValuePair.Value], prop.PropertyType));
}
}
}
return obj;
}
I'm specifying to use this CustomConverter in below statement.
Mapper.CreateMap<IDataReader, T>().ConvertUsing<CustomConverter<T>>();
List<T> tList = Mapper.Map<List<T>>(reader);
return tList;
Please let me know, where I'm going wrong

Related

How to make AutoMapper throw when mapping null into non-nullable value type?

I have two classes:
public class ClassA
{
public ClassA(int? value)
{
Value = value;
}
public int? Value { get; }
}
public class ClassB
{
public ClassB(int value)
{
Value = value;
}
public int Value { get; }
}
When mapping an instance of ClassA with Value == null into ClassB, AutoMapper seems to ignore the property and provide the default value instead:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClassA, ClassB>();
});
var mapper = new Mapper(config);
var classA = new ClassA(null);
var classB = mapper.Map<ClassB>(classA);
Debug.Assert(classB.Value == 0);
How do I make AutoMapper throw an exception when mapping an instance of ClassA with Value == null into ClassB instead? Optimally, can I enable this behavior for all mappings from nullable value types to non-nullable value types?
I can add a custom BeforeMap function that tries to identify these kind of situations via reflection, but I'd prefer a simpler solution and/or one that also handled custom mappings of differently named properties, etc.
cfg.ForAllMaps((_, e) => e.BeforeMap(CheckForDisallowedNulls));
void CheckForDisallowedNulls(object source, object destination)
{
foreach (var sourceProperty in source.GetType().GetProperties())
{
var underlyingType = Nullable.GetUnderlyingType(sourceProperty.PropertyType);
if (underlyingType is { })
{
var destinationProperty = destination.GetType().GetProperty(sourceProperty.Name);
if (destinationProperty is { } && destinationProperty.PropertyType == underlyingType && sourceProperty.GetValue(source) == null)
{
throw new ArgumentNullException(sourceProperty.Name);
}
}
}
}
I can create a map from int? to int that throws an exception when the source value is null:
cfg.CreateMap<int?, int>().ConvertUsing((s, _) => s.Value);
However, this requires adding such a map for each nullable value type I want the behavior for.

How to Skip Empty Collections in Yamldotnet

I'm trying to figure out how to skip serializing empty collections using YamlDotNet. I have experimented with both a custom ChainedObjectGraphVisitor and IYamlTypeConverter. I'm new to using YamlDotNet and have some knowledge gaps here.
Below is my implementation for the visitor pattern, which results in a YamlDotNet.Core.YamlException "Expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, got MappingEnd" error. I do see some online content for MappingStart/MappingEnd, but I'm not sure how it fits into what I'm trying to do (eliminate clutter from lots of empty collections). Any pointers in the right direction are appreciated.
Instantiating the serializer:
var serializer = new YamlDotNet.Serialization.SerializerBuilder()
.WithNamingConvention(new YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention())
.WithEmissionPhaseObjectGraphVisitor(args => new YamlIEnumerableSkipEmptyObjectGraphVisitor(args.InnerVisitor))
.Build();
ChainedObjectGraphVisitor implementation:
public sealed class YamlIEnumerableSkipEmptyObjectGraphVisitor : ChainedObjectGraphVisitor
{
public YamlIEnumerableSkipEmptyObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
: base(nextVisitor)
{
}
public override bool Enter(IObjectDescriptor value, IEmitter context)
{
bool retVal;
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(value.Value.GetType()))
{ // We have a collection
var enumerableObject = (System.Collections.IEnumerable)value.Value;
if (enumerableObject.GetEnumerator().MoveNext()) // Returns true if the collection is not empty.
{ // Serialize it as normal.
retVal = base.Enter(value, context);
}
else
{ // Skip this item.
retVal = false;
}
}
else
{ // Not a collection, normal serialization.
retVal = base.Enter(value, context);
}
return retVal;
}
}
I believe the answer is to also override the EnterMapping() method in the base class with logic that is similar to what was done in the Enter() method:
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
{
bool retVal = false;
if (value.Value == null)
return retVal;
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(value.Value.GetType()))
{ // We have a collection
var enumerableObject = (System.Collections.IEnumerable)value.Value;
if (enumerableObject.GetEnumerator().MoveNext()) // Returns true if the collection is not empty.
{ // Don't skip this item - serialize it as normal.
retVal = base.EnterMapping(key, value, context);
}
// Else we have an empty collection and the initialized return value of false is correct.
}
else
{ // Not a collection, normal serialization.
retVal = base.EnterMapping(key, value, context);
}
return retVal;
}
I ended up with the following class:
using System.Collections;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.ObjectGraphVisitors;
sealed class YamlIEnumerableSkipEmptyObjectGraphVisitor : ChainedObjectGraphVisitor
{
public YamlIEnumerableSkipEmptyObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor): base(nextVisitor)
{
}
private bool IsEmptyCollection(IObjectDescriptor value)
{
if (value.Value == null)
return true;
if (typeof(IEnumerable).IsAssignableFrom(value.Value.GetType()))
return !((IEnumerable)value.Value).GetEnumerator().MoveNext();
return false;
}
public override bool Enter(IObjectDescriptor value, IEmitter context)
{
if (IsEmptyCollection(value))
return false;
return base.Enter(value, context);
}
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
{
if (IsEmptyCollection(value))
return false;
return base.EnterMapping(key, value, context);
}
}
you can specify DefaultValuesHandling
in the serializer:
var serializer = new SerializerBuilder()
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitEmptyCollections)
.Build();
or in an attribute YamlMember for a field/property:
public class MyDtoClass
{
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public List<string> MyCollection;
}

Enum value in tuple using ServiceStack Ormlite throws null reference exception

I get "Object reference not set to an instance of an object" when using the Tuple notation with an Enum in the return structure.
If I change the type in the tuple from Enum type to string it works as it should, also if I change it to return only one value (string or enum) it works as it should.
It it a bug in ServiceStack?
I am running ServiceStack.OrmLite.SqlServer v5.4.0 in LinqPad v5.31.0
void Main()
{
var uniqueId = "a635266024448923446";
var result = new Dictionary<Language, string>();
using (var db = _connectionFactory.OpenDbConnection())
{
// This works fine
var rows1 = db.Select<A>("select LanguageId, Name from tblTable");
foreach (var row in rows1)
{
result.Add(row.LanguageId, row.Name);
}
}
using (var db = _connectionFactory.OpenDbConnection())
{
// This throws "Object reference not set to an instance of an object."
var rows2 = db.Select<(Language Language, string Name)>("select LanguageId, Name from tblTable");
foreach (var row in rows2)
{
result.Add(row.Language, row.Name);
}
}
}
public class A
{
public Language LanguageId { get; set; }
public string Name { get; set; }
}
public enum Language
{
NO,
EN,
SV,
DK
}
This change should be resolved from this commit.
This change is available from v5.4.1 that's now available on MyGet.

Automapper error: Expressions mapping from methods not supported yet

Any idea what might cause the error "Expressions mapping from methods not supported yet." when trying to map two objects? I cannot find any reference to this error anywhere.
EDITED---
I have more information. I have a property in my DTO declared as:
public LookupItem RegionType { get; set; }
However, when I invoke the mapping, it generates the error, "Expressions mapping from methods not supported yet.".
However, if I change the string in the property name "Type" to anything else like "Typeo" or "ASDF", the mapping succeeds. In other words, if change the property name to "RegionTypeo". Am I breaking any convention rules here? There seems to be something wrong with including the string "Type" in my property name.
Below is the generated error:
Result Message:
Test method Rep.Tests.PlanServiceTest.GetBuildings threw exception:
System.NotImplementedException: Expressions mapping from methods not supported yet.
Result StackTrace:
at AutoMapper.PropertyMap.ResolveExpression(Type currentType, Expression instanceParameter)
at AutoMapper.QueryableExtensions.Extensions.CreateMemberBindings(IMappingEngine mappingEngine, Type typeIn, TypeMap typeMap, Expression instanceParameter)
at AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(IMappingEngine mappingEngine, Type typeIn, Type typeOut, Expression instanceParameter)
at AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(IMappingEngine mappingEngine, Type typeIn, Type typeOut)
at AutoMapper.QueryableExtensions.Extensions.<>c__DisplayClass12.<CreateMapExpression>b__0(TypePair tp)
at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory)
at AutoMapper.Internal.DictionaryFactoryOverride.ConcurrentDictionaryImpl2.GetOrAdd(TKey key, Func2 valueFactory)
at AutoMapper.QueryableExtensions.Extensions.CreateMapExpression[TSource,TDestination](IMappingEngine mappingEngine)
at AutoMapper.QueryableExtensions.ProjectionExpression1.ToTResult
at Rep.Services.PlanService.GetBuildings() in c:\Dev\REP\Rep\Services\PlanService.cs:line 369
at Rep.Tests.PlanServiceTest.GetBuildings() in c:\Dev\REP\Rep.Tests\PlanServiceTest.cs:line 50
Based on the source code, you can see that the exception is thrown when you try mapping functions on your objects:
public ExpressionResolutionResult ResolveExpression(Type currentType, Expression instanceParameter)
{
Expression currentChild = instanceParameter;
Type currentChildType = currentType;
foreach (var resolver in GetSourceValueResolvers())
{
var getter = resolver as IMemberGetter;
if (getter != null)
{
var memberInfo = getter.MemberInfo;
var propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo != null)
{
currentChild = Expression.Property(currentChild, propertyInfo);
currentChildType = propertyInfo.PropertyType;
}
else
{
throw new NotImplementedException("Expressions mapping from methods not supported yet.");
}
}
else
{
var oldParameter = CustomExpression.Parameters.Single();
var newParameter = instanceParameter;
var converter = new ConversionVisitor(newParameter, oldParameter);
currentChild = converter.Visit(CustomExpression.Body);
currentChildType = currentChild.Type;
}
}
return new ExpressionResolutionResult(currentChild, currentChildType);
}
Based on OP clarification, I cannot reproduce the problem with the following:
public class Class1
{
public string StringType { get; set; }
public Func<Class1> FuncType { get; set; }
public Class1 Class1Type { get; set; }
}
public class Class2
{
public string StringType { get; set; }
public Func<Class1> FuncType { get; set; }
public Class1 Class1Type { get; set; }
}
/* ... */
AutoMapper.Mapper.CreateMap<Class1, Class2>();
var c1 = new Class1() { Class1Type = new Class1(), FuncType = () => new Class1(), StringType = "Class1" };
var c2 = AutoMapper.Mapper.Map<Class1, Class2>(new Class1());

Accessing XPages Data Source in a Plugin

For a plugin (like the extension library) I try to access the datasource with a given "var" name. Accessing the Datasource object is very easy with the following code:
m_DataSourceName contains the name (var) of the datasource.
public DataSource getDataSource() {
if (StringUtil.isNotEmpty(m_DataSourceName)) {
UIViewRoot vrCurrent = getFacesContext().getViewRoot();
if (vrCurrent instanceof UIViewRootEx) {
for (DataSource dsCurrent : ((UIViewRootEx) vrCurrent)
.getData()) {
if (m_DataSourceName.equals(dsCurrent.getVar())) {
return dsCurrent;
}
}
}
}
System.out.println("Datasource name:" + m_DataSourceName);
return null;
}
I'm getting the datasource back and I can cast this datasource:
private TabularDataModel getTDM(DataSource dsCurrent, FacesContext context) {
try {
if (dsCurrent instanceof ModelDataSource) {
ModelDataSource mds = (ModelDataSource) dsCurrent;
AbstractDataSource ads = (AbstractDataSource) mds;
ads.load(context);
System.out.println(ads.getBeanId());
if (ads.getBeanId() == null) {
}
DataModel tdm = mds.getDataModel();
if (tdm instanceof TabularDataModel) {
TabularDataModel tds = (TabularDataModel) tdm;
return tds;
}
}
return null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And now I wanna access the TDM.getRowCount() and this point I'm getting a nullpointer exception. The datasource contains a notes view. Did I miss anything to initialize the datasource?
Here is a solution for your problem:
This will give you all lines of a view, not the entry count*. F.e. if you have a categorized view with 5 categories and 1 entry for each category, this will result in 10 lines. The entry count is 5.
First, create a dummy class which implements FacesDataIterator
public class DummyDataIterator implements com.ibm.xsp.component.FacesDataIterator{
public DataModel getDataModel() {
return null;
}
public int getFirst() {
return 0;
}
public int getRowIndex() {
return 0;
}
public int getRows() {
return 0;
}
public void setFirst(int paramInt) {}
public void setRows(int paramInt) {}
}
And then you have to do the following:
Set the data iterator
tdm.setDataControl( new DummyDataIterator() );
Init the row counter for the first time
tdm.getRowCount();
Calculate the exact row count with a navigator
(( com.ibm.xsp.model.domino.viewnavigator.NOIViewNavigatorEx) tdm.getDominoViewDataContainer().getNavigator()).calculateExactCount(tdm.getView());
Now your row count is initialized, you can get the result with a normal getRowCount:
System.out.println("Rows: " + tdm.getRowCount() );
Hope this helps!
*:
tdm.getView().getAllEntries().getCount()

Resources