Automapper mapping IList<> to Iesi.Collections.Generic.ISet<> - automapper

I am having some issues in the mapping mentioned in the title. Here are the details:
class MyDomain
{
public Iesi.Collections.Generic.ISet<SomeType> MySomeTypes{ get; set; }
....
}
class MyDTO
{
public IList<SomeTypeDTO> MySomeTypes{ get; set; }
...
}
The mapping:
Mapper.CreateMap<MyDomain, MyDTO>().ForMember(dto=>dto.MySomeTypes, opt.ResolveUsing<DomaintoDTOMySomeTypesResolver>());
Mapper.CreateMap<MyDTO, MyDomain>().ForMember(domain=>domain.MySomeTypes, opt.ResolveUsing<DTOtoDomainMySomeTypesResolver>());
The Resolvers:
class DomaintoDTOMySomeTypesResolver: ValueResolver<MyDomain, IList<SomeTypeDTO>>
{
protected override IList<SomeTypeDTO> ResolveCore(MyDomain source)
{
IList<SomeTypeDTO> abc = new List<DemandClassConfigurationDTO>();
//Do custom mapping
return abc;
}
}
class DTOtoDomainMySomeTypesResolver: ValueResolver<MyDTO, Iesi.Collections.Generic.ISet<SomeType>>
{
protected override Iesi.Collections.Generic.ISet<SomeType> ResolveCore(SystemParameterDTO source)
{
Iesi.Collections.Generic.ISet<SomeType> abc = new HashedSet<SomeType>();
//Do custom mapping
return abc;
}
}
Mapping from Domain to DTO works ok and as expected I get a MyDTO object with IList of "SomeTypeDTO" objects.
However mapping of the DTO to Domain throws the following error:
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
----> AutoMapper.AutoMapperMappingException : Trying to map Iesi.Collections.Generic.HashedSet`1[SomeType, MyAssembly...] to Iesi.Collections.Generic.ISet`1[SomeType, MyAssembly...]
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
----> System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.List`1[SomeType]' to type 'Iesi.Collections.Generic.ISet`1[SomeType]
What might I be doing wrong and what do the error messages imply? It almost seems that automapper is having some issues in mapping the ISet ( together with its concrete implementation HashedSet). My understanding is that in the above described scenario automapper should just use the ISet reference returned by "DTOtoDomainMySomeTypesResolver". I also don't see why I am getting the "cast from List to ISet error".

This is because AutoMapper currently doesn't support ISet<> collection properties. It works when the destination property of ISet<> is already instantiated (is not null), because the ISet<> actually inherits from ICollection<>, thus Automapper can understand that and will do the collection mapping properly.
It doesn't work when the destination property is null and is interface type. You get this error, because automapper actually found out it can be assigned from ICollection<> so it instantiates the property using generic List<>, which is default collection when automapper must create new collection property, but then when it tries to actually assign it, it will fail, because obviously List<> cannot be cast to ISet<>
There are three solution to this:
Create a feature request to support ISet<> collections and hope they will add it
Make sure the property is not null. Eg. instantiate it in constructor to empty HashSet<>. This might cause some troubles for ORM layers, but is doable
The best solution that I went with is to create custom value resolver, which you already have and instantiate the property yourself if it is null. You need to implement the IValueResolver, because the provided base ValueResolver will not let you instantiate the property. Here is the code snippet that I used:
public class EntityCollectionMerge : IValueResolver
where TDest : IEntityWithId
where TSource : IDtoWithId
{
public ResolutionResult Resolve(ResolutionResult source)
{
//if source collection is not enumerable return
var sourceCollection = source.Value as IEnumerable;
if (sourceCollection == null) return source.New(null, typeof(IEnumerable));
//if the destination collection is ISet
if (typeof(ISet).IsAssignableFrom(source.Context.DestinationType))
{
//get the destination ISet
var destSet = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ISet;
//if destination set is null, instantiate it
if (destSet == null)
{
destSet = new HashSet();
source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destSet);
}
Merge(sourceCollection, destSet);
return source.New(destSet);
}
if (typeof(ICollection).IsAssignableFrom(source.Context.DestinationType))
{
//get the destination collection
var destCollection = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ICollection;
//if destination collection is null, instantiate it
if (destCollection == null)
{
destCollection = new List();
source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destCollection);
}
Merge(sourceCollection, destCollection);
return source.New(destCollection);
}
throw new ArgumentException("Only ISet and ICollection are supported at the moment.");
}
public static void Merge(IEnumerable source, ICollection destination)
{
if (source == null) return;
var destinationIds = destination.Select(x => x.Id).ToHashSet();
var sourceDtos = source.ToDictionary(x => x.Id);
//add new or update
foreach (var sourceDto in sourceDtos)
{
//if the source doesnt exist in destionation add it
if (sourceDto.Key (sourceDto.Value));
continue;
}
//update exisiting one
Mapper.Map(sourceDto.Value, destination.First(x => x.Id == sourceDto.Key));
}
//delete entity in destination which were removed from source dto
foreach (var entityToDelete in destination.Where(entity => !sourceDtos.ContainsKey(entity.Id)).ToList())
{
destination.Remove(entityToDelete);
}
}
}
Then on your mapping use opt => opt.ResolveUsing(new EntitCollectionMerge<Entity,Dto>()).FromMember(x => x.ISetMember) or if you have lots of collection like this you can add them automatically to all of them via typeMaps.

