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
Related
I am a beginner, and while testing some code, I can't seem to understand how to do this properly..
1st: I have a City class:
public class City
{
public City()
{
ZipCode = "";
Name = "";
}
public int Id { get; set; }
public string ZipCode { get; set; }
public string Name { get; set; }
}
2nd: I have a Contact class that uses a nullable City class (in case the user does not know the city):
public class Contact
{
public Contact()
{
Name = "";
Line1 = "";
Line2 = "";
CityId = null;
City = new City();
}
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
private int? _CityId;
public int? CityId
{
get { return _CityId; }
set { _CityId = value < 1 ? null : value; }
}
private City _City;
public City City
{
get { return _City; }
set { _City = _CityId == null ? null : value; }
}
}
The problem I encounter is when I retrieve a record that has stored a null City (when retrieving a record and its City is not null, everything will work fine). My .Select() statement looks like this:
var result = await _context.Contact
.Where(w => w.Id == id)
.Include(q => q.City)
.Select(s => new Contact
{
// Entity
Id = s.Id,
// Model
Line1 = s.Line1,
Line2 = s.Line2,
CityId = s.CityId,
City = new City // I am retrieving this so that I can get the inner data as well
{
// Entity
Id = s.City.Id,
// Model
ZipCode = s.City.ZipCode,
Name = s.City.Name,
}
}).FirstOrDefaultAsync();
The output is fine for records that does not have a null City, but if user retrieves a record with null City, it throws the following error:
Nullable object must have a value.
Can anybody please teach me how to do this properly? Thank you in advance!
You don't need to create a new Entity using Select, you are getting the error because if s.City is null s.City.Id doesn't exists. Insteat search it directly using
var result = await _context.Contact
.Include(q => q.City)
.FirstOrDefaultAsync(x => x.Id == id);
Why you use Select and using private property for the city?
Contact Class :
public class Contact
{
public Contact()
{
Name = "";
Line1 = "";
Line2 = "";
CityId = null;
City = new City();
}
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public int? CityId;
public City City
}
Your Select is the same as the entity class, and you don't need to use it.
var result = await _context.Contact
.Include(q => q.City)
.FirstOrDefaultAsync(f => f.Id == id);
Thanks for the answers everyone!
I found that the culprit is this line of code (thanks to your answers and explanations):
Id = s.City.Id
That is because Id here is an int (not nullable since this is an Identity in MSSQL) but when CityId (and also City object) stores a null in the database, s.City.Id will also contain null in the .Select().
The Id = s.City.Id in the above case fails because Id (not nullable) is being forced to receive a null (contained in the s.City.Id).
Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes
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; }
}
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; }
}
}
I have an object lets say its classrooms that is returned from the repository but I use an anonymous type for my View so I convert it like so
return from P in db.ClassRooms
where P.LocationId == LocationId && P.IsApproved==true
select new ClassRoomsViewModel
{
Id = P.Id,
Created = P.CreatedOn,
IsApproved = P.IsApproved,
IsDeleted = P.IsDeleted,
Desks = ??
}
the problem is I am not sure how to handle the desk object.
In my ClassRoomsViewModel class Desks is a list object
public class ClassRoomsViewModel{
public long Id { get; set; }
public DateTime Created { get; set; }
public List<DeskViewModel> Desks { get; set; }
}
public class DeskViewModel{
public long Id { get; set; }
public string Name{ get; set; }
}
The classrooms dataobject is link as a reference to the Desk Object.
so from the above linq query P.Desks.Name will return the name of all objects in the classroom for the linq query
You need to take the collection of desks from the data model, convert each one to a DeskViewModel, and convert the resulting sequence to a List<T>.
That would look something like
p.Desks.Select(d => new DeskViewModel { ... }).ToList()
If P.Desks.Name and P.Desks.Id are arrays you could do it like this with zip.
return from P in db.ClassRooms
where P.LocationId == LocationId && P.IsApproved==true
select new ClassRoomsViewModel
{
Id = P.Id,
Created = P.CreatedOn,
IsApproved = P.IsApproved,
IsDeleted = P.IsDeleted,
Desks = P.Desks.Name.Zip(P.Desks.Id,
(n, i) => new DeskViewModel { Id = i, Name = n });
}