I have two entities
public class A{
public string Example { get; set; }
public ICollection<B> BCollection { get;set; } = new HashSet<B>();
}
public class B {
public string MyProperty { get; set; }
}
And a simple ViewModel
public class AFirstLoadViewModel {
public string Example { get; set; }
public string MyProperty { get; set; }
}
The thing, is, this viewmodel will be use only in the first data entry, when A will only have one B object inside.
So, i'm trying to map a object like this:
var source = new AFirstLoadViewModel
{
Example = "example",
MyProperty = "myproperty"
}
to this
var destination = new A {
Example = "example"
BCollection = new List<B> {
new B { MyProperty = "myproperty" }
}
}
I try to do the trick using ForPath and BeforeMap without luck
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForPath(x => x.BCollection.First().MyProperty, c => c.MapFrom(x => x.MyProperty))
.BeforeMap((viewModel, entity) => {
if(!entity.BCollection.Any())
BCollection.Add(new B());
});
But i get
System.ArgumentOutOfRangeException: Only member accesses are allowed.
How can i deal with it?
I clarify: both, view model and model have many more properties, the question classes are by way of example
Edit:
I try the solution proposed by Johnatan, and it works, the problem here, is that i cant Unit Testing anymore.
I'm testing with
var config = new MapperConfiguration(cfg => cfg.CreateMap<AFirstLoadViewModel, A>(MemberList.Source));
And when i call config.AssertConfigurationIsValid() fails because the MyProperty property is not mapped
The problem is you are trying to map to .First(). First does not yet exist because the query is on a null / empty collection. You can't assign to the .First() element in a collection if one does not exist already. Instead just map as a collection directly.
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));
Related
I have:
Class A
{
public string FirstName { get; set; }
}
Class B
{
public string FirstName { get; set; }
public Guid RequestId { get; set; }
}
I want to map from A to B, since "A" doesn't have RequestId I want to set it to Guid.NewGuid()
I tried this code:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<A, B>().ForMember(m => m.RequestId, o => Guid.NewGuid());
});
_mapper = config.CreateMapper();
But I'm still getting empty Guid in RequestId.
That mapping should look like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<A, B>().ForMember(m => m.RequestId, o => o.MapFrom(s => Guid.NewGuid()));
});
Change mapping confugration:
cfg.CreateMapper<A,B>().ForMember(x => x.RequestId, o => o.NullSubstitute(Guid.NewGuid());
I'd like to map a common object to an instance of an abstraction (concrete instance of the abstraction) without saying which concrete instance I want. I want to choose the concrete instance based on a condition which will be set up in mapping configuration.
Like here:
ConstructUsing(y => !string.IsNullOrEmpty(y.Label) ? (X)new XLabel() : new XValue())
and here:
m.Map<X>(i2); //where X is an abstraction
Data
// Source
public class Input
{
public int Id { get; set; }
public string Label { get; set; }
public decimal Amount { get; set; }
}
//Base target
public abstract class X
{
public int P1 { get; set; }
}
//Concrete target 1
public class XLabel : X
{
public string Name { get; set; }
}
//Concrete target 2
public class XValue : X
{
public string Value { get; set; }
}
CONFIGURATION:
//config
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Input, XLabel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Label));
cfg.CreateMap<Input, XValue>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Amount));
cfg.CreateMap<Input, X>()
.Include<Input, XLabel>()
.Include<Input, XValue>()
.ConstructUsing(y => !string.IsNullOrEmpty(y.Label) ? (X)new XLabel() : new XValue())
.ForMember(dest => dest.P1, opt => opt.MapFrom(src => src.Id));
});
MAPPING:
//Test
Input i1 = new Input { Id = 1, Amount = 2, };
Input i2 = new Input { Id = 1, Label = "my label" };
IMapper m = new Mapper(config);
var result1 = m.Map<X>(i1);
var result2 = m.Map<X>(i2);
Notice that I'm doing this:
var result1 = m.Map<X>(i1); not this var result1 = m.Map<XValue>(i1); -And this is what I wanted to do -I wanted to Map to abstraction not to a concrete instance
TESTS:
Assert.IsType<XValue>(result1); //PASSED
Assert.IsType<XLabel>(result2); //PASSED
Assert.Equal("my label", ((XLabel)result1).Name);// Message: Assert.Equal() FAILURE Expected: my label; Actual: (null)
Problem:
Looks like this mapping does not work:
cfg.CreateMap<Input, XLabel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Label));
cfg.CreateMap<Input, XValue>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Amount));
Message: Assert.Equal() FAILURE Expected: my label; Actual: (null)
I'm trying to map a class (TrackingKeyStatic<T>) using c# and automapper.
TrackingKeyStatic<T> has the interface IBatchProcessing is inherited from Trackingkey<T> which has the interface ITrackingKey.
So by definition TrackingKeyStatic<T> is IBatchProcessing and ITrackingKey.
Automapper working fine with only one interface (IBatchProcessing)
But can't be mapped/be detect with interface ITrackingKey
I've created a fiddle to demonstrate https://dotnetfiddle.net/TO21PI
So the question is how can I map source with two interface to a concrete type<T>?
I've tried with this config, and it didn't work (which is the problem)
cfg.CreateMap<ITrackingKey, MyEntitiesDbFirstModel>()
I've tried to change the automapper config for
cfg.CreateMap<TrackingKeyStatic<NotReleventClassForThisExample>, MyEntitiesDbFirstModel>()
As demonstrate in Method TestWitTrackingKeyStaticAsSource_WORKING() its working just fine. But I can't really make a mapping for each subclass
I've tried to use method like .Include or .IncludeAllDerived, it didn't work, but I'm not quite sure if I need to use them here? Maybe I did it wrong?
Here's the unit tests I wrote for this question
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace StackOverflow
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestWithItrackingAsSource_NOTWORKING()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ITrackingKey, MyEntitiesDbFirstModel>()
.ForMember(d => d.TrackingKey, o => o.MapFrom(s => s.NewTrackingKey));
cfg.CreateMap<IBatchProcessing, MyEntitiesDbFirstModel>()
.ForMember(d => d.Skip, o => o.MapFrom(s => s.Skip))
.ForMember(d => d.Take, o => o.MapFrom(s => s.Take))
.ForMember(d => d.Total, o => o.MapFrom(s => s.Total));
});
var mapper = config.CreateMapper();
var source = new TrackingKeyStatic<NotReleventClassForThisExample>()
{
Skip = 10,
Take = 50,
Total = 123456,
NewTrackingKey = 987654
};
var actual = mapper.Map<MyEntitiesDbFirstModel>(source);
Assert.AreEqual(10, actual.Skip);//ok
Assert.AreEqual(50, actual.Take);//ok
Assert.AreEqual(123456, actual.Total);//ok
Assert.AreEqual(987654, actual.TrackingKey);//failed
}
[TestMethod]
public void TestWitTrackingKeyStaticAsSource_WORKING()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<TrackingKeyStatic<NotReleventClassForThisExample>, MyEntitiesDbFirstModel>()
.ForMember(d => d.TrackingKey, o => o.MapFrom(s => s.NewTrackingKey));
cfg.CreateMap<IBatchProcessing, MyEntitiesDbFirstModel>()
.ForMember(d => d.Skip, o => o.MapFrom(s => s.Skip))
.ForMember(d => d.Take, o => o.MapFrom(s => s.Take))
.ForMember(d => d.Total, o => o.MapFrom(s => s.Total));
});
var mapper = config.CreateMapper();
var source = new TrackingKeyStatic<NotReleventClassForThisExample>()
{
Skip = 10,
Take = 50,
Total = 123456,
NewTrackingKey = 987654
};
var actual = mapper.Map<MyEntitiesDbFirstModel>(source);
Assert.AreEqual(10, actual.Skip);//ok
Assert.AreEqual(50, actual.Take);//ok
Assert.AreEqual(123456, actual.Total);//ok
Assert.AreEqual(987654, actual.TrackingKey);//work fine
}
}
public interface ITrackingKey
{
int NewTrackingKey { get; set; }
List<object> Records { get; set; }
}
public interface IBatchProcessing
{
int Skip { get; set; }
int Take { get; set; }
int Total { get; set; }
}
public class TrackingKey<T> : ITrackingKey
{
private List<object> _records;
public int NewTrackingKey { get; set; }
public List<T> Records //not relevant for question, it just for implementing interface
{
get { return _records?.Cast<T>()?.ToList(); }
set { _records = value?.Cast<object>()?.ToList(); }
}
List<object> ITrackingKey.Records //not relevant for question, it just for implementing interface
{
get { return _records; }
set { _records = value; }
}
}
public class TrackingKeyStatic<T> : TrackingKey<T>, IBatchProcessing
{
public int Skip { get; set; }
public int Take { get; set; }
public int Total { get; set; }
}
public class MyEntitiesDbFirstModel
{
public int Skip { get; set; }
public int Take { get; set; }
public int Total { get; set; }
public int TrackingKey { get; set; }
}
public class NotReleventClassForThisExample { public int MyProperty { get; set; }}
}
I was able to get it working with a small "hacky" wrapper method:
public static MyEntitiesDbFirstModel MapToMyDbModel<T>(TrackingKeyStatic<T> trackingKey, IMapper mapper)
{
var interimTypeObject = new TrackingKey<T>()
{
NewTrackingKey = trackingKey.NewTrackingKey
};
var actual = mapper.Map<MyEntitiesDbFirstModel>(trackingKey);
mapper.Map<ITrackingKey, MyEntitiesDbFirstModel>(interimTypeObject, actual);
return actual;
}
Here's the fiddle for it - https://dotnetfiddle.net/XAjQB4
You may be able get rid of uglyness further - it seems that AutoMapper is not able to choose the correct map here when you use TrackingKeyStatic<T> but has no problems doing TrackingKey<T>.
I am trying to map object's of the same type which have a collection of child objects and am finding that Ignore() applied to properties on the child object seem to be umm... ignored!
Here's a unit test which demonstrates the problem.
class A
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<B> Children { get; set; }
}
class B
{
public int Id { get; set; }
public string Name { get; set; }
}
[TestClass]
public class UnitTest1
{
[TestInitialize()]
public void Initialize()
{
Mapper.CreateMap<A, A>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
Mapper.CreateMap<B, B>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
}
[TestMethod]
public void TestMethod1()
{
A src = new A { Id = 0, Name = "Source", Children = new List<B> { new B { Id = 0, Name = "Child Src" } } };
A dest = new A { Id = 1, Name = "Dest", Children = new List<B> { new B { Id = 11, Name = "Child Dest" } } };
Mapper.Map(src, dest);
}
After the Map call the A object's Id property is still 1, as expected, but child B object's Id property is changed from 11 to 0.
Why?
There are several bugs in AutoMapper 4.1.1.
First is about UseDestinationValue: https://github.com/AutoMapper/AutoMapper/issues/568
Second is about nested collections: https://github.com/AutoMapper/AutoMapper/issues/934
Horrifying! The workaround is to map your B instances directly:
Mapper.CreateMap<A, A>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.Children, opt => opt.Ignore());
Mapper.CreateMap<B, B>()
.ForMember(dest => dest.Id, opt => opt.Condition((ResolutionContext src) => false));
and add additional mapping calls:
Mapper.Map(src, dest);
Mapper.Map(src.Children.First(), dest.Children.First()); //example!!!
You may call Mapper.Map in cycle:
for (int i = 0; i < src.Children.Count; i++)
{
var srcChild = src.Children[i];
var destChild = dest.Children[i];
Mapper.Map(srcChild, destChild);
}
This will make things work right.
From Efficient data structure to hold employee's activities? , i have a List of type ActividadEmpleado which is declared as:
public string Empleado { get; set; }
public DateTime Fecha { get; set; }
public string Actividad { get; set; }
The LINQ query variable reorders the result in the way i need, which is to store by date and then by ActividadEmpleado and a string. However, var types cannot be passed to methods, so searching this site i am finding out that i either need to create a class to store the results or to modify the LINQ variable to return a List, but i am having issues with the proper declaration.
The LINQ variable is:
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new
{
Fecha = fecha,
FechaActividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
Visual Studio says that queryActividades is:
IOrderedEnumerable<'a>
Anonymous Types:
'a new datetime fecha, iorderedenumerable<'b>
'b new IEnumerable<ActividadEmpleado> Actividades, string NombreEmpleado
I need to pass queryActividades to another method. I tried passing it as an Object but then i lose the extension methods such as Where<> (can i cast it somehow?)
I also read that declaring the results as a tuple should work, but i think declaring a new class is cleaner.
I am just starting with LINQ, i have avoided it to use regular data structures but in this case it's really helpful and would like to know how to either handle anonymous types in them or convert the result to a regular List
Final solution:
class GrupoActividad
{
public DateTime Fecha { get; set; }
public IEnumerable<Actividad> Actividades { get; set; }
}
class Actividad
{
public IEnumerable<ActividadEmpleado> Actividades { get; set; }
public string NombreEmpleado { get; set; }
}
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new GrupoActividad
{
Fecha = fecha,
Actividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Actividad
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
Receiving method:
var actividades = from a in queryActividades
where a.Fecha == fechaCiclo
select new { a.Fecha, a.Actividades };
foreach (var item in actividades)
{
//cycle that holds each day's activities
foreach (var empleado in item.Actividades)
{
//cycle that holds each employee with activities in that day
foreach (var actividad in empleado.Actividades)
{
//final cycle that actually reads the activity
ActividadEmpleado actividadEmpleado = (ActividadEmpleado)actividad;
}
}
}
Right now you are creating a collection that's based on an anonymous type (actually two anonymous types), which cannot practically be passed to another method (other than by using reflection or dynamic). The cleanest way is to create a concrete type that represents the collection - something like
public class ActivityGroup
{
public DateTime Fecha {get; set;}
public IEnumerable<Activity> Activities {get; set;}
}
public class Activity
{
public IEnumerable<Activity> Actividades {get; set;}
public string NombreEmpleado {get; set;}
}
then change your query to:
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new ActivityGroup
{
Fecha = fecha,
FechaActividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Activity
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
and pass it as an IEnumerable<ActivityGroup>
You can use the approach presented by D Stanley. But it would be kind a boring to have to create such classes for any similar query that you write in the future. Instead, you can introduce as generic class for that, like this
public class Grouping<TKey, TElement>
{
public TKey Key { get; set; }
public IEnumerable<TElement> Elements { get; set; }
}
and use it instead of the anonymous types like this
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new Grouping<DateTime, Grouping<string, ActividadEmpleado>>
{
Key = fecha,
Elements = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Grouping<string, ActividadEmpleado>
{
Key = nombreEmpleado,
Elements = actividadesEmpleado
})
.OrderBy(a => a.Key)
})
.OrderBy(a => a.Key);
which can be be passed as IEnumerable<Grouping<DateTime, Grouping<string, ActividadEmpleado>>>.
As you can see, there is a trade off between reusability and readability. Basically this is a Tuple with a little more meaningful names. Note that although we cannot improve the verbosity in the result, we can use a similar technique to Tuple.Create to remove the verbosity inside the query, by adding a class like this
public static class Grouping
{
public static Grouping<TKey, TElement> Create<TKey, TElement>(TKey key, IEnumerable<TElement> elements)
{
return new Grouping<TKey, TElement> { Key = key, Elements = elements };
}
}
and use it like this
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => Grouping.Create(
fecha, fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => Grouping.Create(
nombreEmpleado, actividadesEmpleado))
.OrderBy(a => a.Key)))
.OrderBy(a => a.Key);
You can't pass anonymous types between methods; if you need to pass the data to another method, you need to create an explicit named type that contains the Actividades and NombreEmpleado properties, and another one with Fecha and FechaActividades.