Related

AutoMapper.Collection - Skip mapping entirely if source collection property is null

Is there any way to get AutoMapper.Collection to skip the mapping of a collection if the source collection property is null?
In my case, the client may want to signal to the API that there is no need to update a given collection. A logical way do this would be to set the collection property to null in the dto sent from the client.
Basically:
If the source collection property is null: Do not touch the destination collection at all but leave it as-is. Do not clear it
If the source collection is not null: Do the collection mapping. If the source collection is empty this means clearing the destination collection
Is there any way to achieve this in AutoMapper.Collection? What I am looking for is this:
// Sample Classes
public class Entity
{
public ICollection<EntityChild> Children { get; set; }
}
public class EntityChild
{
public int Id { get; set; }
public string Value { get; set; }
}
public class Dto
{
public ICollection<DtoChild> Children { get; set; }
}
public class DtoChild
{
public int Id { get; set; }
public string Value { get; set; }
}
// AutoMapper setup including equality for children
CreateMap<Dto, Entity>();
CreateMap<DtoChild, EntityChild>()
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ReverseMap();
// Sample 1, null source collection
var entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoSkipChildren = new Dto
{
Children = null
};
// Since the source Children property is null, do not update the destination collection
mapper.Map(dtoSkipChildren, entity);
// Sample 2, empty source collection
entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoClearChildren = new Dto
{
Children = new List<DtoChild>()
};
// Now the source children is not null (but empty) so the destination collection should
// be updated (in this case cleared since the source collection is empty)
mapper.Map(dtoClearChildren, entity);
AutoMapper.Collection treats the null source Children property the same as the source Children property containing an empty collection. In both cases the destination Children collection is cleared.
I have tried to tell AutoMapper to skip the source.Children property if null:
CreateMap<Dto, Entity>()
.ForMember(dst => dst.Children, opt => opt.Condition(src => null != src.Children));
This does not change things. Also in this case, the source collection property is null when AutoMapper.Collection sets to work and the destination collection is cleared.
This is not a real solution either:
CreateMap<Dto, Entity>()
.ForMember(
src => src.Children,
opt => opt.MapFrom((src, dst, _, ctx) => src.Children ?? ctx.Mapper.Map<ICollection<DtoChild>>(dst.Children)));
This means reverse mapping the destination collection to the (null) source collection, so it can be mapped back. An ugly hack:
It's crossing the river twice to end up where you started
Wasted effort which is silly on large collections
Risky as the reverse map for other reasons may not be 100% thus introducing pretty hidden bugs
Does anyone have an advice on how to achieve this - or why my use case is not a good idea?
I had the same problem and I figured it out this way
CreateMap<Dto, Entity>()
.ForMember(
dest=> dest.Children,
opt => {
opt.PreCondition(src =>src.Children!= null);
opt.MapFrom(src =>src.Children);
});
Then in the place, you want to use mapping write code like this:
entity = mapper.Map<Dto, Entity>(dto , entity);
opt.PreCondition(src =>src.Children!= null) basically says to AutoMapper to proceed to map if the source field is not empty else do not map.
to read more look at this https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions

Dapper Extensions custom ClassMapper isn't called on Insert()

