C# AutoMapper, How to map group count - c#

My model:
public class StatusDetailsViewModel
{
public string Status { get; set; }
public long CountNo { get; set; }
}
And my function to get the data:
public List<StatusDetailsViewModel> CheckMeetingStatus(long actionId)
{
List<StatusDetailsViewModel> statusDetails;
var statuses = _igniteDb.myTable.Where(a => a.actionId == actionId)
.GroupBy(a => new { a.Status, a.ElectionGroup }).GroupBy(c => new { c.Key.Status})
.Select(b => new { Status = b.Key.Status, CountNo = b.Count()}).ToList();
//How to Map statuses to statusDetails??
return statusDetails;
}
I've being trying to use auto mapper to achieve this. But I am not sure how to configure my mapper. Any ideas?

Why do you have to map at in your code. Instead of creating anonymous type, you can create the object of StatusDetailsViewModel. Change the select statement as Select(b => new StatusDetailsViewModel() { Status = b.Key.Status, CountNo = b.Count()}
If you want to use AutoMapper then you should define the source and target types while defining the Map/profile. You can not map the anonymous type with AutoMapper.
Your code to create the object of StatusDetailsViewModel (without using Automapper as you don't require for the code you have asked for)
public List<StatusDetailsViewModel> CheckMeetingStatus(long actionId)
{
List<StatusDetailsViewModel> statusDetails;
var statuses = _igniteDb.myTable.Where(a => a.actionId == actionId)
.GroupBy(a => new { a.Status, a.ElectionGroup }).GroupBy(c => new { c.Key.Status})
.Select(b => new StatusDetailsViewModel () { Status = b.Key.Status, CountNo = b.Count()}).ToList();
//How to Map statuses to statusDetails??
return statusDetails;
}

Just because you're returning a List<StatusDetailsViewModel>, you don't need AutoMapper here just project your data by using directly your view model like below:
List<StatusDetailsViewModel> statusDetails = _igniteDb.myTable.Where(a => a.actionId == actionId)
.GroupBy(a => new { a.Status, a.ElectionGroup })
.GroupBy(c => new { c.Key.Status})
.Select(b => new StatusDetailsViewModel { /* <--- Here you instantiate your view model */
Status = b.Key.Status,
CountNo = b.Count()}
).ToList();

Related

Automapper Map multiple optional properties to list

I have a class OrderLineRequest that I want to map to an OrderLine class with a list of barcodes. The properties Barcode1,2,3 needs to be mapped to Barcodes only if the contain a value. Barcode1 is always filled, Barcode2 and Barcode3 are optional. I have created a mapping but this gives me always 3 barcodes in the list. If Barcode1 or 2 is an empty string i don't want to add them to the list. How can i do this?
public class OrderLineRequest
{
public string OrderLineId { get; set; }
public string Barcode1 { get; set; }
public string Barcode2 { get; set; }
public string Barcode3 { get; set; }
public int Quantity { get; set; }
}
public class OrderLine
{
public int Id { get;set;}
public int OrderId { get;set;}
public string OrderLineNumber { get; set; }
public int Qty { get; set; }
public List<Barcode> Barcodes { get;set;}
}
public class Barcode
{
public int Id { get;set;}
public int OrderLineId { get;set;}
public string Code { get;set;}
}
CreateMap<OrderLineRequest, OrderLine>()
.ForMember(b => b.Id, e => e.Ignore())
.ForMember(d => d.OrderId, p => p.Ignore())
.ForMember(d => d.OrderLineNumber, p => p.MapFrom(s => s.OrderLineId))
.ForMember(d => d.Qty, p => p.MapFrom(s => s.Quantity))
.ForMember(d => d.BarCodes, p => p.MapFrom(s => new List<EanCode>() { new EanCode(){Code = s.Barcode1}, new EanCode() { Code = s.Barcode2 }, new EanCode() { Code = s.Barcode3 } }));
Why are you always creating those three barcodes?
I would suggest you to create a function for the predicate
that accepts OrderLineRequest and returns your List and handle the creation within the function. Like that:
private List<EanCode> Foo(OrderLineRequest orderLineRequest)
{
var result = new List<EanCode>();
if(!string.IsNullOrEmpty(orderLineRequest.Barcode1)
result.Add(new EanCode {Code = orderLineRequest.Barcode1});
//...
return result;
}
And then you could use it like:
.ForMember(d => d.BarCodes, p => p.MapFrom(s => Foo(s)));
If you're using Automapper, the step of adding three specific properties from the source to a list in the destination can't be accomplished with a simple function. You have to tell Automapper how to accomplish it.
You can do that by telling it to ignore those properties during the initial mapping, and then add items to the destination list after that mapping is complete.
For brevity this includes only those properties:
var configuration = new MapperConfiguration(
cfg => cfg.CreateMap<OrderLineRequest, OrderLine>()
.ForMember(d => d.Barcodes, opt => opt.Ignore())
.ForSourceMember(s => s.Barcode1, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode2, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode3, opt => opt.DoNotValidate())
.AfterMap((source, destination) =>
{
destination.Barcodes = new List<Barcode>
{
new Barcode { Code = source.Barcode1 }
};
if (source.Barcode2 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode2 });
if (source.Barcode3 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode3 });
}));
It could be said that this makes a case for just writing your own extension instead of using Automapper. It's convenient when the mapping is simple, but if it's not then using it could arguably be more trouble than it's worth. That's a matter of preference.
Create an array of the three properties. Once it's in an array, you can use Where to remove the nulls and Select to instantiate the EanCode instances. Once the data are in good shape, call ToList().
.ForMember
(
d => d.BarCodes,
p => p.MapFrom
(
s =>
(new [] { s.BarCode1, s.BarCode2, s.BarCode3 })
.Where( x => x != null)
.Select( x => new EanCode { Code = x } )
.ToList()
)
);

LINQ query with n+1 issue

I have a query with three sub queries and my problem is that the sub queries are run for each Country (n+1).
I have boiled down my query to make it easier to read, since the main query is around 70 rows, and changed the domain to make it more understandable.
I've tried including Cities/Mountains/Rivers and running .ToList() on the sub queries, but to no avail.
// The CountryDto class I'm selecting to.
public class CountryDto
{
public string CountryName { get; set; }
public IEnumerable<CityDto> CityDtos { get; set; }
public IEnumerable<MountainDto> MountainDtos { get; set; }
public IEnumerable<RiverDto> RiverDtos { get; set; }
}
// The query
var query = _db.Countries
.Select(country => new CountryDto
{
CountryName = country.Name,
CityDtos = country.Citites
.Where(city => city.Population > 10000)
.Select(city => new CityDto
{
Name = city.Name,
}),
MountainDtos = country.Mountains
.Where(mountain => mountain.Height > 100)
.Select(mountain => new MountainDto
{
Name = mountain.Name,
}),
RiverDtos = country.Rivers
.Where(river => river.Length > 1000)
.Select(river => new RiverDto
{
Name = river.Name,
}),
})
.Where(c => c.CityDtos.Any() || c.MountainDtos.Any() || c.RiverDtos.Any());
var totalCount = query.Count();
var countries = await query.ToListAsync();
Entity Framework Core supports parts of the query being evaluated on the client and parts of it being pushed to the database. It is up to the database provider to determine which parts of the query will be evaluated in the database.
In your case, all .Any parts are evaluated in the client side I guess. You can configure your code to throw an exception for client evaluation.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
For more info https://learn.microsoft.com/en-us/ef/core/querying/client-eval
Ended up splitting the query into a .Where() part for the count and a .Select() part for the result, which removed my n+1 issue.
var query = await _db.Countries
.Include(c => c.Cities)
.Include(c => c.Mountains)
.Include(c => c.Rivers)
.Where(c => c.Cities.Any(city => city.Population > 10000)
|| c.Mountains.Any(mountain => mountain.Heigh > 1000)
|| c.River.Any(river => river.Length > 100000))
.Where(c => c.Cities.Any() || c.Mountains.Any() || c.Rivers.Any())
.ToListAsync();
var totalCount = query.Count();
var countries = query
.Select(country => new CountryDto
{
CountryName = country.Name,
CityDtos = country.Citites
.Select(city => new CityDto
{
Name = city.Name,
}),
MountainDtos = country.Mountains
.Select(mountain => new MountainDto
{
Name = mountain.Name,
}),
RiverDtos = country.Rivers
.Select(river => new RiverDto
{
Name = river.Name,
}),
})
.ToList();

Return a list or array with different model types

I have the following code:
BandProfileModel BandProfile;
MusicanProfileModel MusicanProfile;
RegularProfileModel RegularProfile;
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
//This below don't work: Canno't implicity convert type "System.Linq.IQueryable"....
BandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId);
MusicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId);
RegularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId);
}
return View(Profiles);
I want to merge the BandProfile, MusicanProfile and RegularProfile into an Array or List and return it to the view, but I don't know how to do It.
How can I merge different class/model types into one Array/list?
If you have an abstraction for these types, you could return a list of this abstraction. It could be an interface or a abstract class or even a simples class that all types inherit. The option could be to return a collection of System.Object (.net) or object (in C#). For sample
var result = new List<object>();
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
var bandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId).ToList();
var musicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId).ToList();
var regularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId).ToList();
result.AddRange(BandProfile);
result.AddRange(MusicanProfile);
result.AddRange(RegularProfile);
}
return View(result);
Using a object you have to check and convert (cast) the type to read all properties or call method from the object. Follow the OOP (short for Oriented Object Programming), you could have a abstract type to hold all comom properties/methods you need into a single type. Doing something like this, you could convert the result of your lists into this abstract type and use it on the View to have a strongly typed view. For sample:
Let's supose you have this classe:
public class Profile
{
public int Id { get; set; }
public string Name { get; set; }
}
You could have classes iheriting from it or converting to it.
Another option could be casting your queries to return this type.
var result = new List<Profile>();
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
var bandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
var musicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
var regularProfile = ctx.RegularProfile.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
result.AddRange(BandProfile);
result.AddRange(MusicanProfile);
result.AddRange(RegularProfile);
}
return View(result);
This can be done by creating a new class that contains the properties common to all three entities. Then you can select a projection of the entity to the new class.
If your new class looks like this:
public class Profile
{
public int Id {get;set;}
public string Name {get;set;}
}
Then you can select like this:
BandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
MusicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
RegularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
Then you should be able to merge those 3 collections into a single list and return it in the View. This can also have the added benefit of not returning more data to the client than you intended because only properties explicitly added to the Profile class will be returned.

