I have a model, FittingProject, which I'm trying to map to another model, FittingsProjectSharepointModel.
Unfortunately FittingProject and FittingsProjectSharepointModel only share values, both property names and types are different.
To ease the mapping process I have created a custom attribute for FittingProject which I use to look up the matching property on FittingsProjectSharepointModel. The problem is that most values fail during conversion, for example going from int to double?.
As you can see by snippet below I have attempt to use the Convert.ChangeType, but it still fails with the same Exception.
public ModelMapper MapModelToFittingsProjectSharepointModel(object model)
{
if (model == null)
{
return null;
}
var propertiesWithCustomAttributes = model
.GetType()
.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(SharepointModelPropertyAttribute), true).Length > 0);
foreach (var prop in propertiesWithCustomAttributes)
{
foreach (var customAttribute in prop.GetCustomAttributes(true))
{
SharepointModelPropertyAttribute sharePointModelPropertyAttribute =
customAttribute as SharepointModelPropertyAttribute;
PropertyDescriptor sharePointModelprop = TypeDescriptor
.GetProperties(typeof(FittingsProjectSharepointModel))
.Find(sharePointModelPropertyAttribute.SharepointModelProperty, false);
try
{
var projectValue = prop.GetValue(model);
var projectValueConverted = Convert.ChangeType(projectValue, sharePointModelprop.PropertyType);
sharePointModelprop.SetValue(FittingsProjectSharepointModel, projectValueConverted);
}
catch (Exception ex)
{
//
}
}
}
return this;
}
ChangeType does not support that:
Convert.ChangeType(1234, typeof(double)) //works
Convert.ChangeType(1234, typeof(double?)) //fails
So you probably need to do your own nullable processing. Simply strip away the nullable property from the destination type using Nullable.GetUnderlyingType. You also need to handle the case, that the value to be converted is null.
You can use AutoMapper. Like this:
Consider the following example models:
public class FittingProject
{
public string Name { get; set; }
public int Size { get; set; }
}
public class FittingsProjectSharepointModel
{
public string Display { get; set; }
public double? Volume { get; set; }
}
Now, you can create a map between the models like this:
AutoMapper.Mapper.CreateMap<FittingProject, FittingsProjectSharepointModel>()
.ForMember(dest => dest.Display, opts => opts.MapFrom(src => src.Name)) //Map Name to Display
.ForMember(dest => dest.Volume, opts => opts.MapFrom(src => src.Size)); //Map Size to Volume
And here is how you can convert:
FittingProject fitting_project = new FittingProject()
{
Name ="project_name",
Size = 16
};
FittingsProjectSharepointModel sharepoint_model =
AutoMapper.Mapper.Map<FittingsProjectSharepointModel>(fitting_project);
Related
I'm using automapper via DI and want to have generic rules for all the mappings found in my solution. Belows example is of a single class, however I've got hundreds of classes to maintain and therefore want to add it to the mapper, not the mapping profile.
So far, I can handle Null values as follows :
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
Which in my example below, allows for the null update to persist past all the subsiquent updates. What I want is a generic rule ( or statement) for the default value of Guids, DateTimeOffset and Int. In my example below, update 1 and 2 are lost once update 3 is mapped.
using System;
using Autofac;
using AutoMapper;
namespace AutoMapperProblem
{
public class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterInstance(new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(Program).Assembly);
cfg.ForAllMaps((obj, cnfg) =>
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}).CreateMapper()).As<IMapper>().SingleInstance();
var container = builder.Build();
var mapper = container.Resolve<IMapper>();
var MainObject = new MainObject();
//this update persists pass all updates
var updateNull = new UpdateObject { NullGuid = Guid.NewGuid() };
mapper.Map(updateNull, MainObject);
var update1 = new UpdateObject { DateTimeOffset = DateTimeOffset.Now };
mapper.Map(update1, MainObject);
var update2 = new UpdateObject { Guid = Guid.NewGuid() };
mapper.Map(update2, MainObject);
var update3 = new UpdateObject { Int = 10 };
mapper.Map(update3, MainObject);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MainObject, UpdateObject>()
.ReverseMap();
}
}
public class MainObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
public class UpdateObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
}
I want it to work based on the type, eg ints, DateTimeOffsets and Guid's .
Heres a working example on DotNetFiddle : https://dotnetfiddle.net/Zh0ta6
Thanks for any help with this
The problem is that you cannot check value types against null in your opts.Condition() check, as they always have a value. Also, you can't use the default keyword directly in this specific context because the variable type is object. That will turn default to null again, and fail.
Depending on what you want to do, you can check if the destination already have a non-default value and do NOT replace it with any source value, which might be the default value of the type (0 for int, 0000...-000 for Guid, etc.). The check could look like this:
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember, destMember) =>
{
object destDefaultValue = null;
if (destMember != null)
{
Type destType = destMember.GetType();
if (destType.IsValueType)
{
destDefaultValue = Activator.CreateInstance(destType);
}
else
{
destDefaultValue = null;
}
}
bool destinationHasNoValue = Object.Equals(destMember, destDefaultValue);
return destinationHasNoValue;
})
The first part is calculating the default value for the given destination value, based on the type of the destination value. The latter part is checking if the destination already have a non-default value or not. Consecutive calls of Map() with the same target object will fill the "missing" property values until all the property values are set (in this example). If necessary, check the srcMember value and it's default type as well to fine-tune when the value should be copied or not.
I need help with building filter for MongoCollection of class A when I have filters for class B
public class A
{
public string ExampleAProperty { get; set; }
public B NestedB { get; set; }
public ICollection<B> NestedBCollection { get; set; }
}
public class B
{
public string ExampleBProperty { get; set; }
}
public class SearchClass
{
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection)
{
// this is just simple example of possible dozens predefined filters for B class.
// see FilterProvider logic used now
// var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, "Example");
// in need filter A where B meets provided filters
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
// predefined filters are easy to reuse with array of elements
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, FilterProvider.SearchValue("Oleh")),
// but i did not found how to do this with single nested element
Builders<A>.Filter.Eq(x => x.NestedB, FilterProvider.SearchValue("Oleh")) // how?
)
);
return await cursor.ToListAsync();
}
}
// statics is not good but just for working example :)
public static class FilterProvider
{
public static FilterDefinition<B> SearchValue(string? value)
{
var builder = Builders<B>.Filter;
// if value is null show all
if (string.IsNullOrEmpty(value))
{
return builder.Empty;
}
// if value is "Oleh" search for "Amir"
if (value == "Oleh")
{
value = "Amir";
}
// any other additional logic to compose proper filter
// this could be search by serveral properties and so on
// however just for example i will search by hashed value :)
value = value.GetHashCode().ToString();
return builder.Eq(x => x.ExampleBProperty, value);
}
}
Please DON'T
use IMongoQueryable
propose to filter nested element by duplicate code (like x => x.NestedB.ExampleBProperty = "something")
UPDATED: example for Amir with explanation why p.2 is not than case and why code will be is duplicated if you his approach :)
As you may see we have complex (but very simple in current example) filter of data in B class. If you will use your approach - logic of composing filter for B (specified in FilterProvider.SearchValue) will be duplicated.
Thank you
You can easily do this:
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection, string search)
{
var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, search);
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, bFilter),
Builders<A>.Filter.Eq(x => x.NestedB.ExampleBProperty, search)
)
);
return await cursor.ToListAsync();
}
I am trying to create a simple class. ColumnSort member is a list of items in comma delimited text "Car,Book,Food".
ColumnSortList creates a List
Car
Book
Food
C# and SonarQube is mentioning items like Error
Get: Add a way to break out of this property accessor's recursion.
Set: Use the 'value' parameter in this property set accessor declaration
How would I resolve these to make warnings/ errors (in SonarQube) go away? Open to making code more efficient also.
Note: columnSortList is purely supposed to be a read only computed field from ColumnSort string.
public class PageModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string ColumnSort { get; set; }
public IEnumerable<string> columnSortList
{
get
{
return columnSortList;
}
set
{
if (ColumnSort == null)
{
columnSortList = null;
}
else
{
columnSortList = ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
If columnSortList is intended to be purely read-only, computed from ColumnSort, then you should not have a set method at all. All the logic should go inside get like this:
public IEnumerable<string> columnSortList
{
get
{
if (ColumnSort == null)
{
return Enumerable.Empty<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
Your getter is returning itself, which you can't do, and your setter is setting itself, which you also can't do. This here seems to be what you want:
public IEnumerable<string> columnSortList
{
get
{
if (ColumSort == null)
{
return new List<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
I need to map an object to another one using AutoMapper. The tricky question is how can I access an instance of the mapper (instance of IMapper) inside of the mapping configuration or inside of a custom type converter?
The code below does not work, however it is an example of what I would like to achieve - please notice the mapper.Map calls and assume that mappings Customer => CustomerDto and Customer => DetailedCustomerDto are defined.
var config = new MapperConfiguration(
cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing(o => {
return o.Type == 1
? mapper.Map<Customer, CustomerDto>(o.Customer)
: mapper.Map<Customer, DetailedCustomerDto>(o.Customer)
})
);
The client part is:
var mapper = config.CreateMapper();
var orderDto = mapper.Map<Order, OrderDto>(order);
The simplified version of objects I want to map is:
public class Order
{
public int Type { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public long Id { get; set; }
public string Name { get; set; }
}
public class OrderDto
{
public CustomerDto Customer { get; set; }
}
public class CustomerDto
{
public long Id { get; set; }
}
public class DetailedCustomerDto : CustomerDto
{
public string Name { get; set; }
}
As you see from the code above, based on the value of Order.Type, the mapper should map the property Order.Customer to different targets. As one target (DetailedCustomerDto) inherits from the other one (CustomerDto) it becomes a bit tricky.
Please notice that usage of the obsolete and deprecated static method Mapper.Map is NOT an option.
As of AutoMapper 8.0 and up
The answer below for 5.1.1 still applies, but note that the use of ResolveUsing has been replaced with an overload of MapFrom, but the signature has otherwise remained consistent.
As of AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with four parameters, fourth of which is ResolutionContext (context.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((order, orderDto, i, context) => {
return order.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(order.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
Prior to AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with two parameters, first of which is ResolutionResult (result.Context.Engine.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((result, order) => {
return order.Type == 1
? result.Context.Engine.Mapper.Map<Customer, CustomerDto>(order.Customer)
: result.Context.Engine.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
In addition to Evk's great answer, which helped me, if you need to do a mapping inside a mapping inside a config/profile that requires a custom constructor (i.e. the type has no default constructor), the following will work in v5.2.0:
CreateMap<Models.Job, Models.API.Job>(MemberList.Source);
CreateMap<StaticPagedList<Models.Job>, StaticPagedList<Models.API.Job>>()
.ConstructUsing((source, context) => new StaticPagedList<Models.API.Job>(
context.Mapper.Map<List<Models.Job>, List<Models.API.Job>>(source.ToList()),
source.PageNumber,
source.PageSize,
source.TotalItemCount));
In this example I'm mapping the X.PagedList custom collection type of one object type onto an equivalent collection of another object type. The first parameter to the lamdba expression is your source object, the second is your ResolutionContext from which you can access a mapper instance to map from.
I'm using Automapper 9 and the answers above didn't work for me.
Then for resolve my problem that is like yours I use .afterMap, like that:
public class AutoMapperOrder : Profile
{
public AutoMapperOrder()
{
CreateMap<Customer, CustomerDto>()
//...
CreateMap<Customer, DetailedCustomerDto>()
//...
CreateMap<Order, OrderDto>()
.AfterMap((src, dest, context) => {
dest.Customer = src.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(src.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(src.Customer)
}
}
}
}
I hope to help somebody.
I have a base class called BaseStatus which looks like this:
public class BaseStatus
{
public int UnitId { get; protected set; }
public UInt16 StatusValue { get; protected set; }
public string StatusCode { get; protected set; }
public string StatusDescription { get; protected set; }
public BaseStatus()
{
this.UnitId = -1;
this.StatusValue = 0;
this.StatusCode = null;
this.StatusDescription = null;
}
}
Furthermore i have two or more other base classes which derive from BaseStatus and define a other unit id. For example the two classes
public class BaseGlobalStatus : BaseStatus
{
public BaseGlobalStatus()
{
base.UnitId = -1;
}
}
public class BaseGcmGdmStatus : BaseStatus
{
public BaseGcmGdmStatus()
{
base.UnitId = 2;
}
}
public class BaseCcuStatus : BaseStatus
{
public BaseCcuStatus()
{
base.UnitId = 1;
}
}
The Background is that i want to derive from for example BaseCcuStatus and have the correct UnitId in the derived class.
Now i define my correct status classes for example:
public class StatStErrDefinition : BaseGlobalStatus
{
public StatStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
public class GcmStErrDefinition : BaseGcmGdmStatus
{
public GcmStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
public class CcuStErrDefinition : BaseCcuStatus
{
public CcuStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
For my understading, the three classes StatStErrDefinition, GcmStErrDefinition and CcuStErrDefinition should have the UnitId which is set in the derived BaseClass?
Now that i have defined my three Status Classes i want to get them into a registry. Currently im using this piece of code to try get them. Problem is that the result has no items.
registry = new StatusDictionary<UInt16, BaseStatus>();
var unitStatus = typeof(BaseStatus)
.Assembly.GetTypes()
.Where(x => x.BaseType == typeof(BaseStatus))
.Select(x => new
{
StatusType = x,
UnitId = x.GetProperty("UnitId", BindingFlags.Public)
StatVal = x.GetProperty("StatusValue", BindingFlags.Public)
}
)
.Where(x => x.StatVal != null && x.UnitId != null)
.Select(x => new
{
UnitId = (int)x.UnitId.GetValue(null, null),
StatusValue = (UInt16)x.StatVal.GetValue(null, null),
Factory = (Func<BaseStatus>)(() => ((BaseStatus)Activator.CreateInstance(x.StatusType)))
});
try
{
foreach (var status in unitStatus)
{
if (status.UnitId == unitId
|| status.UnitId < 0)
registry.Register(status.StatusValue, status.Factory);
}
}
catch (Exception ex)
{
string temp = ex.Message;
}
After the LINQ expression the var unitStatus is empty...
Later, the registry call looks like that to get the specific class but that is unimportant at this point:
stat = StatusContainer.GetRegistry(this.unitTypeId).GetInstance(this.StatusValue);
For information:
I want to get the status class which should be in the registry by the unittypeid and the specific status value.
Currently my registry method does not work because he is not able to find any class. So there has to be a mistake somewhere. Thanks in advance
#Update 1
I changed my functionality a little bit:
registry = new StatusDictionary<UInt16, BaseStatus>();
//get all types of cucrent assembly
var allAssemblyTypes = Assembly.GetCallingAssembly().GetTypes();
//get all types from base status
var baseStatusTypes = allAssemblyTypes.Where(x => x.BaseType == typeof(BaseStatus));
//Place all concrete types in the foundtypes
List<Type> foundTypes = new List<Type>();
foreach (Type item in baseStatusTypes)
{
var temp = allAssemblyTypes.Where(x => x.BaseType == item)
.Select(x => new
{
StatusType = x,
UnitId = x.GetProperty("UnitId", BindingFlags.Public),
StatVal = x.GetProperty("StatusValue", BindingFlags.Public),
}
);
}
Temp contains now the correct type.
Problem is that if temp is type of StatStErrDefinition the StatusValue and UnitId Property is null.
The fact is that these members are instance members. Is there a way to get the values out of them?
First thing first : your LINQ query is pretty long.
divide it in different step and store them in different variables (or make properties out of them, whatever you prefer)
This is
easy to read / maintain
easy to debug
With this given I think you are able to solve your problem :)
To check if the class is of a certain type you could use the method .OfType
Use this method to get the value. Notice that you must make an instance in your case because the value change in your constructor.
public static object GetPropValue(Type src, string propName)
{
var prop = src.GetProperty(propName);
var instance = Activator.CreateInstance(src);
var value = prop.GetValue(instance);
return value;
}
Instead of
UnitId = x.GetProperty("UnitId", BindingFlags.Public),
use
UnitId = GetPropValue(x,"UnitId"),