I'm using Dapper Extensions and have defined my own custom mapper to deal with entities with composite keys.
public class MyClassMapper<T> : ClassMapper<T> where T : class
{
public MyClassMapper()
{
// Manage unmappable attributes
IList<PropertyInfo> toIgnore = typeof(T).GetProperties().Where(x => !x.CanWrite).ToList();
foreach (PropertyInfo propertyInfo in toIgnore.ToList())
{
Map(propertyInfo).Ignore();
}
// Manage keys
IList<PropertyInfo> propsWithId = typeof(T).GetProperties().Where(x => x.Name.EndsWith("Id") || x.Name.EndsWith("ID")).ToList();
PropertyInfo primaryKey = propsWithId.FirstOrDefault(x => string.Equals(x.Name, $"{nameof(T)}Id", StringComparison.CurrentCultureIgnoreCase));
if (primaryKey != null && primaryKey.PropertyType == typeof(int))
{
Map(primaryKey).Key(KeyType.Identity);
}
else if (propsWithId.Any())
{
foreach (PropertyInfo prop in propsWithId)
{
Map(prop).Key(KeyType.Assigned);
}
}
AutoMap();
}
}
I also have this test case to test my mapper:
[Test]
public void TestMyAutoMapper()
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(MyClassMapper<>);
MySubscribtionEntityWithCompositeKey entity = new MySubscribtionEntityWithCompositeKey
{
SubscriptionID = 145,
CustomerPackageID = 32
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
var result = connection.Insert(entity);
var key1 = result.SubscriptionID;
var key2 = result.CustomerPackageID;
}
}
Note that I set the default mapper in the test case.
The insert fails and I notive that my customer mapper is never called. I have no documentation on the github page on the topic, so I'm not sure if there's anything else I need to do to make dapper extensions use my mapper.
Thanks in advance!
Looking at your question, you are attempting to write your own defalut class mapper derived from the existing one. I never used this approach; so I do not know why it is not working or whether it should work.
I explicitly map the classes as below:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
}
public sealed class CustomerMapper : ClassMapper<Customer>
{
public CustomerMapper()
{
Schema("dbo");
Table("Customer");
Map(x => x.CustomerID).Key(KeyType.Identity);
AutoMap();
}
}
The AutoMap() will map rest of the properties based on conventions. Please refer to these two resources for more information about mapping.
Then I call SetMappingAssemblies at the startup of the project as below:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { Assembly.GetExecutingAssembly() });
The GetExecutingAssembly() is used in above code because mapping classes (CustomerMapper and other) are in same assembly which is executing. If those classes are placed in other assembly, provide that assembly instead.
And that's it, it works.
To set the dialect, I call following line just below the SetMappingAssemblies:
DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();
Use your preferred dialect instead of SqlServerDialect.
Apparently, the solution mentioned here may help you achieve what you are actually trying to. But, I cannot be sure, as I said above, I never used it.

jackson serializer cover String null to empty String("") and keep object null is null

I have tried several ways
e.g.
1.create a custom JsonSerializer, and override serialize method
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("");
}
and set the JsonSerializer to NullValueSerializer,like this:
objectMapper.getSerializerProvider().setNullValueSerializer(new NullStringSerializer());
but we can not get the Class Type from null. All null will be covert to "" , include the object.
2.if use SimpleModule
SimpleModule simpleModule = new SimpleModule("StringModule", Version.unknownVersion());
simpleModule.addSerializer(Object.class, new NullStringSerializer());
objectMapper.registerModule(simpleModule);
in the serialize method, the param value do not have any properties which is null.
from the resouce code of jackson 2.6.0 , find method serializeFields in MapSerializer.java (my object is a map) line: 545
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
// First, serialize key
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
} else {
// One twist: is entry ignorable? If so, skip
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer.serialize(keyElem, gen, provider);
}
// And then value
if (valueElem == null) {
provider.defaultSerializeNull(gen);
} else {...}
}
when the valueElem is null, the provide just covert it to null.
and do not have any interface for me to change the strategy.
I can override MapSerializer ,but I do not know how to set the new MapSerializer to the factory.
Is there any solution?
expect your help,thank you!
find a solution :
the abstract class SerializerProvider has a method named findNullValueSerializer, which is called to get the serializer to use for serializing null values for specified property.
We can override SerializerProvider#findNullValueSerializer and match String class:
#Override
public JsonSerializer<Object> findNullValueSerializer(BeanProperty property) throws JsonMappingException {
if (property.getType().getRawClass().equals(String.class)) {
return EmptyStringSerializer.INSTANCE;
} else {
return super.findNullValueSerializer(property);
}
}
and then set SerializerProvider to our ObjectMapper instance.
done.

Using Dapper.Net ORM, how do I cast stored procedure output to a concrete type?

