using Automapper with LINQ in Web Api get method [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have a small Web Api method. How can I map properties in GetBooks method with Automapper? I have tried the solution here .
I have tried
return from c in db.Books select Mapper.Map<BookDTO>(c);
But it didnt work.
Here is my complete code;
//GET: api/Books
public IQueryable<BookDTO> GetBooks()
{
var books = from b in db.Books
select new BookDTO()
{
Id = b.Id,
Title = b.Title,
AuthorName = b.Author.Name
};
return books;
}
Book.cs
public class Book
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public string Genre { get; set; }
// Foreign Key
public int AuthorId { get; set; }
// Navigation property
public Author Author { get; set; }
}
BookDTO.cs
public class BookDTO
{
public int Id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
}
thanks in advance.

Config Map between Book and BookDTO in Global.asax.cs or Startup.cs file:
Mapper.CreateMap<Book, BookDTO>();
Map db.Books in GetBooks() method:
using AutoMapper.QueryableExtensions;
...
public IQueryable<BookDTO> GetBooks()
{
return db.Books.ProjectTo<BookDTO>();
}

You need projection. See Automapper - Projection
Mapper.CreateMap<Book, BookDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name));
IEnumerable<Book> books = db.Books.Cast<Book>();
// assuming Books implements IEnumerable
BookDTO result = Mapper.Map<Book, BookDTO> books;
I'm not sure if you need the projection lines for properties Id and Title. Read the documentation of AutoMapper to see what defaults it uses.

Related

Unable to map from one ViewModel to another

I'm making a list of checkboxes to update a user's roles, and I'm trying to map from this:
public class ApplicationRoleViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string NormalizedName { get; set; }
public string ConcurrencyStamp { get; set; }
public int SortOrder { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Icon { get; set; } // Font Awesome-ikoner, f.eks. "fa-user"
}
to this:
public class SelectableRoleViewModel
{
public Guid Id { get; set; }
public string DisplayName { get; set; }
public bool Selected { get; set; }
}
This is my mapping:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Id, s => s.MapFrom(i => i.Id))
.ForMember(dest => dest.DisplayName, s => s.MapFrom(d => d.DisplayName))
.ForMember(dest => dest.Selected, i => i.Ignore());
Mapping it like this in the controller:
ApplicationRole role = await db.Roles.FirstOrDefaultAsync();
SelectableRoleViewModel sr = auto.Map<SelectableRoleViewModel>(role);
gives me the following error message:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I am registering AutoMapper in Startup.cs like this:
services.AddAutoMapper(typeof(Startup));
Then, in AutoMapperProfile.cs:
public class AutomapperProfile : Profile
{
public AutomapperProfile()
{
// This is not working:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
// This is working:
CreateMap<ApplicationUser, ApplicationUserViewModel>();
// Many more mappings, all working
}
}
How can I get it to work?
The code you specified seems to be correct.
I will just suggest to remove the ForMember method for properties with the same names as auto mapper handles it automatically:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
The problem seems to be because you are not using the mapper right. Where have you registered the mapper? Is the registration happens before the map? Did you do it in the Startup? If you specify more code, it will be easier to help.
UPDATE:
After getting more code & info, the problem was that the map worked on a different object, ApplicationRoleViewModel and not ApplicationRole.
Just to see a difference ;)
public static SelectableRoleViewModel ToSelectable(this ApplicationRoleViewModel model)
{
return new SelectableRoleViewModel
{
Id = model.Id,
DisplayName = model.DisplayName
};
}
// Usage
var selectable = applicationRole.ToSelectable();
Type it once
Perfectly testable
Fully maintainable - supports all kinds of conversion/mapping
Reduce amount of injected dependencies and abstractions (mapper)
No extra dependencies on third party libraries

Automapper: How to flatten complex object to plain object

public class Complex
{
public A A { get; set; }
public A B { get; set; }
}
public class A
{
public int a1 { get; set; }
public int a2 { get; set; }
}
public class B
{
public int b1 { get; set; }
public int b2 { get; set; }
}
//----------------Source Object End Here---------------------
public class Simple <----[This Simple class has only properties of A class]
{
public int aa1 { get; set; }
public int aa2 { get; set; }
}
//----------------Destination Object End Here---------------------
CreateMap<A, Simple>()
.ForMember(dest => dest.aa1, opt => opt.MapFrom(src => src.a1))
.ForMember(dest => dest.aa2, opt => opt.MapFrom(src => src.a2))
// Mapper IS NOT AVAILABLE HERE AS I AM USING PROFILE BASED CONFIGURATION
CreateMap<Complex, Simple>()
.ConvertUsing(src => Mapper.Map<A, Simple>(src.A)); <------Error at this line
//----------------Automammer config End Here---------------------
How to flatten from Complex to Simple? I don't wish to map Complex.A to Simple one by one again in the Complex to Simple config as it is already configured above.
Finally, I figured out with another overloaded method of ConvertUsing
CreateMap<Complex, Simple>()
.ConvertUsing((src,ctx) => {
return ctx.Mapper.Map<Complex, Simple>(src.A)
});
I feel this overloaded method has quite a multiple possibilities and flexibility. I don't have further issue of accessing Mapper directly as mentioned in the question. This overloaded method has its own context parameter (ResolutionContext). We can use Mapper from this context parameter like ctx.Mapper.Map<Complex, Simple>(src.A)

Entity Framework Include Nest collection and nested properties [duplicate]

