I'm trying to figure out how to get the results to show the name value instead of the id value on a join of two tables. My tables basically look like this:
VideoCatwegories (table name)
Id,
CategoryName
and
Videos (table name)
Id,
VideoCategory (fk to category table)
Title
Desc
Url
My linq join:
var query = _videoRepository.Table
.Join
(
_videoCategoryRepository.Table,
v => v.Id,
vc => vc.Id,
(v, vc) => new { Videos = v, VideoCategories = vc }
)
.Select(vid => vid.Videos);
The results yield the category id of 1 instead of Instructional etc.
What am I not understanding / doing correctly?
Thank You
First, your join criterion does not look right: it appears that you are joining an id of video to the id of video category, which is probably not set up that way: more likely, video has an id of the corresponding video category, in which case you should use that id in the join.
The rest of your join appear to be fine. The problem is that you are projecting out the results of the join in the Select method. You should remove it, and use the anonymous class to get the data that you need from the joined category, like this:
var query = _videoRepository.Table.Join(
_videoCategoryRepository.Table
, v => v.CategoryId // <<== Use the proper ID here
, vc => vc.Id
, (v, vc) => new { Videos = v, VideoCategories = vc } // <<== This part is fine
);
foreach (var v in query) {
Console.WriteLine("Video: '{0}'
, category '{1}'"
, v.Videos.Name
, v.VideoCategories.Name
);
}
var query =
(from v in _videoRepository
from c in _videoCategoryRepository
where c.Id == v.Id
select new { id = v.id, catname = c.CategoryName, vname = v.Title }
).ToList()
Related
I'm currently using Entity Framework for my db access but want to have a look at Dapper. I have classes like this:
public class Course{
public string Title{get;set;}
public IList<Location> Locations {get;set;}
...
}
public class Location{
public string Name {get;set;}
...
}
So one course can be taught at several locations. Entity Framework does the mapping for me so my Course object is populated with a list of locations. How would I go about this with Dapper, is it even possible or do I have to do it in several query steps?
Alternatively, you can use one query with a lookup:
var lookup = new Dictionary<int, Course>();
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course))
lookup.Add(c.Id, course = c);
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(l); /* Add locations to course */
return course;
}).AsQueryable();
var resultList = lookup.Values;
See here https://www.tritac.com/blog/dappernet-by-example/
Dapper is not a full blown ORM it does not handle magic generation of queries and such.
For your particular example the following would probably work:
Grab the courses:
var courses = cnn.Query<Course>("select * from Courses where Category = 1 Order by CreationDate");
Grab the relevant mapping:
var mappings = cnn.Query<CourseLocation>(
"select * from CourseLocations where CourseId in #Ids",
new {Ids = courses.Select(c => c.Id).Distinct()});
Grab the relevant locations
var locations = cnn.Query<Location>(
"select * from Locations where Id in #Ids",
new {Ids = mappings.Select(m => m.LocationId).Distinct()}
);
Map it all up
Leaving this to the reader, you create a few maps and iterate through your courses populating with the locations.
Caveat the in trick will work if you have less than 2100 lookups (Sql Server), if you have more you probably want to amend the query to select * from CourseLocations where CourseId in (select Id from Courses ... ) if that is the case you may as well yank all the results in one go using QueryMultiple
No need for lookup Dictionary
var coursesWithLocations =
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (course, location) => {
course.Locations = course.Locations ?? new List<Location>();
course.Locations.Add(location);
return course;
}).AsQueryable();
I know I'm really late to this, but there is another option. You can use QueryMultiple here. Something like this:
var results = cnn.QueryMultiple(#"
SELECT *
FROM Courses
WHERE Category = 1
ORDER BY CreationDate
;
SELECT A.*
,B.CourseId
FROM Locations A
INNER JOIN CourseLocations B
ON A.LocationId = B.LocationId
INNER JOIN Course C
ON B.CourseId = B.CourseId
AND C.Category = 1
");
var courses = results.Read<Course>();
var locations = results.Read<Location>(); //(Location will have that extra CourseId on it for the next part)
foreach (var course in courses) {
course.Locations = locations.Where(a => a.CourseId == course.CourseId).ToList();
}
Sorry to be late to the party (like always). For me, it's easier to use a Dictionary, like Jeroen K did, in terms of performance and readability. Also, to avoid header multiplication across locations, I use Distinct() to remove potential dups:
string query = #"SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id";
using (SqlConnection conn = DB.getConnection())
{
conn.Open();
var courseDictionary = new Dictionary<Guid, Course>();
var list = conn.Query<Course, Location, Course>(
query,
(course, location) =>
{
if (!courseDictionary.TryGetValue(course.Id, out Course courseEntry))
{
courseEntry = course;
courseEntry.Locations = courseEntry.Locations ?? new List<Location>();
courseDictionary.Add(courseEntry.Id, courseEntry);
}
courseEntry.Locations.Add(location);
return courseEntry;
},
splitOn: "Id")
.Distinct()
.ToList();
return list;
}
Something is missing. If you do not specify each field from Locations in the SQL query, the object Location cannot be filled. Take a look:
var lookup = new Dictionary<int, Course>()
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.Name, l.otherField, l.secondField
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course)) {
lookup.Add(c.Id, course = c);
}
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(a);
return course;
},
).AsQueryable();
var resultList = lookup.Values;
Using l.* in the query, I had the list of locations but without data.
Not sure if anybody needs it, but I have dynamic version of it without Model for quick & flexible coding.
var lookup = new Dictionary<int, dynamic>();
conn.Query<dynamic, dynamic, dynamic>(#"
SELECT A.*, B.*
FROM Client A
INNER JOIN Instance B ON A.ClientID = B.ClientID
", (A, B) => {
// If dict has no key, allocate new obj
// with another level of array
if (!lookup.ContainsKey(A.ClientID)) {
lookup[A.ClientID] = new {
ClientID = A.ClientID,
ClientName = A.Name,
Instances = new List<dynamic>()
};
}
// Add each instance
lookup[A.ClientID].Instances.Add(new {
InstanceName = B.Name,
BaseURL = B.BaseURL,
WebAppPath = B.WebAppPath
});
return lookup[A.ClientID];
}, splitOn: "ClientID,InstanceID").AsQueryable();
var resultList = lookup.Values;
return resultList;
There is another approach using the JSON result. Even though the accepted answer and others are well explained, I just thought about an another approach to get the result.
Create a stored procedure or a select qry to return the result in json format. then Deserialize the the result object to required class format. please go through the sample code.
using (var db = connection.OpenConnection())
{
var results = await db.QueryAsync("your_sp_name",..);
var result = results.FirstOrDefault();
string Json = result?.your_result_json_row;
if (!string.IsNullOrEmpty(Json))
{
List<Course> Courses= JsonConvert.DeserializeObject<List<Course>>(Json);
}
//map to your custom class and dto then return the result
}
This is an another thought process. Please review the same.
Sorry, if it is a really stupid question, but I cannot understand why person.Cars property is not populated. :
var persons = db.Persons.AsNoTracking()
.Select(person => new PersonDto
{
ID = person.ID,
Name = person.SP_Status.Name
});
//The following code where person.Cars is not populated until I write persons.ToList()
foreach (var person in persons)
{
if (person.Name != "Adam")
{
person.Cars = (from ca in db.Cars
where ca.ID == person.ID
select new CarDTO
{
ID = ca.ID,
CarNumber = ca.DocNumber,
}).Distinct()
}
else
{
person.Cars = (from car in db.AnotherCars
where car.ID == person.ID
select new CarDTO
{
ID = car.ID,
CarNumber = car.DocNumber,
}).Distinct()
}
}
If I materialize persons by persons.ToList() and then executing a populating person.Cars, then it works perfectly. But I have thousand objects in memory.
//this code works perfectly
persons.ToList();
foreach (var person in persons)
{
if (person.Name != "Adam")
{
person.Cars = (from ca in db.Cars
where ca.ID == person.ID
select new CarDTO
{
ID = ca.ID,
CarNumber = ca.DocNumber,
}).Distinct()
}
else
{
person.Cars = (from car in db.AnotherCars
where car.ID == person.ID
select new CarDTO
{
ID = car.ID,
CarNumber = car.DocNumber,
}).Distinct()
}
}
What I missed? Be very kind to explain and do not close this question, it is really important to me. Thanks in advance.
What I want to do is to fill person.Cars based on condition person.Name != "Adam" from tables db.Cars or db.AnotherCars.
Is it possible to rewrite this query without materializing (calling .ToList()) data in memory?
Edited. I have edited an answer because there was error in it.
After reading all comments, I decide that this problem could be solved by UNION ALL two left joins:
table Persons(with additional filter person.Name != "Adam") left join table Cars
table Persons(with additional filter person.Name == "Adam") left join table AnotherCars.
The result rows will be with columns:
PersonID
PersonName (I'm getting PersonName, if you need another, then change the selections)
CarID
CarNumber.
Here is the code for this query (I'm using another ORM. But it should work in EF, I guess):
// Getting persons.
var persons = db.Persons
.Where(p => p.ID <= 10) // any of your filtering condition on persons
.Select(p => new { p.ID, p.Name });
// Left join with 'Cars' table
var leftJoin1 = from p in persons.Where(p => p.Name != "Adam")
join c in db.Cars on p.ID equals c.PersonID into j
from c in j.Distinct().DefaultIfEmpty() // do you really need Distinc here?
select new
{
PersonID = p.ID,
PersonName = p.Name,
CarID = c.ID,
CarNumber = c.DocNumber
};
// Left join with 'AnotherCars' table
var leftJoin2 = from p in persons.Where(p => p.Name == "Adam")
join ac in db.AnotherCars on p.ID equals ac.PersonID into j
from ac in j.Distinct().DefaultIfEmpty() // do you really need Distinc here?
select new
{
PersonID = p.ID,
PersonName = p.Name,
CarID = ac.ID,
CarNumber = ac.DocNumber
};
// Resul query
var result = leftJoin1.Concat(leftJoin2)
.OrderBy(r => r.PersonID)
.ThenBy(r => r.CarID)
.ToList();
If it is sufficient for you to deal with with 4 properies (PersonID, PersonName, CarID, CarNumber), you need class for it and use it in left joins (instead of select new {} use select new YourNewDTO {}).
If you really need your DTO's then continue.
var personsWithCars = result.GroupBy(p => new { p.PersonID, p.PersonName })
.Select(g => new PersonDTO
{
ID = g.Key.PersonID,
Name = g.Key.PersonName,
Cars = result.Where(r => r.PersonID == g.Key.PersonID)
.Select(r => new CarDTO { ID = r.CarID, CarNumber = r.CarNumber })
.ToList()
});
imho, this is a necessary information:
By Ivan Stoev:
persons is a query. When you foreach it, it's executed, then you
do something with each element, but since you are not storing the
result, you basically do nothing. The next time you enumerate/tolist
etc. the persons, the query will execute again and give you brand new
objects. However, if you remove AsNoTracking, you may get the same
objects.
IQueryable<T> is not a storage like List<T> etc. memory
collections. Even IEnumerable<T> isn't. e.g. var q =
Enumerable.Range(1, 10).Select(i => new SomeObject { Id = i });
will create new objects anytime you enumerate it.
Q: So it means foreach statement is never executed until I call ToList() method?
Answer by NetMage: No, it means that each person object only exists inside the foreach for one iteration. They are all lost at the end of the foreach.
If you don't run tolist() your query will not be executed until you actually request data because of delayed/deferred execution in LINQ queries. For the most part you just have an expression tree not actual objects. To switch to actual objects you have to call tolist(). In essence you are just adding to the query and not actually requesting the data.
I have two tables, Table1 and Table2. Table1 has 1 to many relationship with Table2.
Table1 has ListId, ListName
Table2 has FirstName, LastName, Phone, ListId, CustomField1, CustomField2
What I want to retrieve is All rows from Table1 but only retrieve CustomField2 value of Table2 for only 1st row.
The existing query I have below is retrieving me all rows from Table2 which I do not want.
var result = _db.Lists
.Join(_db.ListUsers, c => c.ListID, d => d.ListID, (c, d) => new { c, d });
My final resultset need to look like this
Table1.ListId, Table1.ListName, Table2.CustomField2
1, first list, "abc"
2, second list, "def"
This should do the trick, get only the first record on the right table:
from i in _db.Lists
let p = _db.ListUsers.Where(p2 => i.ListID == p2.ListID).FirstOrDefault()
select new
{
ListID = i.ListID,
ListName = i.ListName,
CustomField2 = p.CustomField2
}
With lambda expression, it would be:
_db.Lists
.Select (
i =>
new
{
i = i,
p = _db.ListUsers
.Where (p2 => i.ListID == p2.ListID))
.Take(1)
.FirstOrDefault()
})
.Select (
results =>
new
{
ListID= results.i.ListID,
ListName = results.i.ListName,
CustomField2 = results.p.CustomField2
}
)
From what I read, the result you want to achieve is retrieve a list of results contains All columns from Table1 & 1 column from Table2 (Not rows).
There are few ways to do this. I would use create a DTO class to retrieve my results. And in the select , you would need to specifically list out the item in your select result.
For example,
Create a DTO class
public class DTOListResult
{
public string XXX {get; set;}
...
}
Then in your result, you can write in this way.
var result = (from a in _db.Lists select new DTOListResult { XXX = _db.table2.ID,
xxx = a.ID,
XXX = a.XX});
I've been looking at other threads here to learn how to do a GroupBy in linq. I am following the EXACT syntax that has worked for others, but, it's not working.
Here's the query:
var results = from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.ContactID, // <--- Error here
Amount = amount
};
pending is a List<T> that contains, among other columns, ContactID and Amount, but those are the only two I care about for this query.
The trouble is, inside the the select new, the g. won't give me any of the columns inside the original list, pending. And when I try, I get:
IGrouping <int, LeadPurchases> does not contain a definition for ContactID, and no extension method blah blah blah...
This is the SQL I am trying to emulate:
SELECT
lp.PurchasedFromContactID,
SUM (lp.Amount)
FROM
LeadPurchases lp
GROUP BY
lp.PurchasedFromContactID
You are grouping on the basis of ContactID, so it should be the Key for the result, So you have to use g.Key instead of g.ContactID; Which means the query should be like the following:
from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.Key,
Amount = amount
};
updates :
If you want to perform grouping based on more than one column then the GroupBy clause will be like this:
group p by new
{
p.ContactID,
p.Field2,
p.Field3
}into g
select new PaymentItemModel()
{
ContactID = g.Key.ContactID,
anotherField = g.Key.Field2,
nextField = g.Key.Field3
};
I am trying to write SQL query in LINQ to SQL but after 4h of trying i gave up.
select B.* from Bid B
join
(
select BidId, max(BidVersion) as maxVersion
from Bid
group by BidId
) X on B.BidId = X.BidId and B.BidVersion = X.maxVersion
i saw some tips on the stackOverflow but they werent helpful.
I am using some VERY bad code like:
List<Bid> bidEntities = new List<Bid>();
var newest = from bid in _dbContext.Bids
group bid by bid.BidId into groups
select new { Id = groups.Key, Vs = groups.Max(b => b.BidVersion) };
foreach (var group in newest)
{
bidEntities.Add(await _dbContext.Bids.Where(b => b.BidId == group.Id && b.BidVersion == group.Vs).SingleOrDefaultAsync());
}
Thank you for any advice.
Something like this should work:
var rows = Bids
.GroupBy(b => b.BidId)
.Select(g => g.OrderByDescending(gg => gg.BidVersion).FirstOrDefault())
With LINQ, it helps sometimes not to think in the 'SQL way', but about what you really want. You want the highest version bid for each BidId