Automapper Mapping Exception - c#

I am trying to build a simple application where I can store and retrieve some details about some devices and the users of them, like an inventory. But when I try to display the list of devices with their owners, Automapper throws this error:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I don't understand what I am doing wrong here. How can I deal with this?
Startup.cs
builder.Services.AddAutoMapper(typeof(MapConfig));
builder.Services.AddControllersWithViews();
var app = builder.Build();
MapConfig.cs
public class MapConfig : Profile
{
public MapConfig()
{
CreateMap<Asset, AssetVM>().ReverseMap();
CreateMap<AppUser, AppUsersVM>().ReverseMap();
}
}
Asset.Cs
public class Asset
{
public int Id { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string? ProductNumber { get; set; }
public string? SerialNumber { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public bool IsAssigned { get; set; }
public string? ISN { get; set; }
public string Status { get; set; }
public bool IsInsured { get; set; }
public string Condition { get; set; }
[ForeignKey("UserId")]
public AppUser AppUser { get; set; }
public string? UserId { get; set; }
}
AssetVM
public class AssetVM
{
public int Id { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
[Display(Name ="Product Number")]
public string? ProductNumber { get; set; }
[Display(Name ="Serial Number")]
public string? SerialNumber { get; set; }
[Display(Name ="Date Created")]
[DataType(DataType.Date)]
public DateTime DateCreated { get; set; }
[Display(Name = "Date Modified")]
[DataType(DataType.Date)]
public DateTime DateModified { get; set; }
[Display(Name ="Assigned")]
public bool IsAssigned { get; set; }
public string? ISN { get; set; }
[Required]
public string Status { get; set; }
[Display(Name ="Has Insurance")]
public bool IsInsured { get; set; }
[Required]
public string Condition { get; set; }
public string? UserId { get; set; }
public SelectList? AppUsersList { get; set; }
public AppUsersVM AppUsers { get; set; }
}
This is how I get and map the data that is to be displayed on the page:
public async Task<AssetVM> GetAssets()
{
var asset = await context.Assets.Include(q => q.AppUser).ToListAsync();
var model = mapper.Map<AssetVM>(asset);
return model;
}
And finally I return the result of GetAssets method to the view in my controller:
var model = await assetRepository.GetAssets();
return View(model);

Well, I found what I'm doing wrong. This is what I've done:
Since I was getting a list after querying the database in my GetAssets method, I had to change my mapping to:
var model = mapper.Map<List<AssetVM>>(asset);
And to be able to return this model, I also had to change my method declaration to:
public async Task<List<AssetVM>> GetAssets()
This changes made it work, except I didn't get the details of the user that is using that asset. And this was due to a typo in my AssetVM viewmodel.
public AppUsersVM AppUser { get; set; }
Those were all the changed I had to do. Still have a looong way to be a competent programmer so I'd be glad if you let me know if I have any flaws in my logic or any recommendations at all.

Related

Elastic search Nest (.Net) Total and Documents don't match

I'm having the following situation: the Total from the Valid Nest Response is equals 2 and the Documents count equal 0. I'm not sure how that is possible.
Found the solution. The problem was in my model. You see, my model was something like that:
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string Nickname { get; set; }
public string PersonType { get; set; }
public string Gender { get; set; }
public string MaritalStatus { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{dd/MM/yyyy HH:mm:ss}")]
public DateTime? BirthDate { get; set; }
}
And this Index had Documents missing some fields, like:
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string PersonType { get; set; }
public string Gender { get; set; }
}
The solution was adding the decorator PropertyName from Nest, to Map my model, like this:
public class Customer
{
[PropertyName("id")]
public string Id { get; set; }
[PropertyName("name")]
public string Name { get; set; }
[PropertyName("nickname")]
public string Nickname { get; set; }
[PropertyName("personType")]
public string PersonType { get; set; }
[PropertyName("gender")]
public string Gender { get; set; }
[PropertyName("maritalStatus")]
public string MaritalStatus { get; set; }
[PropertyName("birthDate")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{dd/MM/yyyy HH:mm:ss}")]
public DateTime? BirthDate { get; set; }
}
Now it works!

How to to make properties visible when the Foreign Key comes up as ICollection<T> type in Entity Framework's .edmx's .tt classes [.NET]

I have 2 Tables in the SQL Server SUST_HC_DTLS [Primary Key Column Name = SUST_ID] and the second table is SUST_INC_TRCKR_DTLS [Foreign Key = SUST_HANDLER (it references SUST_ID column of first table. This Table also has a Primary Key column = INC_NBR)].Now when i create the Entity Data Model. It create 2 partial classes for both the Table. As show Below --
For Table = SUST_HC_DTLS (Primary Key Table)
public partial class SUST_HC_DTLS
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public SUST_HC_DTLS()
{
this.SUST_INC_TRCKR_DTLS = new HashSet<SUST_INC_TRCKR_DTLS>();
}
public int SUST_ID { get; set; }
public string HC_NAME { get; set; }
public string HC_EMAIL { get; set; }
public Nullable<System.TimeSpan> SHIFT_START { get; set; }
public Nullable<System.TimeSpan> SHIFT_END { get; set; }
public Nullable<System.DateTime> JOINED_ON { get; set; }
public Nullable<System.DateTime> UPDTD_AT { get; set; }
public string UPDTD_BY { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<SUST_INC_TRCKR_DTLS> SUST_INC_TRCKR_DTLS { get; set; }
}
For Secondary Table = SUST_INC_TRCKR_DTLS (Foreign Key Table)
public partial class SUST_INC_TRCKR_DTLS
{
public string INC_NBR { get; set; }
public string REASON_4_INC { get; set; }
public string RAISED_BY { get; set; }
public int SUST_HANDLER { get; set; }
public string COMMENTS { get; set; }
public string CURRENT_STTS { get; set; }
public Nullable<System.DateTime> CREATED_ON { get; set; }
public string DTLS_ENTRD_BY { get; set; }
public Nullable<System.DateTime> UPDTD_ON { get; set; }
public string DTLS_UPDTD_BY { get; set; }
public virtual SUST_HC_DTLS SUST_HC_DTLS { get; set; }
}
Now, I have separate Class Library for My Models for these respective Table and Below are the Classes in the Class Library --
For Primary Table --
public class HC_TBL
{
[Required]
public int SUST_ID { get; set; }
[Required]
public string HC_NAME { get; set; }
[EmailAddress]
public string HC_EMAIL { get; set; }
[Required]
[DataType(DataType.Time)]
public TimeSpan? SHIFT_START { get; set; }
[Required]
[DataType(DataType.Time)]
public TimeSpan? SHIFT_END { get; set; }
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? JOINED_ON { get; set; }
public DateTime? UPDTD_AT { get; set; }
public string UPDTD_BY { get; set; }
public INC_Trckr_TBL Trckr_TBL { get; set; }
}
For Foreign Key Table
public class INC_Trckr_TBL
{
public string INC_NBR { get; set; }
//[Required]
public string REASON_4_INC { get; set; }
//[Required]
public string RAISED_BY { get; set; }
public int SUST_HANDLER { get; set; }
public string COMMENTS { get; set; }
//[Required]
public string CURRENT_STTS { get; set; }
public DateTime? CREATED_ON { get; set; }
public string DTLS_ENTRD_BY { get; set; }
public DateTime? UPDTD_ON { get; set; }
public string DTLS_UPDTD_BY { get; set; }
}
Now when i do the Database Operations to get all records from the Database. Its not letting me call Foreign key properties /fields. It doesn't even provide them in the list.
However, when i do the LINQ Select from the options i get, i do get the property but then it gives me the error -- Cannot implicitly convert type System.Collections.Generic.IEnumerable to GDE.Model.INC_Trckr_TBL. An explicit conversion exists (Are you missing one?).
Now when i do the explicit conversion it says --
System.NotSupportedException
HResult=0x80131515
Message=Values of type 'collection[Edm.String(Nullable=True,DefaultValue=,MaxLength=20,Unicode=False,FixedLength=False)]' can not be converted to string.
I Honestly don't know what to do anymore. Below is my DBOperations Repository Code --
public List<SustHCModel> GetAllDetails()
{
using (var context = new GDE_SUSTEntities())
{
var result= context.SUST_HC_DTLS.Select(x => new SustHCModel()
{
SUST_ID = x.SUST_ID,
HC_NAME = x.HC_NAME,
HC_EMAIL = x.HC_EMAIL,
SHIFT_START = x.SHIFT_START,
SHIFT_END = x.SHIFT_END,
JOINED_ON = x.JOINED_ON,
IncTrckrModel = new INCTrckrModel()
{
INC_NBR = x.SUST_INC_TRCKR_DTLS.Select(y => y.INC_NBR).ToString(),
REASON_4_INC = x.SUST_INC_TRCKR_DTLS.Select(y => y.REASON_4_INC).ToString(),
RAISED_BY = x.SUST_INC_TRCKR_DTLS.Select(y => y.RAISED_BY).ToString(),
COMMENTS = x.SUST_INC_TRCKR_DTLS.Select(y => y.COMMENTS).ToString(),
CURRENT_STTS = x.SUST_INC_TRCKR_DTLS.Select(y => y.CURRENT_STTS).ToString()
}
}).ToList();
return result;
}
}
I am new to this. So, i am pretty sure that i have made some mistake somewhere. So, please guide me and my apologies in advance for whatever silly mistake i have made.

Entity Framework - Update action and Automapper

For example, I have the following infrastructure class:
[Table("GeoHistory")]
public partial class GeoHistory
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int DriverId { get; set; }
[StringLength(50)]
public string Name { get; set; }
public string Description { get; set; }
[StringLength(100)]
public string GeofenceLocation { get; set; }
[StringLength(100)]
public string GeofencePrevious { get; set; }
[StringLength(20)]
public string StateLocation { get; set; }
[StringLength(20)]
public string StatePrevious { get; set; }
public DateTime? DateTime { get; set; }
[StringLength(5)]
public string Heading { get; set; }
public decimal? Speed { get; set; }
[StringLength(50)]
public string Status { get; set; }
}
and the following View class (let's forget about domain layer):
public class GeoHistoryViewModel
{
public int Id { get; set; }
public int? CompanyId { get; set; }
public int? DriverId { get; set; }
[StringLength(50)]
public string Name { get; set; }
public string Description { get; set; }
}
as we can see, we edit only part of field list.
Now, we want to update data in DB. Of course, we can write like:
Infrastructure.Main.GeoHistory geoHistory = db.GeoHistories.Find(id);
geoHistory.CompanyId = model.CompanyId;
geoHistory.DriverId = model.DriverId;
geoHistory.Name = model.Name;
........
db.SaveChanges();
It works. But I want to use Automapper. And if I try to do the following:
Infrastructure.Main.GeoHistory geoHistory = mapper.Map<Infrastructure.Main.GeoHistory>(model);
db.GeoHistories.Attach(geoHistory);
db.Entry(geoHistory).State = EntityState.Modified;
db.SaveChanges();
It works, but of course remove values of fields, which are not exist in View Model, but exist in infrastructure class. How to use automapper, but don't lost these fields?

how to determine that my model pass all data annotations rule check or not?

I have scenario where I want to know is my model is valid or not.
here is my model
public class CallPartyModel
{
public System.Guid PartyId { get; set; }
public System.Guid FwCallMasterId { get; set; }
[Required(ErrorMessage = "Principal Party is required.")]
[Display(Name = "Principal Party")]
public System.Guid PrincipalPartyId { get; set; }
[Required(ErrorMessage = "Responsible Party is required.")]
[Display(Name = "Responsible Party")]
public System.Guid ResponsiblePartyId { get; set; }
[Display(Name = "File Type")]
public System.Guid FileTypeId { get; set; }
[Display(Name = "Agent Type")]
public Nullable<System.Guid> AgentTypeId { get; set; }
public string AgentTypeCode { get; set; }
public bool AdvancedRequired { get; set; }
public bool SeperateDARequired { get; set; }
public string PrincipalPartyName { get; set; }
public string ResponsiblePartyName { get; set; }
public string PrincipalReferenceCode { get; set; }
public string ResponsibleReferenceCode { get; set; }
public string FileTypeName { get; set; }
public string FileTypeCode { get; set; }
public string AgentTypeName { get; set; }
public bool? DAIssuedFlag { get; set; }
[Range(0, 999999999.999, ErrorMessage = "Value lies outside the 0 to 999999999.999 range")]
public decimal? AdvanceReceivedAmount { get; set; }
public System.Guid CreatedBy { get; set; }
public System.DateTime CreatedDateTime { get; set; }
public Nullable<System.Guid> ModifiedBy { get; set; }
public Nullable<System.DateTime> ModifiedDateTime { get; set; }
public bool IsDeleted { get; set; }
public Nullable<System.Guid> DeletedBy { get; set; }
public Nullable<System.DateTime> DeletedDateTime { get; set; }
//public virtual UserModel FwCore_Users { get; set; } //Created By User
//public virtual UserModel FwCore_Users1 { get; set; }//Modified By User
//public virtual UserModel FwCore_Users2 { get; set; }// Deleted by User
public bool IsDirtyCheck { get; set; }
public bool LockPrinFlag { get; set; }
public string LockPrinMsg { get; set; }
}
I have defined some rules for this ex. public decimal? AdvanceReceivedAmount { get; set; }
the range rule.
I know how to check model state when our model is bonded to view as ModelState.Isvalid()
but in my code I am working with tow diffident models, its in some wcf service, where I am getting the input as string for all properties and I can't define the data annotation rule on second model. So I have to transfer the data manually from model one to model two and in model two (CallPartyModel) I have define the data annotation rules. Now before performing any transaction in database, I have to check if the model properties's value are valid or not, I know I can do it manually but is there any method as modelState.IsValid() for this kind of scenario?
as:
CallPartyModel obj=new CallPartyModel();
obj.AdvanceReceivedAmount=88.88;
if(obj.IsValid())
{
//go
}
else
{
//Show the error according to property
}
Any suggestion or help will be appreciated
How about you check your model1 against model2 by loading the model2 with the values of Model1 and then using
Model2 m2 = new Model2();
//... load up the values into m2 from Model1
if(TryUpdateModel(m2)) //if it is ok (checks validation)
{
... your code...
}
I hope this helps.

Mapping complex DTOs to entities with automapper

I want to map from
LDTTicketUploadDTO[] to IEnumerable<LDTTicket>
The mappings are created in this method and at the end I map the data.
public void UploadLDTTickets(LDTTicketUploadDTO[] ticketDTOs)
{
Mapper.CreateMap<LDTTicketUploadDTO, LDTTicket>();
Mapper.CreateMap<LDTTicketDTO, LDTTicket>();
Mapper.CreateMap<LDTCustomerDTO, LDTCustomer>();
Mapper.CreateMap<LDTDeviceDTO, LDTDevice>();
Mapper.CreateMap<LDTUnitDTO, LDTUnit>();
Mapper.CreateMap<LDTCommandDTO, LDTCommand>();
Mapper.CreateMap<LDTCommandParameterDTO, LDTCommandParameter>();
Mapper.CreateMap<LDTObjectDTO, LDTObject>();
Mapper.CreateMap<LDTControlFileDTO, LDTControlFile>();
Mapper.CreateMap<LDTDeviceDTO, LDTDevice>();
Mapper.CreateMap<LDTLanguageDTO, LDTLanguage>();
Mapper.CreateMap<LDTObjectBitDTO, LDTObjectBit>();
var tickets = Mapper.Map<IEnumerable<LDTTicketUploadDTO>, IEnumerable<LDTTicket>>(ticketDTOs);
// do something with tickets
}
This is how the DTO´s are structured:
public class LDTTicketUploadDTO
{
public LDTTicketDTO Ticket { get; set; }
public LDTDeviceDTO Device { get; set; }
public LDTCustomerDTO Customer { get; set; }
}
public enum TicketStatus
{
New,
InProgress,
Done
}
public class LDTTicketDTO
{
public bool UploadNeeded { get; set; }
public string TicketNumber { get; set; }
public TicketStatus Status { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string AssignedTo { get; set; }
public IEnumerable<LDTUnitDTO> Units { get; set; }
}
public class LDTUnitDTO
{
public int Id { get; set; }
public string FunctionUnit { get; set; }
public int FunctionUnitAddress { get; set; }
public string Zone { get; set; }
public int ZoneUnitAddress { get; set; }
public string Object { get; set; }
public int ObjectAddress { get; set; }
public IEnumerable<LDTCommandDTO> Commands { get; set; }
}
and more...
What works is that these properties are correctly mapped to their counterpart entities:
public LDTDeviceDTO Device { get; set; }
public LDTCustomerDTO Customer { get; set; }
What works NOT is that this property is not mapped:
public LDTTicketDTO Ticket { get; set; }
This is how the Entities are structured:
public class LDTTicket
{
[Key, Column(Order = 0)]
[Required]
public string SerialNumber { get; set; }
[Key, Column(Order = 1)]
[Required]
public string TicketNumber { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[Required]
public string AssignedTo { get; set; }
public TicketStatus Status { get; set; }
public string CreatedBy { get; set; }
public bool UploadNeeded { get; set; }
public virtual LDTCustomer Customer { get; set; }
public virtual LDTDevice Device { get; set; }
public virtual ICollection<LDTUnit> Units { get; set; }
}
ONLY the Customer and Device property are mapped in the LDTTicket
What is wrong with my configuration?
It's expecting to populate a LDTTicket sub-property on the ticket, not the matching properties of the ticket itself. Create direct mappings onto the ticket from the Ticket subproperty of the source directly onto the matching properties of the destination. NOTE: You only need to define your mappings once, not per method execution. Mappings should be defined at app start up and thereafter used.
public void UploadLDTTickets(LDTTicketUploadDTO[] ticketDTOs)
{
Mapper.CreateMap<LDTTicketUploadDTO, LDTTicket>();
.ForMember(d => d.SerialNumber, m => m.MapFrom(s => s.Ticket.SerialNumber))
...
//Mapper.CreateMap<LDTTicketDTO, LDTTicket>(); You don't need this
Mapper.CreateMap<LDTCustomerDTO, LDTCustomer>();
Mapper.CreateMap<LDTDeviceDTO, LDTDevice>();
...
}

Categories