Can't convert from model to viewmodel, using AutoMapper - c#

I'm sure this is dead simple, but I need help ... I'm trying to display a single product in a view, and have this query:
var Product = await (from p in _context.Products
where p.Id == id
select p).FirstOrDefaultAsync();
Then I try to map the result to my viewmodel and return it to the view:
var VMProduct = _mapper.Map<ViewModelProduct, Product>(Product);
return View(VMProduct);
However, I get a build error on the mapping:
"Error CS1503 Argument 1: cannot convert from 'MyStore.Models.Product'
to MyStore.Models.ViewModels.ViewModelProduct'"
This is my entity model,
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
public List<ProductInCategory> InCategories { get; set; }
}
and this is my viewmodel
public class ViewModelProduct
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
public int SortOrder { get; set; }
public IEnumerable<ViewModelCategoryWithTitle> Categories { get; set; }
public ViewModelProduct(ProductInCategory pic)
{
Id = pic.Product.Id;
Title = pic.Product.Title;
Price = pic.Product.Price;
Info = pic.Product.Info;
SortOrder = pic.SortOrder;
}
public ViewModelProduct() { }
}
This is my mapping profile:
CreateMap<Product, ViewModelProduct>();
CreateMap<ViewModelProduct, Product>();
Edit:
After changing
var VMProduct = _mapper.Map<ViewModelProduct, Product>(Product);
to
var VMProduct = _mapper.Map<Product, ViewModelProduct>(Product);
and adding Mapper.AssertConfigurationIsValid();, I get one step further, and am informed that SortOrder, Categories and InCategories are unmapped.
I'm reluctant to change my viewmodel (too much). Can I make the mapping work with the current viewmodel?
Edit 2:
Apparently, now it works. The unmapped properties are still unmapped, but when I removed Mapper.AssertConfigurationIsValid();, the view rendered just fine.

Note that you can define for each member how it should be mapped. This is necessary if the destination member has a different name than the source member.
If source and destination have different (complex) types, add an additional mapping config between these types.
If the member is not mapped, but set somewhere else (e.g. in the controller), ignore it to prevent an error when checking the configuration with Mapper.AssertConfigurationIsValid().
CreateMap<Product, ViewModelProduct>()
// other members will be mapped by convention, because they have the same name
.ForMember(vm => vm.SortOrder, o => o.Ignore()) // to be set in controller
.ForMember(vm => vm.Categories, o => o.MapFrom(src => src.InCategories));
// needed to resolve InCategories -> Categories
CreateMap<ViewModelCategoryWithTitle, ProductInCategory>();
Also, most of the time it is sufficient to tell Automapper just the destination type you want, and let it resolve which mapping to apply:
var VMProduct = _mapper.Map<ViewModelProduct>(Product);

Related

AutoMapper - Assign a value manually depending on the value of the source

I would like to assign a value manually to a DTO property in the Profile of the AutoMapper depending on the value I have in my entity.
Below you can see my code, but it doesn't work as expected because the .AfterMap is independent from the .Condition, as a matter of fact every dest.Sent is mapped with true:
.ForMember(
dest => dest.Sent,
opts => opts.Condition(src => src.Status == 2 && src.Status != 1)
)
.AfterMap((notification, dto) =>
dto.Sent = true);
My DTO
public class NotificationItemDTO
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public bool Sent { get; set; }
}
My Source
public class Notification
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public int Status { get; set; }
}
I wish to assign a value to dest.Sent which is a bool depending on src.Status which is an int. So if src.Status == 1, my dest.Sent = false, if src.Status == 2, my dest.Sent = true. Is it possible to achieve this?
You have misunderstood the usage of .Condition().
AutoMapper allows you to add conditions to properties that must be met before that property will be mapped.
Hence, from your scenario, you are trying to assign a different value to the destination based on condition but not block the mapping from source to destination by condition which is the main purpose of .Condition().
While .AfterMap() is the last action to be executed after the mapping definition logic is executed, hence the value will be overridden.
Using .MapFrom() and defining the logic to set the destination's Sent is true only when the source's Status is 2.
CreateMap<Notification, NotificationItemDTO>()
.ForMember(
dest => dest.Sent,
opts => opts.MapFrom(src => src.Status == 2)
);
Demo # .NET Fiddle

Lookup data with EF6

