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; }
}
}
Related
Most of examples/question just introduce solutions to map "only one" level of the query using split on like this:
var sql = "SELECT P.Id, P.FirstName, P.LastName, " +
"A.Id AS AddressId, A.StreetNumber, A.StreetName, A.City, A.State " +
"FROM People P INNER JOIN Addresses A ON A.AddressId = P.AddressId; ";
db.Query<Person, Address, Person>( sql, (person, address) => {
person.Address = address;
return person; }, splitOn: "AddressId" ).ToList();
I have a query like this one (just an example):
Select * from Country C
inner join State S
on C.CountryId = S.CountryId
inner join City Ct
on S.StateId = Ct.StateId
How could I map it using dapper to my Model/Class?
There is no out of box solution for your needs in Dapper or its extensions. Dapper maps every single row in result set separately. So you need some extra mapping in order to do something like what you want. You can do it manually after getting results of Query with multiple splitOn. Or use some mapping tool. Please, consider this question with various answers. Adapted to your case the solution(with Slapper.Automapper mapping) would be:
[Test]
public async Task MultipleSplitOn()
{
// Arrange
using (var conn =new SqlConnection("Data Source=YourDb"))
{
await conn.OpenAsync();
var sql = #"SELECT TOP 10 c.[Id] as CountryId
,c.[Name]
,s.[Id] as States_StateId
,s.[Name] as States_Name
,ct.[Id] as States_Cities_CityId
,ct.[Name] as States_Cities_Name
FROM Country c
JOIN State s ON s.[CountryId] = c.[Id]
JOIN City ct ON ct.[StateId] = s.[Id] ";
// Act
dynamic result = await conn.QueryAsync<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Country), new [] { "CountryId" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(State), new [] { "StateId" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(City), new [] { "CityId" });
var countries = (Slapper.AutoMapper.MapDynamic<Country>(result) as IEnumerable<Country>).ToList();
//Assert
Assert.IsNotEmpty(countries);
foreach (var country in countries)
{
Assert.IsNotEmpty(country.States);
foreach (var state in country.States)
{
Assert.IsNotEmpty(state.Cities);
}
}
}
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public List<State> States { get; set; }
}
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public class City
{
public int CityId { get; set; }
public string Name { get; set; }
}
I am trying query all countries and in each country object it would fill up the provinces.
I have the following Classes
public class Country
{
public int Countryid { get; set; }
public string CountryName { get; set; }
public IEnumerable<Province> Provinces { get; set; }
}
public class Province
{
public int ProvinceId { get; set; }
public string ProvinceName { get; set; }
}
public IEnumerable<Country> GetCountries()
{
var query = #"
SELECT [Country].[CountryId], [Country].[Name] as CountryName, [Province].[ProvinceId], [Province].[Name] as ProvinceName
FROM [Province]
RIGHT OUTER JOIN [Country] ON [Province].[CountryId] = [Country].[CountryId]
WHERE [Country].[CountryId] > 0";
return _connection.Query<Country, Province, Country>(query, (country, province) =>
{
country.Provinces = country.Provinces.Concat(new List<Province> { province });
return country;
}, null);
}
The error that I get is the following:
System.ArgumentException: 'When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id
Parameter name: splitOn'
I have been following this example:
https://gist.github.com/Lobstrosity/1133111
From my perspective, I don't see what I did much different besides mine being an outer join which I think should not matter, the result format is about the same.
Why do I need the split on and can this work without it?
IIRC, I did something like this.
var query = #"
SELECT [Country].[CountryId], [Country].[Name] as CountryName, [Province].[ProvinceId], [Province].[Name] as ProvinceName
FROM [Province]
RIGHT OUTER JOIN [Country] ON [Province].[CountryId] = [Country].[CountryId]
WHERE [Country].[CountryId] > 0";
List<Country> countries = new List<Country>();
_connection.Query<Country, Province, Country>(query, (country, province) =>
{
Country lastCountry = countries.FirstOrDefault(d => d.CountryId == country.Id);
if(lastCountry == null)
{
countries.Add(country);
lastCountry = country;
}
lastCountry.Provinces = lastCountry.Provinces.Concat(new List<Province> { province });
return lastCountry;
}, null);
return countries;
I typed this out in LinqPad, so you will need to debug and check it is correct, been a long time since I used Dapper in anger
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.
In my project i have : countries and CountryEditModel.
public class countries
{
public int id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CountryEditModel
{
public int id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool isvalid{ get;set; }
}
countries is my domain model which is binded with entity framework and countryEditModel is the model which i use in my views.
How can i fill values from countries to countryEditModel. I actually want to bind list of all countries to dropdownlist in my view and I don't want to use my countries domain model directly in my view.
To solve i have done this
var countryDomain = context.Country.Select(c => c).ToList();
var countrylist = new List<CountryEditModel>();
var countrymodel = new CountryEditModel();
foreach (var country in countryDomain)
countrymodel = new CountryEditModel()
{
Code = country.Code,
Name = country.Name,
id = country.id
};
countrylist.Add(countrymodel);
Is there any better way?
Answer:
Actually this is what i exactly wanted to do
var countryViewModel = context.Country.Select(c => new CountryEditModel
{
Code = c.Code,
Name = c.Name,
id = c.id
}).ToList();
As indicated by the #rohitsingh this is what he exactly wanted to do
var countryViewModel = context.Country.Select(c => new CountryEditModel
{
Code = c.Code,
Name = c.Name,
id = c.id
}).ToList();
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();