how to map an anonymous object to a class by AutoMapper?

I have an entity:
public class Tag {
public int Id { get; set; }
public string Word { get; set; }
// other properties...
// and a collection of blogposts:
public ICollection<Post> Posts { get; set; }
}
and a model:
public class TagModel {
public int Id { get; set; }
public string Word { get; set; }
// other properties...
// and a collection of blogposts:
public int PostsCount { get; set; }
}
and I query the entity like this (by EF or NH):
var tagsAnon = _context.Tags
.Select(t => new { Tag = t, PostsCount = t. Posts.Count() })
.ToList();
Now, how can I map the tagsAnon (as an anonymous object) to a collection of TagModel (e.g. ICollection<TagModel> or IEnumerable<TagModel>)? Is it possible?
Update 2019-07-31: CreateMissingTypeMaps is now deprecated in AutoMapper v8, and will be removed in v9.
Support for automatically created maps will be removed in version 9.0.
You will need to explicitly configure maps, manually or using
reflection. Also consider attribute mapping.
Update 2016-05-11: DynamicMap is now obsolete.
Now you need to create a mapper from a configuration that sets CreateMissingTypeMaps to true:
var tagsAnon = Tags
.Select(t => new { t.Id, t.Word, PostsCount = t.Posts.Count })
.ToList();
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
var tagsModel = tagsAnon.Select(mapper.Map<TagModel>)
.ToList();
Yes, it is possible. You would have to use the DynamicMap<T> method of the Automapper's Mapper class for each anonymous object you have. Something like this:
var tagsAnon = Tags
.Select(t => new { t.Id, t.Word, PostsCount = t.Posts.Count() })
.ToList();
var tagsModel = tagsAnon.Select(Mapper.DynamicMap<TagModel>)
.ToList();
I am not entirely sure if this is possible. Suggestions:
Why can't you just do this:
var tagsAnon = _context.Tags
.Select(t => new TagModel { Tag = t, PostsCount = t. Posts.Count() })
.ToList();
This SHOULD work, however it fails (I have read that DynamicMap is iffy on collections.
var destination = Mapper.DynamicMap<IEnumerable<TagModel>>(tagsAnon);
This proves that DynamicMap does work with anon types, just not seemingly with enumerables:
var destination = Mapper.DynamicMap<TagModel>(tagsAnon);
You can create a custom function to achieve this with latest Automapper. It uses the CreateMissingTypeMaps property as mentioned in other answers above.
public static List<T> MapDynamicList<T>(IEnumerable<object> obj)
{
var config = new MapperConfiguration(c => c.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
var newModel = obj.Select(mapper.Map<T>).ToList();
return newModel;
}
Then you just call the function with this single line of code:
var viewModel = Models.Helper.MapDynamicList<MatchSubCategory>(model);
where model is the IEnumerable<object> or List<object>.

Cast EF query result to extended type with extended property taken from EF query

I've got a Tag object:
public class Tag
{
public int TagID { get; set; }
public string Name { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public Tag()
{
Jobs = new HashSet<Job>();
}
}
and extended:
public class RecentTag : Tag
{
public int Count { get; set; }
}
...and I'm trying to retrieve a list of RecentTag objects with Count from the query added to each object:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
var tags = Jobs
.Where(j => j.DatePosted > DateTime.Now.AddDays(-(numberofdays)))
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new
{
RecentTag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
// return RecentTags { TagID, Name, Count, Jobs }
}
So, how do I cast results of the query to RecentTag type and return the list of extended objects?
Any help would be appreciated. Thanks in advance!
if Jobs is actually a collection of Tags, and they are in fact RecentTag objects, then you can simply use the Cast method.
var rtags = tags.Cast<RecentTags>;
However, if Jobs is not a collection of tags, then you need to project into a RecentTags objects..
var rtags = tags.Select(x => new RecentTags() { // assign the members });
I ended up doing:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
DateTime startdate = DateTime.Now.AddDays(-(numberofdays));
IEnumerable<RecentTag> tags = Jobs
.Where(j => j.DatePosted > startdate) // Can't use DateTime.Now.AddDays in Entity query apparently
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new RecentTag
{
TagID = k.TagID,
Name = k.Name,
Count = g.Count()
})
.OrderByDescending(g => g.Count)
.Select(a => a);
return tags;
}

Categories