How can I sort a nested collection using linq? - c#

I am fairly certain this is possible, but I am having some problems getting it to work. I am trying to get a simple object graph with only 1 level of nesting using linq. Here is an example of the structure:
public class City
{
public int CityId { get; set; }
public string Name { get; set; }
public List<House> Houses { get; set; }
}
public class House
{
public int HouseId { get; set; }
public int Number { get; set; }
public int CityId { get; set; }
}
So what I am trying to do is get all the cities, with all of their related Houses. I am trying to have the cities sorted by Name, and then their nested houses sorted by number. I tried a projection, a foreach to sort, and a slow iteration after the query is complete. Here is the basics of what I have at the moment, but I cannot see how to access the nested set:
List<City> Cities = db.Cities.Include(c => c.Houses).OrderBy(c => c.Name).ToList();
How can I also manage to sort each cities Houses by their Number (without affecting the order that the cities are in)?

Would something like this work?
var Cities =
(from c in db.Cities
orderby c.Name
select new
{
Name = c.Name,
Houses = c.Houses.OrderBy(c => c.Number).ToList()
}
);

Try this
var cities = (from city in db.Cities
orderby city.Name
select new {
City = city,
Houses = city.Houses.OrderBy(c => c.Number)
})
.ToList();

Related

Trying Extract List of Categories with Corresponding Tickets using Linq