Hello i'd like to create relation between EF Poco and DTO.
Here is my situation
I've got these 2 entities in my application
public partial class RFID_TAG
{
public int TAG_ID { get; set; }
public string RFID { get; set; }
public Nullable<int> EMPLOYEE_ID{ get; set; }
public virtual EMPLOYEE EMPLOYEE{ get; set; }
}
public partial class EMPLOYEE
{
public int EMPLOYEE_ID{ get; set; }
public string FIRST_NAME{ get; set; }
public string LAST_NAME{ get; set; }
//ETC...
}
I also have this DTO
public class EMPLOYEELookUpData
{
public int EMPLOYEE_ID{ get; set; }
public string FULL_NAME{ get; set; }
}
I'm using this DTO for specific selects where i only need EMPLOYEE's id and name, I've got CRUD view where user can add new tags it contains datagrid with that contains all tags and textbox thats bound to currently selected tags RFID and combobox which has SelectedItem bound to currently selected tags EMPLOYEE property. This is how i'm selecting data:
private async void GetData()
{
Data = await DbContext.RFID_TAG.Include(x => x.EMPLOYEE).ToListAsync();
EmployeesList = await DbContext.MPLOYEE.Where(x => x.ACTIVE == 1)
.Select(x => new EMPLOYEELookUpData{EMPLOYEE_ID = x.EMPLOYEE_ID, FULL_NAME= x.FIRST_NAME + " " + x.LAST_NAME})
.ToListAsync();
}
But i can't figure how to make relation between EMPLOYEE and EMPLOYEELookUpData so that EF knows how to convert EMPLOYEELookUpData to EMPLOYEE.
I believe you can use AutoMapper for this: https://www.nuget.org/packages/AutoMapper/. It can be installed using Nuget.
The code would look something like this:
using (MyEntities myEntities = new MyEntities())
{
List<EMPLOYEELookUpData> employeeLookupData;
try
{
employeeLookupData = myDB
.Employee
.Select(EMPLOYEELookUpData)
.Where(c => x => x.ACTIVE == 1)
.ToList();
}
catch (InvalidOperationException e)
{
//Write a log entry
}
I have not tested the code. You would have to create the mappings and create special mappings for : EMPLOYEELookUpData .FullName as it equals EMPLOYEE.FirstName + EMPLOYEE.Surname. You can find out how to do this by reading the documentation or posting another question on here.

To show the very large data on the ui grid

public List<PropertyListDto> GetPropertiesByStatus(GetPropertyInput input)
{
//exception occurs here
var properties = _propertyRepository
.GetAll()
.Include(p => p.Address)
.ToList();
var results = new List<PropertyListDto>(properties.OrderBy(p => p.Id).MapTo<List<PropertyListDto>>());
return results;
}
[AutoMapFrom(typeof(Property))]
public class PropertyListDto : FullAuditedEntityDto
{
public new int Id { get; set; }
public CountyListDto County { get; set; }
public string Priority { get; set; }
public string Dist { get; set; }
public decimal ListingPrice { get; set; }
public string Blk { get; set; }
public AddressDto Address { get; set; }
public string Contact { get; set; }
public string Lot { get; set; }
public decimal Taxes { get; set; }
public string Mls { get; set; }
public ICollection<CommentEditDto> Comments { get; set; }
public int? LegacyId { get; set; }
}
Q : I need to show around 100,000 (1 lakh) data on the Angular UI Grid.But the problem is, above query gives memory exception.So could you tell me how to sort out this issue ? Thanks.
Note : I need to show the data without pagination.So I have selected this UI Grid.
UPDATE :
When I use .AsNoTracking(), then it works fine on the first query.
var properties = _propertyRepository
.GetAll()
.AsNoTracking()
.Include(p => p.Address)
.ToList();
But then the problem is on MapTo line.Could you tell me how to sort it out ? Thanks.
var results = new List<PropertyListDto>(properties.OrderBy(p => p.Id).MapTo<List<PropertyListDto>>());//error is here now
This is the error :
{"Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding."}
Mapping types:
Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 -> ICollection`1
System.Data.Entity.DynamicProxies.Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 -> System.Collections.Generic.ICollection`1[[IP.Comments.Dtos.CommentEditDto,IP.Application, Version=1.7.1.1, Culture=neutral, PublicKeyToken=null]]
Destination path:
List`1[3126].Comments3126[3126].Comments3126[3126]
Source value:
[Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 3166]
UPDATE 2 : Here I have used Automapper to map EF object into Dto object.
I suppose you are initiate two ToList methods since I do not know what MapTo doing.
However instead doing mapping select directly your dto:
var properties = _propertyRepository
.GetAll()
.AsNoTracking()
.Include(p => p.Address).
.Select(s=> new PropertyListDto{
Id = s.Id
CountyListDto = s.CountyListDto
...
})
OP's Answer : Actually I have reduced all the unnecessary data on the above table and now it's having around 1K+ records.So no problem at all now.Cheers :)

How to query multiple inherited sub-classes in a single query in Entity Framework

I have a big problem of querying diverse types of inherited subentities in a single query in Entity Framework. My essential aim is providing all of my data model structure in a single JSON string by eager loading. And the tricky point is "the inherited subclasses may contain another inherited subclass". The example seen below will clearly explain the situation.
Assume that I have a simple class structure like this:
public class Teacher
{
public int id { get; set; }
public string fullname{ get; set; }
//navigation properties
public virtual HashSet<Course> courses{ get; set; }
}
public class Course
{
public int id { get; set; }
public string coursename{ get; set; }
//foreign keys
public int TeacherId{ get; set; }
//navigation properties
public virtual Teacher teacher{ get; set; }
public virtual HashSet<Course> prerequisites{ get; set; }
}
Course has some subclasses GradedCourse and UngradedCourse
B1 or B2 may have a list of subentities consists of entities of types B1 or B2.
public class GradedCourse : Course
{
public string gradeType{ get; set; }
}
public class UngradedCourse: Course
{
public string successMetric { get; set; }
}
Now by this structure I want to provide a JSON structure from my WEBApi yielding list of Teacher objects including both GradedCourse and UngradedCourse with their subentities and specific fields. I have a query like this but it does not compile
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
gradeType = g.gradeType
}
).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
successMetric= u.successMetric // subclass specific field
}
)
)
}
)
The problem is concating two different types of objects (they have different fields which is not possible for SQL UNION)
How can I handle this? Any help will open my mind. Thanks in advance for the professionals :)
It does not compile because the element type of 2 sets is not the same. So you just need to make them the same before being able to do anything:
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = true,
gradeTypeOrMetric = g.gradeType
}).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = false,
gradeTypeOrMetric= u.successMetric // subclass specific field
}))
//finally select what of your choice
.Select(e => new {
id = e.id,
coursename = e.coursename,
prerequisites = e.prerequisites,
gradeType = e.isGradedCourse ? e.gradeTypeOrMetric : "",
successMetric = e.isGradedCourse ? "" : e.gradeTypeOrMetric
})
});
You still benefit the query being executed on server side without having to pull all teachers to local (and then being able to cast the entities - which is not supported in LinqToEntity query).

RavenDB cross entity query matching on non-identifier property

I have the following entity collections in RavenDB:
public class EntityA
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
public class EntityB
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
The only thing shared is the Tags collection: a tag of EntityA may exist in EntityB, so that they may intersect.
How can I retrieve every EntityA that has intersecting tags with EntityB where the Name property of EntityB is equal to a given value?
Well, this is a difficult one. To do it right, you would need two levels of reducing - one by the tag which would expand out your results, and another by the id to collapse it back. Raven doesn't have an easy way to do this.
You can fake it out though using a Transform. The only problem is that you will have skipped items in your result set, so make sure you know how to deal with those.
public class TestIndex : AbstractMultiMapIndexCreationTask<TestIndex.Result>
{
public class Result
{
public string[] Ids { get; set; }
public string Name { get; set; }
public string Tag { get; set; }
}
public TestIndex()
{
AddMap<EntityA>(entities => from a in entities
from tag in a.Tags.DefaultIfEmpty("_")
select new
{
Ids = new[] { a.Id },
Name = (string) null,
Tag = tag
});
AddMap<EntityB>(entities => from b in entities
from tag in b.Tags
select new
{
Ids = new string[0],
b.Name,
Tag = tag
});
Reduce = results => from result in results
group result by result.Tag
into g
select new
{
Ids = g.SelectMany(x => x.Ids),
g.First(x => x.Name != null).Name,
Tag = g.Key
};
TransformResults = (database, results) =>
results.SelectMany(x => x.Ids)
.Distinct()
.Select(x => database.Load<EntityA>(x));
}
}
See also the full unit test here.
There is another approach, but I haven't tested it yet. That would be to use the Indexed Properties Bundle to do the first pass, and then map those results for the second pass. I am experimenting with this in general, and if it works, I will update this answer with the results.

Categories