Using Entity Framework I can create concrete classes from most of the sprocs in the database of a project I'm working on. However, some of the sprocs use dynamic SQL and as such no metadata is returned for the sproc.
So for a that sproc, I manually created a concrete class and now want to map the sproc output to this class and return a list of this type.
Using the following method I can get a collection of objects:
var results = connection.Query<object>("get_buddies",
new { RecsPerPage = 100,
RecCount = 0,
PageNumber = 0,
OrderBy = "LastestLogin",
ProfileID = profileID,
ASC = 1},
commandType: CommandType.StoredProcedure);
My concrete class contains
[DataContractAttribute(IsReference=true)]
[Serializable()]
public partial class LoggedInMember : ComplexObject
{
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int16 RowID
{
get
{
return _RowID;
}
set
{
OnRowIDChanging(value);
ReportPropertyChanging("RowID");
_RowID = StructuralObject.SetValidValue(value);
ReportPropertyChanged("RowID");
OnRowIDChanged();
}
}
private global::System.Int16 _RowID;
partial void OnRowIDChanging(global::System.Int16 value);
partial void OnRowIDChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String NickName
{
get
{
return _NickName;
}
set
{
OnNickNameChanging(value);
ReportPropertyChanging("NickName");
_NickName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("NickName");
OnNickNameChanged();
}
}
private global::System.String _NickName;
partial void OnNickNameChanging(global::System.String value);
partial void OnNickNameChanged();
.
.
.
Without having to iterate through the results and add the output parameters to the LoggedInMember object, how do I map these on the fly so I can return a list of them through a WCF service?
If I try var results = connection.Query<LoggedInMember>("sq_mobile_get_buddies_v35", ... I get the following error:
System.Data.DataException: Error parsing column 0 (RowID=1 - Int64)
---> System.InvalidCastException: Specified cast is not valid. at Deserialize...
At a guess your SQL column is a bigint (i.e. Int64 a.k.a. long) but your .Net type has a n Int16 property.
You could play around with the conversion and ignore the stored procedure by doing something like:
var results = connection.Query<LoggedInMember>("select cast(9 as smallint) [RowID] ...");
Where you are just selecting the properties and types you want to return your object. (smallint is the SQL equivalent of Int16)
The solution to this was to create a complex object derived from the sproc with EF:
public ProfileDetailsByID_Result GetAllProfileDetailsByID(int profileID)
{
using (IDbConnection connection = OpenConnection("PrimaryDBConnectionString"))
{
try
{
var profile = connection.Query<ProfileDetailsByID_Result>("sproc_profile_get_by_id",
new { profileid = profileID },
commandType: CommandType.StoredProcedure).FirstOrDefault();
return profile;
}
catch (Exception ex)
{
ErrorLogging.Instance.Fatal(ex); // use singleton for logging
return null;
}
}
}
In this case, ProfileDetailsByID_Result is the object that I manually created using Entity Framework through the Complex Type creation process (right-click on the model diagram, select Add/Complex Type..., or use the Complex Types tree on the RHS).
A WORD OF CAUTION
Because this object's properties are derived from the sproc, EF has no way of knowing if a property is nullable. For any nullable property types, you must manually configure these by selecting the property and setting its it's Nullable property to true.

Create content error - Specified cast is not valid

I have a custom module. Migrations.cs looks like this.
public int Create()
{
SchemaBuilder.CreateTable("MyModuleRecord", table => table
.ContentPartRecord()
...
);
ContentDefinitionManager.AlterPartDefinition(
typeof(MyModulePart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition("MyModule",
cfg => cfg
.WithPart("MyModulePart")
.WithPart("CommonPart")
.Creatable()
);
return 1;
}
This is the code I have in the controller.
var newcontent = _orchardServices.ContentManager.New<MyModulePart>("MyModule");
...
_orchardServices.ContentManager.Create(newcontent);
I get the invalid cast error from this New method in Orchard.ContentManagement ContentCreateExtensions.
public static T New<T>(this IContentManager manager, string contentType) where T : class, IContent {
var contentItem = manager.New(contentType);
if (contentItem == null)
return null;
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
return part;
}
Any idea what I am doing wrong?
Thanks.
This is the handler.
public class MyModuleHandler : ContentHandler
{
public MyModuleHandler(IRepository<MyModuleRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
You are getting the InvalidCastException because the content item doesn't appear to have your MyModulePart attached.
If there were a driver for your part, then there is an implicit link somewhere that allows your part to be shown on a content item (I'm not sure how this is done, maybe someone else could elaborate - but it is something to do with how shapes are harvested and picked up by the shape table deep down in Orchard's core).
However since you don't have a driver, adding an ActivatingFilter to your part's handler class will make the link explicitly:
public MyModulePartHandler : ContentHandler {
public MyModulePartHandler() {
Filters.Add(StorageFilter.For(repository));
Filters.Add(new ActivatingFilter<MyModulePart>("MyModule");
}
}
Your part table name is wrong. Try renaming it to this (so the part before "Record" matches your part model name exactly):
SchemaBuilder.CreateTable("MyModulePartRecord", table => table
.ContentPartRecord()
...
);

Resources