I am trying to extract list of Categories with the corresponding Tickets for a specific userId using Linq Lambda expression.
Category:
public class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
Ticket:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public User User { get; set; }
public Category Category { get; set; }
}
User
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
This is what I tried so far. But it is not even close.
public async Task<IEnumerable<Category>> GetCategories(string userid)
{
var categories = _context.Categories
.Include(c => c.Tickets)
.AsQueryable();
categories = categories
.Where(c => c.Tickets.Any(t =>t.User.Id.Equals(id)));
return await categories.ToListAsync();
}
How can I have a list of categories with corresponding tickets for a specific userId?
The issue is this line of code:
categories = categories.Where(c => c.Tickets.Any(t =>t.User.Id.Equals(id)));
This will return all categories that contain at least 1 ticket with the specified user id, but all tickets in the category will be included. My understanding is, you want to keep only the tickets that belong to the specified user id.
The fact that your Tickets are represented by an ICollection<Ticket> instead of IEnumerable<Ticket>, make this a bit more difficult - since LINQ works on IEnumerables. That's the reason for the ToList() call, which causes enumeration - something to be aware of here - Tickets collection is no longer lazy past that point.
This code will do what you want:
IEnumerable<Category> selection = (from c in categories
select new Category
{
Id = c.Id,
CategoryName = c.CategoryName,
Tickets = (from t in c.Tickets
where t.User.Id.Equals(id)
select t).ToList()
}).Where(c => c.Tickets.Count() > 0);
but, I think, both performance-wise and readability-wise, you might want a loop instead:
List<Category> selection = new List<Category>();
foreach (var category in categories)
{
category.Tickets = category.Tickets.Where(t => t.User.Id.Equals(id)).ToList();
if (category.Tickets.Count > 0)
{
selection.Add(category);
}
}
Finally, since your Ticket class carries the Category info anyway, it may be worth to flatten the list using SelectMany:
var selection = categories.SelectMany(c => c.Tickets.Where(t => t.User.Id.Equals(id)));
This returns a flattened list of Tickets - but only the ones that match the specified user id, from all categories. This has the advantage of staying lazy (it's still an IEnumerable), being simple, and very likely of higher performance than the other options; but, you no longer have the nested list.
Take your pick!

Cannot implicitly convert type 'System.Collection.Generic.IEnumerable<string>' to 'string' while Adding data to the ViewModel

While using LinQ, retrieving data from foreign key table data are available. But when I try to 'Add' into my ViewModel this warnings shows. Warningsare difference from each other. such as,
Cannot implicitly convert type 'System.Collection.Generic.IEnumerable<string>' to 'string'
and
Cannot implicitly convert type 'System.Collection.Generic.IEnumerable<System.Collection.Generic.IEnumerable.IEnumerable<string>' to 'string'.
I tried casting to ToString() but it was worthless, no errors shows but data was replaced with system messages. I also tried to LinQ Join in students but I was unable to show Skills Comma Separated that way.
Here is my code:
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
coutryName=sg.Select(c=>c.Country.CountryName),
cityName=sg.Select(ct=>ct.Country.Cities.Select(x=>x.CityName)),
skillName=sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName)),
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName)),
dob=sg.Select(d=>d.DateOfBirth)
}).ToList();
List<StudentListVM> studentLists=new List<StudentListVM>();
foreach (var item in students)
{
studentLists.Add(new StudentListVM
{
studentName = item.studentName,
country = item.coutryName, //warnings here
city = item.cityName, //warnings here
skills = string.Join(",", item.skillName),
resume = item.resumeName, //warnings here
dateOfBirth = item.dob //warnings here
});
}
return View(studentLists);
}
```
StudentListVM class
public class StudentListVM
{
public string studentName { get; set; }
public string country { get; set; }
public string city { get; set; }
public string skills { get; set; }
public string resume { get; set; }
public DateTime dateOfBirth { get; set; }
}
```
I tried this before
var students = (from stud in db.Students
join con in db.Countries on stud.CountryId equals con.CountryId
join ct in db.Cities on stud.CityId equals ct.CityId
join rsm in db.Resumes on stud.ResumeID equals rsm.ResumeId
join stsk in db.StudentSkills on stud.StudentId equals stsk.StudentId
//group stud by stud.StudentId into sg
select new StudentListVM()
{
studentName = stud.StudentName,
countries = con.CountryName,
cities = ct.CityName,
skills=stsk.Skill.SkillName,
resumes = rsm.ResumeName,
dateOfBirth = stud.DateOfBirth,
}).ToList();
```
StudentSkill class:
public partial class StudentSkill
{
public int StudentSkillsId { get; set; }
public int StudentId { get; set; }
public int SkillId { get; set; }
public virtual Student Student { get; set; }
public virtual Skill Skill { get; set; }
}
```
This returns All fine except The Skills in a comma separated list. All I need to show my multiple skills that are checked multiply and added to the database in a separated table name StudentSkills. Is there any good solution to do it?
You are trying to assign a group of strings, specifically the various IEnumerable<string> collections, into a single string due to your Select() calls.
For example, this line is clearly selecting more than one resume name.
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName))
If you don't care and expect them to be all the same value you could just grab the first one:
resume = item.resumeName.FirstOrDefault()
Or flatten the collection some other way.
That said, there's something off with the design when you grab a collection and try to assign it to a single item.
Try to change this line:
skills = string.Join(",", item.skillName),
With this:
skills = string.Join(",", item.skillName.ToArray()),
The problem you have is not in your code. The problem is how you think you are solving it. Think it right, the solution will be right. #Zer0 already mentioned that but you probably need more explanation. I will try to explain with some assumption that you might be wanting to do --
1: If Student can have multiple country, city, skill and resumes, then the StudentVM class you have is most certainly wrong. By definition it only supports one city, country, skill, etc. Modify it to support multiples -
public class StudentListVM
{
public string studentName { get; set; }
public List<string> countries { get; set; }
public List<string> cities { get; set; }
public string skills { get; set; }
public List<string> resume { get; set; }
//does not make sense to have a list, a person has only one DOB
public DateTime dateOfBirth { get; set; }
}
then the code you have will work -
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
coutryName=sg.Select(c=>c.Country.CountryName),
cityName=sg.Select(ct=>ct.Country.Cities.Select(x=>x.CityName)),
skillName=sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName)),
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName)),
dob=sg.Select(d=>d.DateOfBirth).FirstOrDefault()
}).ToList();
List<StudentListVM> studentLists=new List<StudentListVM>();
foreach (var item in students)
{
studentLists.Add(new StudentListVM
{
studentName = item.studentName,
countries = item.coutryName.ToList(), //should work, as these are lists
cities = item.cityName.ToList(), //should work, as these are lists
skills = string.Join(",", item.skillName),
resume = item.resumeName.ToList(), //should work, as these are lists
dateOfBirth = item.dob //does not make sense to have a list, a person has only one DOB
});
}
return View(studentLists);
}
2: Once the class is okay, you could shorten the code. You don't need a second block to create a typed list, you can do it directly -
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new StudentListVM
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
countries=sg.Select(c=>c.Country.CountryName).ToList(),
cities=sg.SelectMany(ct=>ct.Country.Cities.Select(x=>x.CityName)).ToList(),
skills=string.Join(",", sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName))),
resume=sg.SelectMany(r=>r.Resumes.Select(m=>m.ResumeName)).ToList(),
//does not make sense to have a list, a person has only one DOB
dob=sg.Select(d=>d.DateOfBirth).FirstOrDefault()
}).ToList();
return View(students);
}
3: If the above does not make sense, then the idea is not right. Think of what you are trying to achieve and update the question. May be then people will be able to help.
You have said -
Actually I can do them separately in different ways but I can't do it in a single code
So what are those ways? Mentioning them will probably give an idea of what you are trying to achieve. The details on the question is not enough to give you solution. It does not say what you are trying to do.

