I have a scenario where I want to create an object (Calc) which takes some options as a constructor argument. It happens I have a serialized version of Calc, its properties and the options properties as another single object. Here's my code:
void Main()
{
var mockMapper = new MapperConfiguration(c =>
{
c.AddProfile<MapperProf>();
})
.CreateMapper();
}
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Calc>()
.ConstructUsing(c => new Calc()) // I AM STUCK HERE
CreateMap<Scen, Opt>()
.ForMember(o => o.OptProp, o => o.MapFrom(o => o.OptProp));
}
}
public class Calc
{
public Calc(Opt opt)
{
OptProp = opt.OptProp;
}
public string CalcProp { get; set; }
private string OptProp { get; set; }
}
public class Opt
{
public string OptProp { get; set; }
}
public class Scen
{
public string CalcProp { get; set; }
public string OptProp { get; set; }
}
For various reasons I cannot access Calc.OptProp, I have to pass it in via the constructor argument.
In equivalent terms what I want to do in one shot is:
Calc c = mockMapper.Map<Calc>().ConstructUsing(c => new Calc(mockMapper.Map<Opt>(scen)));
That is, construct both the Calc and Opt from the same Scen.
In the ConstructUsing you can use ResulotionContext and then use mapper for create constructor like this:
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Opt>().ForMember(o => o.OptProp, o => o.MapFrom(scen => scen.OptProp));
CreateMap<Scen, Calc>().ConstructUsing((scen, context) => new Calc(context.Mapper.Map<Scen, Opt>(scen)));
}
};
And for mapping scen:
var scen = new Scen() { CalcProp = "Calc", OptProp = "Opt" };
var calc = mockMapper.Map<Scen, Calc>(scen);
Related
I'm using an generic method to map two classes using Automapper
My generic methods
public class AutoMapperConfiguration
{
public MapperConfiguration Configure<TSource, TDestination>() where TSource:class where TDestination:class
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ClientMappingProfile<TSource,TDestination>>();
});
return config;
}
}
ClientMappingProfile.cs
public class ClientMappingProfile<TSource,TDestination>: Profile where TSource : class where TDestination:class
{
public ClientMappingProfile()
{
CreateMap<TSource, TDestination>().ReverseMap();
}
}
StudentDetailsViewModel.cs
public class StudentDetailsViewModel
{
public long ID { get; set; }
public string FirstName { get; set; }
public List<QualificationViewModel> listQualificationViewModel { get; set; }
}
QualificationViewModel.cs
public class QualificationViewModel
{
public long ID { get; set; }
public long StudentID { get; set; }
public string ExaminationPassed { get; set; }
}
StudentValueObject.cs
public class StudentValueObject
{
public long ID { get; set; }
public string FirstName { get; set; }
public List<StudentQualificationValueObject> listStudentQualificationValueObject { get; set; }
}
StudentQualificationValueObject.cs
public class StudentQualificationValueObject
{
public long ID { get; set; }
public long StudentID { get; set; }
public string ExaminationPassed { get; set; }
}
Usage
StudentValueObject studentValueObject = new StudentValueObject();
var config = new AutoMapperConfiguration().Configure<StudentValueObject, StudentDetailsViewModel>();
var iMapper = config.CreateMapper();
studentValueObject = iMapper.Map<StudentDetailsViewModel, StudentValueObject>(objStudentModel);
So, this works fine with Mapping StudentDetailsViewModel.cs with StudentValueObject.cs. But it silently fails to copy my child list objects which is List<QualificationViewModel> to List<StudentQualificationValueObject>. The child list object always seems to be null. I'm pretty newbie to AutoMapper. I need some help as to know where am I going wrong or what need to be added/fixed to my generic method, so that the child list object gets copied to with Parent object.
Update -
Currently I'm doing it using below code and its working properly but I'm confused is this the proper way of doing this.
StudentValueObject studentValueObject = new StudentValueObject();
var config = new AutoMapperConfiguration().Configure<StudentValueObject, StudentDetailsViewModel>();
var iMapper = config.CreateMapper();
studentValueObject = iMapper.Map<StudentDetailsViewModel, StudentValueObject>(objStudentModel);
config = new AutoMapperConfiguration().Configure<StudentQualificationValueObject, QualificationViewModel>();
iMapper = config.CreateMapper();
studentValueObject.listStudentQualificationValueObject = iMapper.Map<List<QualificationViewModel>, List<StudentQualificationValueObject>>(objStudentModel.listQualificationViewModel);
You have to map the list properties, cause they have different names in the given parent types and you have to add a mapping for the types used within both lists. Here is an working example for your code:
public class StudentsMappingProfile : Profile
{
public StudentsMappingProfile()
{
CreateMap<StudentValueObject, StudentDetailsViewModel>()
.ForMember(viewModel => viewModel.listQualificationViewModel, conf => conf.MapFrom(value => value.listStudentQualificationValueObject));
CreateMap<StudentQualificationValueObject, QualificationViewModel>();
}
}
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<StudentsMappingProfile>());
var mapper = config.CreateMapper();
var source = new StudentValueObject { ID = 73, FirstName = "Hello", listStudentQualificationValueObject = new List<StudentQualificationValueObject> { new StudentQualificationValueObject { ID = 42, StudentID = 17, ExaminationPassed = "World" } } };
var destination = mapper.Map<StudentDetailsViewModel>(source);
Console.ReadKey();
}
}
I want to use automapper to create absolute url using Automappers profile. What is best practice of doing it?
My profiles are autoconfigured during startup.
I am using an Ioc Container if that might help.
SourceToDestinationProfile : Profile
{
public SourceToDestinationProfile()
{
var map = CreateMap<Source, Destination>();
map.ForMember(dst => dst.MyAbsoluteUrl, opt => opt.MapFrom(src => "http://www.thisiswhatiwant.com/" + src.MyRelativeUrl));
...
}
}
In some way I dynamically want to pick up the request base url ("http://www.thisiswhatiwant.com/") to make it possible to put it together with my relativeurl. I know one way of doing it but it is not pretty, ie can't be the best way.
I don't know whether this is what you are looking for:
public class Source
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class Destination
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class ObjectResolver : IMemberValueResolver<Source, Destination, string, string>
{
public string Resolve(Source s, Destination d, string source, string dest, ResolutionContext context)
{
return (string)context.Items["domainUrl"] + source;
}
}
public class Program
{
public void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(o => o.Value1, opt => opt.ResolveUsing<ObjectResolver, string>(m=>m.Value1));
});
var mapper = config.CreateMapper();
Source sr = new Source();
sr.Value1 = "SourceValue1";
Destination de = new Destination();
de.Value1 = "dstvalue1";
mapper.Map(sr, de, opt => opt.Items["domainUrl"] = "http://test.com/");
}
}
CreateMap<SourceType, DestinationType>()
.ForMember(dest => dest.Type3Property, opt => opt.MapFrom
(
src => new Type3
{
OldValueType5 = src.oldValType6,
NewValueType5 = src.newValType6
}
);
While creating Type3 I have to assign nested properties of Type6 to Type5. How do I do it using Automapper.
We probably still need a more complete example to fully answer the question. But... from what you've given us so far, I think you only need to add a mapping from Type6 to Type5.
Here's an example of mapping those "nested properties". You can copy past this into a console app to run yourself.
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
//The map for the outer types
cfg.CreateMap<SourceType, DestinationType>()
.ForMember(dest => dest.Type3Property, opt => opt.MapFrom(src => src.Inner));
//The map for the inner types
cfg.CreateMap<InnerSourceType, Type3>();
//The map for the nested properties in the inner types
cfg.CreateMap<Type6, Type5>()
//You only need to do this if your property names are different
.ForMember(dest => dest.MyType5Value, opt => opt.MapFrom(src => src.MyType6Value));
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var source = new SourceType
{
Inner = new InnerSourceType {
OldValue = new Type6 { MyType6Value = 15 },
NewValue = new Type6 { MyType6Value = 20 }
}
};
var result = mapper.Map<SourceType, DestinationType>(source);
}
}
public class SourceType
{
public InnerSourceType Inner { get; set; }
}
public class InnerSourceType
{
public Type6 OldValue { get; set; }
public Type6 NewValue { get; set; }
}
public class DestinationType
{
public Type3 Type3Property { get; set; }
}
//Inner destination
public class Type3
{
public Type5 OldValue { get; set; }
public Type5 NewValue { get; set; }
}
public class Type5
{
public int MyType5Value { get; set; }
}
public class Type6
{
public int MyType6Value { get; set; }
}
So I have a source object with one list, the list includes two types of values but I would like for the destination to have two list, one for each value but in the same resulting property. I hope the example here is self explanatory.
My Source:
public class SourceObject
{
public SourceObject()
{
SourceList = new List<AnotherSourceObject>();
}
public IList<AnotherSourceObject> SourceList { get; private set; }
//some other properties
}
public class AnotherSourceObject
{
public int Number { get; set; }
public decimal Value1 { get; set; }
public decimal Value2 { get; set; }
public decimal Total { get; set; }
}
My Destination:
public class DestObject
{
public DestObject()
{
ValueOneList = new List<AnotherDestObject>();
ValueOneList = new List<AnotherDestObject>();
}
public IList<AnotherDestObject> ValueOneList { get; private set; }
public IList<AnotherDestObject> ValueTwoList { get; private set; }
//some other properties that map perfectly
}
public class AnotherDestObject
{
public int Number { get; set; }
public decimal Value { get; set; }
}
My Automapper mapping:
Mapper.CreateMap<Source, DestObject>()
.ForMember(dest => dest.ValueOneList, opt => opt.MapFrom(source => source.SourceList)) //get value1 to Value
.ForMember(dest => dest.ValueTwoList, opt => opt.MapFrom(source => source.SourceList)) //get value2 to Value
You can use AfterMap if you have accidentally made setters private:
Mapper.CreateMap<Source, DestObject>()
.AfterMap((src, dest) =>
{
foreach (var item in src.SourceList)
{
dest.ValueOneList.Add(new AnotherDestObject { Number = item.Number, Value = item.Value1 });
dest.ValueTwoList.Add(new AnotherDestObject { Number = item.Number, Value = item.Value2 });
}
});
If setter must be private, then you must add new constructor to your class and use ConstructUsing().
Add this constructor:
public DestObject(List<AnotherSourceObject>() sourceList)
{
ValueOneList = sourceList.Select(x => new AnotherDestObject { Number = item.Number, Value = item.Value1 }).ToList();
ValueTwoList = sourceList.Select(x => new AnotherDestObject { Number = item.Number, Value = item.Value2 }).ToList();
}
And, then:
Mapper.CreateMap<Source, DestObject>()
.ConstructUsing(src => new DestObject(src.SourceList));
With a model such as ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.ComponentModel.DataAnnotations;
namespace Singleton
{
public class Program
{
public static void Main(string[] args)
{
var builder = new ModelBuilder();
builder.Configurations.Add(new TemplateConfiguration());
builder.Configurations.Add(new UserConfiguration());
builder.Configurations.Add(new UnitConfiguration());
builder.Configurations.Add(new AttributeConfiguration());
var model = builder.CreateModel();
using (var context = new SampleDataContext(model))
{
bool updating = true;
if (updating)
{
var units = new List<Unit>
{
new Unit{ Name = "Unit1" },
new Unit{ Name = "Unit2" }
};
units.ForEach(x => { context.Units.Add(x); });
context.SaveChanges();
var templates = new List<Template>
{
new Template{
Name = "Default",
Attributes = new List<Attribute>
{
new Attribute
{
Unit = context.Units.Single( i => i.Name == "Unit1" )
}
}
}
};
templates.ForEach(x =>
{
context.Templates.Add(x);
});
context.SaveChanges();
var users = new List<User>
{
new User
{
Name = "Stacey"
},
new User
{
Name = "Daniel"
},
new User
{
Name = "Derek"
}
};
users.ForEach(x => { context.Users.Add(x); });
context.SaveChanges();
updating = !updating; // stop updating
}
if (!updating)
{
Single.Instance = context.Templates.Single(i => i.Name == "Default");
}
foreach (User user in context.Users)
{
Console.WriteLine(user.Template.Name); // does template requery?
}
}
}
}
public class Template
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Attribute> Attributes { get; set; }
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
//// map the collection entity
HasMany(k => k.Attributes).WithRequired()
.Map("template.attributes",
(template, attribute) => new
{
Template = template.Id,
Attribute = attribute.Id
});
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("templates");
}
}
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
[StoreGenerated(StoreGeneratedPattern.None)]
public Template Template { get { return Single.Instance; } }
}
public class UserConfiguration : EntityConfiguration<User>
{
public UserConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("users");
}
}
public class Unit
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class UnitConfiguration : EntityConfiguration<Unit>
{
public UnitConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("units");
}
}
public class Attribute
{
public virtual int Id { get; set; }
public Unit Unit { get; set; }
}
public class AttributeConfiguration : EntityConfiguration<Attribute>
{
public AttributeConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
// Initialize the Statistic
HasRequired(k => k.Unit);
// map the data type to the context so that it can be queried.
MapHierarchy(c => new
{
c.Id,
Unit = c.Unit.Id
}).ToTable("attributes");
}
}
public class Single
{
public static Template Instance;
}
public class SampleDataContext : DbContext
{
public SampleDataContext(DbModel model)
: base(model)
{
this.ObjectContext.ContextOptions.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Attribute> Attributes { get; set; }
}
}
if I assign Singleton.Instance to a query from the DataContext, and then assign the Singleton.Instance to other objects in my code, will the SQL be run once, or each time it is accessed? Can anyone help me here to see if this pattern is going to save some SQL?
From context.SomeQuery, you're almost certainly just returning some kind of queryable object or other iterable (I'm not sure your example reflects how your EF solution is actually architected, I think some elements were lost for the sake of brevity). So, every time you access this singleton, you're just going to iterate over it (run the query) again.
I recommend you use a simple memoization revolving around 4.0 caching, try this.
The SQL will run each time you access it.
What you'r looking for is some kind of caching strategy.
Have a look at the following thread. I think it will help you: How to make Entity Framework cache some objects