This question already has an answer here:
EFCore Linq ThenInclude Two Foreign Keys To Same Table
(1 answer)
Closed 5 years ago.
I have 4 classes
public class Customer
{
public string CustomerName { get; set; }
public ICollection<Order> Orders{ get; set; }
}
public class Order
{
public string OrderNumber { get; set; }
public ICollection<OrderLine> OrderLines { get; set; }
public OrderType Type { get; set; }
}
public class OrderLine
{
public string StockItem { get; set; }
}
public class OrderType
{
public string Type { get; set; }
}
Using entity frameworks i want to pull out all the information starting at Customer level. I can get most information out but i am stuck getting OrderType to show against the order.
This is what i have so far.
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.ThenInclude(l => l.OrderLines)
I've tried added select after the o.Orders but that doesn't seem to work.
You can do something like this:
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.Include(o => o.Orders.Select(l => l.OrderLines))
.Include(o => o.Orders.Select(t => t.Type))
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.ThenInclude(l => l.OrderLines)
.Include(o => o.Orders).ThenInclude(t => t.Type)
Adding another Include after the OrderLines and then selecting the type seemed to work.

AutoMapper: What is the difference between ForMember() and ForPath()?

I am reading AutoMapper's ReverseMap() and I can not understand the difference between ForMember() and ForPath(). Implementations was described here. In my experience I achieved with ForMember().
See the following code where I have configured reverse mapping:
public class Customer
{
public string Surname { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomerDto
{
public string CustomerName { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDto>()
.ForMember(dist => dist.CustomerName, opt => opt.MapFrom(src => $"{src.Surname} {src.Name}"))
.ReverseMap()
.ForMember(dist => dist.Surname, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[0]))
.ForMember(dist => dist.Name, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[1]));
});
// mapping Customer -> CustomerDto
//...
//
// mapping CustomerDto -> Customer
var customerDto = new CustomerDto
{
CustomerName = "Shakhabov Adam",
Age = 31
};
var newCustomer = Mapper.Map<CustomerDto, Customer>(customerDto);
}
It is working.
Question
Do ForMember and ForPath the same things or when should I use ForPath() over ForMember()?
In this case, to avoid inconsistencies, ForPath is translated internally to ForMember. Although what #IvanStoev says makes sense, another way to look at it is that ForPath is a subset of ForMember. Because you can do more things in ForMember. So when you have a member, use ForMember and when you have a path, use ForPath :)

Fluent NHibernate "Could not resolve property"

I have read a lot of the questions about that same error but none since to match my exact problem. I'm trying to access the property of an object, itself part of a root object, using Fluent NHibernate. Some answers say I need to use projections, others that I need to use join, and I think it should work through lazy loading.
Here are my two classes along with the Fluent mappings:
Artist class
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Album> Albums { get; set; }
public virtual string MusicBrainzId { get; set; }
public virtual string TheAudioDbId { get; set; }
public Artist() { }
}
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
LazyLoad();
Id(a => a.Id);
Map(a => a.Name).Index("Name");
HasMany(a => a.Albums)
.Cascade.All();
Map(a => a.MusicBrainzId);
Map(a => a.TheAudioDbId);
}
}
Album class
public class Album
{
public virtual int Id { get; set; }
public virtual Artist Artist { get; set; }
public virtual string Name { get; set; }
public virtual IList<Track> Tracks { get; set; }
public virtual DateTime ReleaseDate { get; set; }
public virtual string TheAudioDbId { get; set; }
public virtual string MusicBrainzId { get; set; }
public Album() { }
}
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
LazyLoad();
Id(a => a.Id);
References(a => a.Artist)
.Cascade.All();
Map(a => a.Name).Index("Name");
HasMany(a => a.Tracks)
.Cascade.All();
Map(a => a.ReleaseDate);
Map(a => a.TheAudioDbId);
Map(a => a.MusicBrainzId);
}
}
And the error happens when this code is interpreted:
var riAlbum = session.QueryOver<Album>()
.Where(x => x.Name == albumName && x.Artist.Name == artist)
.List().FirstOrDefault();
The error happens when Fluent NHibernate tries to resolve the x.Artist.Name value:
{"could not resolve property: Artist.Name of: Album"}
What would be the correct way of doing this?
You have to think of your QueryOver query as (nearly) directly translating into SQL. With this in mind, imagine this SQL query:
select
Album.*
from
Album
where
Album.Name = 'SomeAlbumName' and
Album.Artist.Name = 'SomeArtistName'
This won't work because you can't access a related table's properties like that in a SQL statement. You need to create a join from Album to Artist and then use a Where clause:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.List()
.FirstOrDefault();
Also, since you're using FirstOrDefault, you may want to consider moving that logic to the database end. Currently, you're pulling back every record matching your criteria and then taking the first one. You could use .Take to limit the query to 1 result:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.Take(1)
.SingleOrDefault<Album>();
Another explanation is that you are missing your mapping of this property or field in a NHibernateClassMapping definition. I came here about why I was getting this error based on the following scenario.
var query = scheduleRepository.CurrentSession().Query<Schedule>()
.Where(x => x.ScheduleInfo.StartDate.Date < dateOfRun.Date);
This was giving me a Could Not Resolve Property error for StartDate. This was a head scratcher, since I use this syntax all the time.
My mapping file was the following:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
}
}
which was missing the StartDate. Changed to:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
Map(x => x.StartDate);
}
}
Which resolved the error.

Categories

Resources