How to query data with calculated fields and map to a ViewModel with a nested ViewModel Collection?

I have referenced numerous questions on this site related to calculated fields and ViewModels, but I can't seem to extrapolate from examples given. I hope that laying out a specific scenario would allow someone to pin point what I can't see. I am new to WebApp design in general. Please take that into consideration. Also, if I've left off any relevant information, please let me know and I will update the question.
Here is the scenario:
I have a complex query that is spanning multiple tables to return data used in calculations. Specifically, I store units for a recipe converted to a base unit and then convert the quantity to the units specified by the user.
I am using AutoMapper to map from entities to ViewModels and vice versa, but I am not sure how to handle the calculated values. Especially with the nested ViewModel Collection thrown into the mix.
Option 1
Do I return an autonomous set of data? Like the following... and then somehow use AutoMapper to do the mapping? Perhaps I would need to do the mapping manually, which I haven't found a solid example which includes nested ViewModels. At this point, I'm not even sure if the following code handles the nested collection correctly for the autonomous data.
var userId = User.Identity.GetUserId();
var recipes = from u in db.Users.Where(u => u.Id == userId)
from c in db.Categories
from r in db.Recipes
join ur in db.UserRecipes.Where(u => u.UserId == userId) on r.Id equals ur.RecipeId
join mus in db.MeasUnitSystems on ur.RecipeYieldUnitSysId equals mus.Id
join muc in db.MeasUnitConvs on mus.Id equals muc.UnitSysId
join mu in db.MeasUnits on mus.UnitId equals mu.Id
join msy in db.MeasUnitSymbols on mu.Id equals msy.UnitId
select new
{
Id = c.Id,
ParentId = c.ParentId,
Name = c.Name,
Descr = c.Descr,
Category1 = c.Category1,
Category2 = c.Category2,
Recipes = new
{
Id = r.Id,
Title = r.Title,
Descr = r.Descr,
Yield = String.Format("{0} {1}", ((r.Yield * muc.UnitBaseConvDiv / muc.UnitBaseConvMult) - muc.UnitBaseConvOffset), msy.Symbol)
}
};
Option 2
Another option that crossed my mind was to return the entities and use AutoMapper as I normally would. Then iterate through the collections and perform the calculations there. I feel like I could make this work, but it seems inefficient to me because it would result in many queries back to the database.
Option 3
???? I can't think of any other method to do this. But, please, if you have suggestions, I am more than willing to hear them.
Relevant Data
Here is the query returning the data I want in SQL Server (more or less).
declare #uid as nvarchar(128) = 'da5435ae-5198-4690-b502-ea3723a9b217'
SELECT c.[Name] as [Category]
,r.Title
,r.Descr
,(r.Yield*rmuc.UnitBaseConvDiv/rmuc.UnitBaseConvMult)-rmuc.UnitBaseConvOffset as [Yield]
,rmsy.Symbol
FROM Category as c
inner join RecipeCat as rc on c.Id = rc.CategoryId
inner join Recipe as r on rc.RecipeId = r.Id
inner join UserRecipe as ur on r.Id = ur.RecipeId and ur.UserId = #uid
inner join MeasUnitSystem as rmus on ur.RecipeYieldUnitSysId = rmus.Id
inner join MeasUnitConv as rmuc on rmus.Id = rmuc.UnitSysId
inner join MeasUnit as rmu on rmus.UnitId = rmu.Id
inner join MeasUnitSymbol as rmsy on rmu.Id = rmsy.UnitId
inner join UserUnitSymbol as ruus on rmsy.UnitId = ruus.UnitId and rmsy.SymIndex = ruus.UnitSymIndex and ruus.UserId = #uid
ViewModels
public class CategoryRecipeIndexViewModel
{
public int Id { get; set; }
public int ParentId { get; set; }
[Display(Name = "Category")]
public string Name { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }
public CategoryRecipeIndexViewModel Category2 { get; set; }
public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}
public class RecipeIndexViewModel
{
public int Id { get; set; }
[Display(Name = "Recipe")]
public string Title { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
[Display(Name = "YieldUnit")]
public string Yield { get; set; }
}
UPDATE 2/10/2018
I found an answer here that does a very good job of explaining exactly what I'm looking at. Particularly under the A Better solution ? section. Mapping queries directly to my ViewModels looks like it would allow me to get my calculated values as well. Problem is, the example given is once again too simplistic.
He gives the following DTO's
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public UserTypeDto UserType { set; get; }
}
public class UserTypeDto
{
public int Id { set; get; }
public string Name { set; get; }
}
And does the following for mapping:
var users = dbContext.Users.Select(s => new UserDto
{
Id = s.Id,
Name = s.Name,
UserType = new UserTypeDto
{
Id = s.UserType.Id,
Name = s.UserType.Name
}
});
Now what if the UserDTO looked like this:
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<UserTypeDto> UserTypes { set; get; }
}
How would the mapping be done if the UserTypes were a collection?
Update 2/13/2018
I feel I am making progress, but am currently headed in the wrong direction. I found this and came up with the following (which currently errors because of the method call in the linq query):
*Note: I removed Category2 from the ViewModel as I found it was not needed and only complicated this further.
query inside index controller method
IEnumerable<CategoryRecipeIndexViewModel> recipesVM = db.Categories
.Where(x => x.ParentId == null)
.Select(x => new CategoryRecipeIndexViewModel()
{
Id = x.Id,
ParentId = x.ParentId,
Name = x.Name,
Descr = x.Descr,
Category1 = MapCategoryRecipeIndexViewModelChildren(x.Category1),
Recipes = x.Recipes.Select(y => new RecipeIndexViewModel()
{
Id = y.Id,
Title = y.Title,
Descr = y.Descr
})
});
Recursive Method
private static IEnumerable<CategoryRecipeIndexViewModel> MapCategoryRecipeIndexViewModelChildren(ICollection<Category> categories)
{
return categories
.Select(c => new CategoryRecipeIndexViewModel
{
Id = c.Id,
ParentId = c.ParentId,
Name = c.Name,
Descr = c.Descr,
Category1 = MapCategoryRecipeIndexViewModelChildren(c.Category1),
Recipes = c.Recipes.Select(r => new RecipeIndexViewModel()
{
Id = r.Id,
Title = r.Title,
Descr = r.Descr
})
});
}
At this point, I don't even have the calculations I require, but that doesn't matter until I get this working (small steps). I quickly discovered you can't really call a method inside a Linq Query. Then a thought occurs to me, if I need to force the Linq Query to execute and then perform all the mapping on the in memory data, then I would essentially be doing the same thing as Option 2 (above), but I could perform the calculations within the ViewModel. This is the solution I will pursue and will keep everyone posted.
You have to iterate over UserType Collection and map the value to UserType dto's collection.
Use this code.
var users = dbContext.Users.Select(s => new UserDto
Id = s.Id,
Name = s.FullName,
UserType = s.UserType.Select(t => new UserTypeDto
{
Id = t.Id,
Name = t.Name
}).ToList()
Hope this will help.
I got it working! ...I think. ...Maybe. If anything, I'm querying the data, mapping it to my ViewModels and I have the calculations too. I do have additional questions, but they are a lot more specific. I will layout the solution I followed and where I think it requires work below.
I basically implemented my Option 2 from above, but instead of iterating through the collections, I just performed the calculations within the ViewModels.
Controller Method
public ActionResult Index()
{
var userId = User.Identity.GetUserId();
var recipes = db.Categories.Where(u => u.Users.Any(x => x.Id == userId))
.Include(c => c.Category1)
.Include(r => r.Recipes
.Select(u => u.UserRecipes
.Select(s => s.MeasUnitSystem.MeasUnitConv)))
.Include(r => r.Recipes
.Select(u => u.UserRecipes
.Select(s => s.MeasUnitSystem.MeasUnit.MeasUnitSymbols)));
IEnumerable<CategoryRecipeIndexViewModel> recipesVM = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryRecipeIndexViewModel>>(recipes.ToList());
return View(recipesVM);
}
View Models
public class CategoryRecipeIndexViewModel
{
public int Id { get; set; }
public int ParentId { get; set; }
[Display(Name = "Category")]
public string Name { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }
public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}
public class RecipeIndexViewModel
{
public int Id { get; set; }
[Display(Name = "Recipe")]
public string Title { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public double Yield { get; set; }
public ICollection<UserRecipeIndexViewModel> UserRecipes { get; set; }
[Display(Name = "Yield")]
public string UserYieldUnit
{
get
{
return System.String.Format("{0} {1}", ((Yield *
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvDiv /
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvMult) -
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvOffset).ToString("n1"),
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnit.MeasUnitSymbols.FirstOrDefault().Symbol);
}
}
}
public class UserRecipeIndexViewModel
{
public MeasUnitSystemIndexViewModel MeasUnitSystem { get; set; }
}
public class MeasUnitSystemIndexViewModel
{
public MeasUnitIndexViewModel MeasUnit { get; set; }
public MeasUnitConvIndexViewModel MeasUnitConv { get; set; }
}
public class MeasUnitIndexViewModel
{
public ICollection<MeasUnitSymbolIndexViewModel> MeasUnitSymbols { get; set; }
}
public class MeasUnitConvIndexViewModel
{
public double UnitBaseConvMult { get; set; }
public double UnitBaseConvDiv { get; set; }
public double UnitBaseConvOffset { get; set; }
}
public class MeasUnitSymbolIndexViewModel
{
public string Symbol { get; set; }
}
This appears to be working, but I know it needs some work.
For instance, the relation shown between the Recipe and UserRecipe shows one to many. In reality, if the UserRecipe were filtered by the current user, the relationship would be one to one. Also, the same goes for the MeasUnit and the MeasUnitSymbol entities. Currently, I'm relying on the FirstOrDefault of those collections to actually perform the calculations.
Also, I have seen numerous posts that state that calculations should not be done in the View Models. Except for some who say it's okay if it is only a requirement of the View.
Last I will say that paying attention to variable names within the ViewModels would have saved me some headaches. And I thought I knew how to utilize Linq Queries, but had issues with the data returned. It was easier to rely on the eager loading provided by Entity Framework to bring back the hierarchical data structure needed, versus the flat table structures I'm used to working with.
I'm still new to a lot of this and wrapping my head around some of the quirks of MVC and Entity Framework leaves me brain dead after a few hours, but I will continue to optimize and adopt better programming methods as I go.

LINQ Query (Group BY) one column

Consider the following classes
public class DashboardTile
{
public int ID { get; set; }
public int? CategoryID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DashboardTileBO : DashboardTile
{
public bool IsChecked { get; set; }
public List<DashboardTileBO> DashboardTiles { get; set; }
}
I have list of tiles in which some tiles are child of other.Now I want to show my list of tiles in such a way that if it has childs it gets added to the list.
query I am trying
var allDashBoardTiles = (from a in context.DashboardTiles
group a by a.CategoryID into b
select new BusinessObjects.DashboardTileBO
{
ID = a.ID,
Name = a.Name,
Description = b.Description,
DashboardTiles = b.ToList(),
}).ToList();
var list = context.DashboardUserTiles.Where(a => a.UserID == userId).Select(a => a.DashboardTileID).ToList();
allDashBoardTiles.ForEach(a => a.IsChecked = list.Contains(a.ID));
Now in above query when I use group clause and in select if I use a.ID,a.Name etc it says that it doesnot contain definitionor extension method for it.
Table
You can't access the properties of a directly because GroupBy returns IGrouping<TKey,T>. You can include other columns also in your group by and access them like this:-
(from a in context.DashboardTiles
group a by new { a.CategoryID, a.ID, a.Name } into b
select new BusinessObjects.DashboardTileBO
{
ID = b.Key.ID,
Name = b.Key.Name,
DashboardTiles = b.ToList(),
}).ToList();
Edit:
Also, I guess the property DashboardTiles in DashboardTileBO class should be List<DashboardTile> instead of List<DashboardTileBO>, otherwise we cannot fetch it from DashboardTiles data.

Best way to use LINQ to Entities with a Collection Property

Let's say I have the following model:
As you can see each city has one or more train stations.
And I have the following type:
public class CityDTO
{
public string CityName { get; set; }
public string StateName { get; set; }
public List<string> Trainstations { get; set; }
}
Now the cool thing about LINQ is that I can query the Entity Framework and return a new collection of my type:
public List<CityDTO> GetCities()
{
using (var db = new CityDataContext())
{
var cities = db.Cities;
var trainstations = db.TrainStations;
var query =
(from city in cities
select new CityDTO
{
CityName = city.Name,
StateName = city.State
}).ToList();
return query;
}
}
The question is: can I also return a collection of train station names in the same LINQ query to add to my CityDTO class?
public List<CityDTO> GetCities()
{
using (var db = new CityDataContext())
{
var cities = db.Cities;
var trainstations = db.TrainStations;
var query =
(from city in cities
join trainstation in trainstations
on city.CityId
equals trainstation.CityId into orderGroup
select new CityDTO
{
CityName = city.Name,
StateName = city.State
//assign List<string> of trainstation names to CityDTO.Trainstations
}).ToList();
return query;
}
}
The other problem with the Join above is that it will return the same city name multiple times (for each of its train stations), and I only want one instance of CityDTO per city.
Is this best done with two LINQ statements? One to get a new list of cities, and the second to get the list of train stations for each?
#haim770, your answer helped me on the right track. Just as a matter of interest, I had to change two more things.
When I just add the Select projector, I get the following run time error
So the Select expects the projected type to be IEnumerable, so I could have done this and all would be cool:
public class CityDTO
{
public string CityName { get; set; }
public string StateName { get; set; }
public IEnumerable<string> Trainstations { get; set; } //changed from List<> to IEnumerable<>
}
Now this works fine:
public List<CityDTO> GetCities()
{
using (var db = new CitysDataEntities())
{
var cities = db.Cities;
var query =
(from city in cities
select new CityDTO
{
CityName = city.Name,
Trainstations = city.TrainStations.Select(ts => ts.Name) //now this works
}).ToList();
return query;
}
}
However, after some further reading, I decided to use IQueryable which also has a Select method.
The reason for this is that IQueryable.Select is under System.Linq, while IEnumerable.Select is in the System.Collections Namespace. This is important as IQueryable.Select is optimized to execute with all filters on the server and return only the result to the client, while IEnumerable.Select loads the objects to the client first before filtering.
IEnumerable Vs IQueryable
So the result is this
public class CityDTO
{
public string CityName { get; set; }
public string StateName { get; set; }
public IQueryable<string> Trainstations { get; set; } // changed to IQueryable
}
and
public List<CityDTO> GetCities()
{
using (var db = new CitysDataEntities())
{
var cities = db.Cities;
var query =
(from city in cities
select new CityDTO
{
CityName = city.Name,
Trainstations = city.TrainStations.Select(ts => ts.Name).AsQueryable() // return AsQueryable
}).ToList();
return query;
}
}
Now when I add filters they will be applied server-side.
MSDN - Queryable.Select
MSDN - Enumerable.Select
The Join operation here is implicit because there is a navigation-property from City to TrainStations so EF will be able to automatically retrieve the stations for you :
(from city in cities
select new CityDTO
{
CityName = city.Name,
StateName = city.State,
Trainstations = city.TrainStations.Select(ts => ts.Name).ToList()
})
The above is using Select() to project the list of train stations into a list of string based on the Name property.
See MSDN
Use AutoMapper to project collection property to IList.
Queryable.ProjectTo<UserListDto>().ToArray();
Entity
public class User
{
public string Name { get; set; }
public virtual ICollection<UserRole> Roles { get; set; }
}
Dto
[AutoMapFrom(typeof(User))]
public class UserListDto
{
public string Name { get; set; }
public IList<UserRoleOutput> Roles { get; set; }
[AutoMapFrom(typeof(UserRole))]
public class UserRoleOutput
{
public int RoleId { get; set; }
}
}

Categories