I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}
Related
I have a DTO containing properties and a Model, eg student can have more than one module and a module can be associated with more than one student. the properties are mapping fine but the Model doesn't map.
public class GetStudentByIdMapping : Profile
{
public GetStudentByIdMapping()
{
CreateMap<Student,StudentDetails>();
CreateMap<Module, StudentDetails>()
.ForPath(dest => dest.StudentModules.ModuleName, opt => opt.MapFrom(m => m.ModuleName))
.ForPath(dest => dest.StudentModules.ModuleCode, opt => opt.MapFrom(m => m.ModuleCode))
.ForPath(dest => dest.StudentModules.Description, opt => opt.MapFrom(m => m.Description))
.ReverseMap();
}
}
public async Task<StudentDetails> GetStudent(int studentId)
{
var student = context.Student
.Where(s => s.StudentId == studentId)
.FirstOrDefault();
var module = await context.Order
.Include(m => m.Module)
.Where(o => o.StudentId == studentId)
.Select(m => m.Module).ToListAsync();
var studMap = Mapper.Map<StudentDetails>(student);
Mapper.Map<StudentDetails>(module);
return studMap;
}
These are the ViewModels I want to map to the Models Model in the StudentDetails ViewModel
public class StudentDetails
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public StudentModule StudentModules { get; set; }
}
public class StudentModule
{
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
}
These are my Entities generated by EF Core
public partial class Student
{
public Student()
{
Order = new HashSet<Order>();
}
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
public partial class Module
{
public Module()
{
Order = new HashSet<Order>();
}
public int ModuleId { get; set; }
public int? LectureId { get; set; }
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
public string ModulePath { get; set; }
public virtual Lecture Lecture { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
You only need to pass the created destination to the second map call:
Just try the following code when mapping:
var studMap = Mapper.Map<Student,StudentDetails>(student);
Mapper.Map<Module,StudentDetails>(module,studMap);
Then the studMap will receive all mapped fields value.
I am trying to use automapper 8.0.0 to fill a list of WorkViewModel.
This list is getting data from the Work class out of the database using entity framework.
How ever it looks like something is going wrong with initializing as throwing follows error:
InvalidOperationException: Mapper not initialized
What am I doing wrong?
I have setup the following code!
Startup.cs
services.AddAutoMapper();
Function being called:
public async Task<IEnumerable<WorkViewModel>> GetAllAsync()
{
IList<Work> works = await _context.Work.ToListAsync();
IList<WorkViewModel> viewModelList = Mapper.Map<IList<Work>, IList<WorkViewModel>>(works);
return viewModelList;
}
Configuration:
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<WorkViewModel, Work>();
});
}
}
WorkViewModel:
public class WorkViewModel
{
public int WorkID { get; set; }
public string Name { get; set; }
public byte[] Tumbmail { get; set; }
public string Discription { get; set; }
public string Client { get; set; }
public DateTime Date { get; set; }
public string PreviewLink { get; set; }
public string GitLink { get; set; }
public string DownloadLink { get; set; }
public int DetailID { get; set; }
public byte[] Banner { get; set; }
public string Documentation { get; set; }
public int CategoryID { get; set; }
public string Category { get; set; }
}
Work Model:
public class Work
{
[Key]
public int WorkID { get; set; }
[Display(Name = "Project Name")]
public string Name { get; set; }
[Display(Name = "Client name")]
public string Client { get; set; }
[Display(Name = "Description")]
public string Discription { get; set; }
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Display(Name = "Thumbmail")]
public byte[] Tumbmail { get; set; }
[Display(Name = "Preview Link")]
public string PreviewLink { get; set; }
[Display(Name = "Git Link")]
public string GitLink { get; set; }
[Display(Name = "DownloadLink")]
public string DownloadLink { get; set; }
public WorkCategory workCategory { get; set; }
public WorkDetailed WorkDetailed { get; set; }
}
Only adding services.AddAutoMapper(); to the ConfigureServices method would not work for you. You have to configure AutoMapper as follows:
public void ConfigureServices(IServiceCollection services)
{
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}
And also don't forget to install the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package.
I got question on this error which I cannot solve. I am using Entity Framework database first. And before that I have deleted the entity model and create a new one. I am not sure it will affect or not.
This is my viewModel
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
namespace MVCTutorial.Models
{
public class OtherIncViewModel
{
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserGroup> UserGroups { get; set; }
public virtual DbSet<Patient> Patients { get; set; }
//User
public int UserId { get; set; }
public string UserFullName { get; set; }
public string UserIC { get; set; }
[DataType(DataType.Password)]
[Required(ErrorMessage = "This field is requiered.")]
public string UserPassword { get; set; }
public string UserEmail { get; set; }
public Nullable<int> UserGroupId { get; set; }
public string UserMMCRegistrationNo { get; set; }
[DisplayName("UserName")]
[Required(ErrorMessage = "This field is requiered.")]
public string UserLogin { get; set; }
public string UserFaxNo { get; set; }
public string UserDesignation { get; set; }
public string UserStatus { get; set; }
public string UserContact { get; set; }
public virtual UserGroup UserGroup { get; set; }
public string LoginErrorMessage { get; set; }
//Patient
public int PatientId { get; set; }
public string PatientName { get; set; }
public string PatientIC { get; set; }
public int PatientAge { get; set; }
public string PatientGender { get; set; }
public string Hospital { get; set; }
public string Ward { get; set; }
public string Department { get; set; }
public string Barcode { get; set; }
public string Race { get; set; }
public string PatientErrorMessage { get; set; }
public virtual ICollection<CaseOtherInc> CaseOtherIncs { get; set; }
public virtual ICollection<DraftCaseOtherInc> DraftCaseOtherIncs { get; set; }
}
}
This is my controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCTutorial.Models;
namespace MVCTutorial.Controllers
{
public class WardStaffController : Controller
{
OtherIncContext db = new OtherIncContext();
// GET: WardStaff
public ActionResult Index() //Ward Staff Inbox
{
return View();
}
public ActionResult CreateTransfusion() //Ward Staff Create Transfusion
{
return View();
}
public ActionResult CreateIBCT() //Ward Staff Create IBCT
{
return View();
}
public ActionResult CreateNearMiss() //Ward Staff Create Near Miss
{
return View();
}
[HttpPost]
public ActionResult SearchPatient(MVCTutorial.Models.OtherIncViewModel patient)
{
using (OtherIncContext db = new OtherIncContext())
{ //I am wondering here why my local variable does not reference the namespace in this case is MVCTutorial. Is this why i get the null exception? I have check my database...i didn't have null record.
var patientDetails = db.Patients.Where(x => x.PatientIC == patient.PatientIC).FirstOrDefault();
if (patientDetails == null)
{
patient.PatientErrorMessage = "Please enter patient's IC number.";
return View("CreateOtherIncident", patient);
}
else
{
Session["PatientName"] = patientDetails.PatientName;
}
return RedirectToAction("CreateOtherIncident", "WardStaff");
}
}
This is my view
#model MVCTutorial.Models.OtherIncViewModel
#{
ViewBag.Title = "CreateOtherIncident";
Layout = "~/Views/Shared/_LayoutWardStaff.cshtml";
}
<h2>CreateOtherIncident</h2>
<h2>Reported By:</h2>
<p> Name: #Session["UserFullName"].ToString()</p>
<p>Email Address: #Session["UserEmail"].ToString()</p>
<p>Date:</p>
<p>Designation No.: #Session["UserDesignation"].ToString()</p>
<p>Tel.No: #Session["UserContact"].ToString()</p>
<p>Fax.No: #Session["UserFaxNo"].ToString()</p>
<h2>Patient Details:</h2>
#Html.LabelFor(model=>model.PatientIC)
#Html.EditorFor(model=>model.PatientIC)
<p>Patient Name: #Session["PatientName"].ToString()</p> //I get the exception here.
I suspect it is because of the local variable, but I cannot find any further error or exception on that.
Before the exception, To check session should not be null.
like:
#if (HttpContext.Current.Session["PatientName"] != null)
{ //#Session["PatientName"].ToString()
}
Since the object we need in the Mobile Client needs to access its related/associated objects, we decided to return an objectDTO instead of the object when the GetAllObjects method in the controller is called.
Using Postman to query the Backend Server results to the proper behaviour, the retrieved list has all the properties of the DTO.
Problem arises when using the Mobile Client. According to the logs, an "HTTP Error 400.0 - Bad Request" happened and "The request could not be understood by the server due to malformed syntax." is indicated under "More Information."
I dont know why this error happened. I updated the Object class in the Client App to match the ObjectDTO class in the server. For comparison:
ObjectDTO in Server
public class SaleDto
{
public string Id { get; set; }
public string ProductId { get; set; }
public string PromoterId { get; set; }
public string StoreId { get; set; }
public string PaymentMethodId { get; set; }
public bool CorporateSale { get; set; }
public DateTime? DateSold { get; set; }
public double PriceSold { get; set; }
public int QuantitySold { get; set; }
public string Remarks { get; set; }
public bool Deleted { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public byte[] Version { get; set; }
public string ProductSku { get; set; }
public string ProductPartNumber { get; set; }
public string StoreName { get; set; }
public string PaymentMethodName { get; set; }
}
Object Model in Client App
public class Sale
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "productId")]
public string ProductId { get; set; }
[JsonProperty(PropertyName = "promoterId")]
public string PromoterId { get; set; }
[JsonProperty(PropertyName = "storeId")]
public string StoreId { get; set; }
[JsonProperty(PropertyName = "paymentMethodId")]
public string PaymentMethodId { get; set; }
[JsonProperty(PropertyName = "corporateSale")]
public bool CorporateSale { get; set; }
[JsonProperty(PropertyName = "dateSold")]
public DateTime? DateSold { get; set; }
[JsonProperty(PropertyName = "priceSold")]
public double PriceSold { get; set; }
[JsonProperty(PropertyName = "quantitySold")]
public int QuantitySold { get; set; }
[JsonProperty(PropertyName = "remarks")]
public string Remarks { get; set; }
[JsonProperty(PropertyName = "deleted")]
public bool Deleted { get; set; }
[JsonProperty(PropertyName = "createdAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "updatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
[JsonProperty(PropertyName = "productSku")]
public string ProductSku { get; set; }
[JsonProperty(PropertyName = "productPartNumber")]
public string ProductPartNumber { get; set; }
[JsonProperty(PropertyName = "storeName")]
public string StoreName { get; set; }
[JsonProperty(PropertyName = "paymentMethodName")]
public string PaymentMethodName { get; set; }
public virtual Product Product { get; set;}
public virtual Store Store { get; set; }
public virtual PaymentMethod PaymentMethod { get; set; }
}
Or it might be because of the Sync Tables? Here's the code that handles syncing (stuff has been omitted for brevity)
public class DataStore
{
private static DataStore _instance;
public MobileServiceClient MobileService { get; set; }
IMobileServiceSyncTable<Sale> saleTable;
public static DataStore Instance
{
get
{
if (_instance == null)
{
_instance = new DataStore();
}
return _instance;
}
}
private DataStore()
{
MobileService = new MobileServiceClient("url");
var store = new MobileServiceSQLiteStore("tabletable.db");
store.DefineTable<Sale>();
MobileService.SyncContext.InitializeAsync(store);
saleTable = MobileService.GetSyncTable<Sale>();
}
public async Task<Sale> AddSaleAsync(Sale sale)
{
await saleTable.InsertAsync(sale);
bool wasPushed = await SyncSalesAsync();
if (wasPushed) return null;
return sale;
}
public async Task<List<Sale>> GetSalesAsync(int take = 20, int skip = 0)
{
IEnumerable<Sale> items = await saleTable
.Where(sale => !sale.Deleted)
.OrderByDescending(sale => sale.CreatedAt)
.Take(take)
.Skip(skip)
.ToEnumerableAsync();
return new List<Sale>(items);
}
public async Task<bool> SyncSalesAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
bool wasPushed = true;
try
{
await MobileService.SyncContext.PushAsync();
await saleTable.PullAsync("allSales", saleTable.CreateQuery());
}
catch (Exception e)
{
Debug.WriteLine(#"/Sale/ Catch all. Sync error: {0}", e.Message);
Debug.WriteLine(e.StackTrace);
}
return wasPushed;
}
}
Any kind of help will be much appreciated.
Having SaleDto extend/implement EntityData solved the problem
public class SaleDto : EntityData
So here's what I'm getting back from the oData service...
{
"odata.metadata":"http://server.ca/Mediasite/Api/v1/$metadata#UserProfiles",
"value":[
{
"odata.id":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')",
"QuotaPolicy#odata.navigationLinkUrl":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/QuotaPolicy",
"#SetQuotaPolicyFromLevel":{
"target":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/SetQuotaPolicyFromLevel"
},
"Id":"111111111111111",
"UserName":"testuser",
"DisplayName":"testuser Large",
"Email":"testuser#testuser.ca",
"Activated":true,
"HomeFolderId":"312dcf4890df4b129e248a0c9a57869714",
"ModeratorEmail":"testuser#testuserlarge.ca",
"ModeratorEmailOptOut":false,
"DisablePresentationContentCompleteEmails":false,
"DisablePresentationContentFailedEmails":false,
"DisablePresentationChangeOwnerEmails":false,
"TimeZone":26,
"PresenterFirstName":null,
"PresenterMiddleName":null,
"PresenterLastName":null,
"PresenterEmail":null,
"PresenterPrefix":null,
"PresenterSuffix":null,
"PresenterAdditionalInfo":null,
"PresenterBio":null,
"TrustDirectoryEntry":null
}
]
}
I want to deserialize this into a simple class, like just the important stuff (Id, Username, etc...to the end).
I have my class create, but for the life of me I can't figureout how to throw away all the wrapper objects oData puts around this thing.
Can anyone shed some light?
You can use JsonObject do dynamically traverse the JSON, e.g:
var users = JsonObject.Parse(json).ArrayObjects("value")
.Map(x => new User
{
Id = x.Get<long>("Id"),
UserName = x["UserName"],
DisplayName = x["DisplayName"],
Email = x["Email"],
Activated = x.Get<bool>("Activated"),
});
users.PrintDump();
Or deserialize it into a model that matches the shape of the JSON, e.g:
public class ODataUser
{
public List<User> Value { get; set; }
}
public class User
{
public long Id { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool Activated { get; set; }
public string HomeFolderId { get; set; }
public string ModeratorEmail { get; set; }
public bool ModeratorEmailOptOut { get; set; }
public bool DisablePresentationContentCompleteEmails { get; set; }
public bool DisablePresentationContentFailedEmails { get; set; }
public bool DisablePresentationChangeOwnerEmails { get; set; }
public int TimeZone { get; set; }
}
var odata = json.FromJson<ODataUser>();
var user = odata.Value[0];