EF 6 select from other table without navigation property - c#

I have a small problem which I need a help to solve:
I have following situation:
For example: I want to select all students who have a dog.
I have 2 tables:
students
id name petid
pet
id name
BUT there is no specified foreign key between them no navigation property, although I have but I haven't specified it and I don't want for my case, but I still want to make a correct select statement.
So with navigation property i could query like this:
var students = (student s in context.students where s.Pet.Name.Equals("dog").ToList();
I would avoid doing this also
var students = context.students
foreach(student s in students)
{
string pet = (from pet p in context.pets where p.Id==s.PetId select p.name).SingleOrDefault();
if(pet=="dog")
{
//do something
}
}
Of course it would be easy to make navigation property, but for my case I really don't want to.
So my question is how can i do this kind of query simple and with only one to DB?

Use a join.
var students = (from s in context.students
join p in context.pets on s.petid equals p.id
where p.name == "dog"
select s).ToList();
For the lambda syntax, you can use this:
var students = context.students.Join(context.pets.Where(p => p.name== "dog"), //filter the pets
student => student.PetId, //left side key for the join
pet => pet.id, //right side key for the join
(student, pet) => student); //what do you want to select

Related

How do I select from a link table, based on a value from another link table

Let say we have the tables
Student
Id,
Name
ClassRoom
Id,
Room Number
StudentClassRoom
Id
StudentId
ClassRoomId
Homework
Id
Name
StudentHomework
Id
StudentId
HomeworkId
CreatedAt
I want to find all the StudentHomework records for all the students I am in a class with.
For example, If there are 4 classes, A, B, C, D. If I am in classes A, B, C, then I want to be able to find all the StudentHomework records where the Student is in the same StudentClassRoom as me.
I have tried
return Context.StudentHomeworks
.Where(e => e.Student.StudentClassRooms.Select(b => b.Student.Id).Contains(e.Student.Id))
.OrderByDescending(b => b.CreatedAt)
and it's not giving me the results I expect. Can anybody help me with the query that I need? Thanks
I would do something like that :
var myId = 1111111;
// first select all ids of student in your class
var studentInMyClass = Context.StudentClassRooms
.Where(a => a.StudentId == myId).Select(a => a.StudentId).ToList();
// then select homeworks of those students
return studentHomeworks
.Where(b => studentIdsInMyClass.Contains(b.StudentId))
.OrderByDescending(b => b.CreatedAt);

Linq using join and grouping

I'm trying to do something very simple.
I have two tables in my database that I would like to query using linq.
Table of Books, and table of GenreTypes. The result of this query would go to my web Api.
Here is a code snippet:
public List<BooksChart> GetBooksChart()
{
var results = from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select (z => new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
return results;
}
public class BooksChart
{
public string category_name;
public int value;
}
The results of the grouping "n" I would like to store them in BooksChart class to construct the Api.
This code is not compiling.
Previously, I was querying only one table of Books which I have divided into Books and GenreTypes.
My previous working code for querying Books was :
var results = _dbcontext
.Books
.GroupBy(x => x.GenreType)
.Select(z => new BooksPieChart
{
category_name = z.Key,
value = z.Count()
}).ToList();
return results;
EDIT
What I want to achieve in SQL is the following:
select count(*), g.Name
from books b, GenreTypes g
where b.GenreTypeId = g.Id
group by g.Name;
You are mixing the two syntax options of query and method. For query syntax you need to do the projection (select) like this:
return (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart {
category_name = n.Key,
value = n.Count()
}).ToList();
The format of (z =>....) is the declaration of the labmda passed to the Select method.
Site notes:
As #Rabbi commented, since you are using EF, consider properly defining navigation properties. It will make querying simpler.
Side note for the sql - consider using joins instead of multiple tables in the from: INNER JOIN ON vs WHERE clause
The parentheses must surround the whole query, like so:
var results = (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
The compilation error is due to this (z => which is not needed at all.

Entity Framework filter nested collection

I have a entity relation diagram as follows.
ClassEntity:
public int id
public int std
public virtual ICollection<StudentEntity> students
StudentEntity:
public int id
public string name
public string gender
public virtual ClassEntity class
public virtual StudentAddressEntity studentAddress
StudentAddressEntity:
public int id
public string address
I need to get the class and its male children.
var classEntity = dbContext.Set<ClassEntity>().Where(t => t.id == classId);
var query = classEntity.Include(c => c.students.Select(s => s.studentAddress))
.FirstOrDefault(c => c.students.Any(s => s.gender == GenderEnum.Male));
But it is returning the class with all the students. How to filter only male students?
I have used joins to accomplish similar results in the past. For eg I've accounts that have addresses nested (1:M). If I want to get, say, all the accounts that belong to a particular country, I would use joins as below:
(from a in accountRepo.GetAll()
join aa in accountAddressRepo.GetAll() on a.AccountId equals aa.AccountId
join ad in addressRepo.GetAll() on aa.AddressId equals ad.AddressId
where ad.CountryId == codeCountryId
select a).ToList();
If you are not using repository pattern you can simply replace accountRepo.GetAll() with DbContext.Set().
In your case you should be able to join Student, Address and Class entities and get similar results. Something like below should work for you:
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();
please note this is a simple representation based on my understanding of your database and entity names. You may need to tweak this query a bit to make it compilable but the underlying idea should work for you. Please let me know how did it work for you.
You intentionally "can't" do this directly with the EF proxies. For example, consider what would happen when you tried to call SaveChanges() and all of the female students are missing from ClassEntity.Students!
Instead, the usual thing to do if you're just displaying data is to project onto an anonymous type or a DTO, e.g.:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new // I'm using an anonymous type here, but you can (and usually should!) project onto a DTO instead
{
// It's usually best to only get the data you actually need!
Id = x.Id
Students = x.Students
.Where(y => y.Gender == GenderEnum.Male)
.Select(y => new { Name = y.Name, ... })
});
Or, if you desperately need to make changes and save them:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new
{
Class = x,
MaleStudents = x.Students.Where(y => y.Gender == GenderEnum.Male)
});
I quite strongly recommend the former unless there's no way around it. It's really easy to introduce bugs if you're making changes to filtered data and trying to save it.
The below should load only the male students for each class.
var classEntity = testContext.Set<ClassEntity>().Where(t => t.Id == classId);
var classes = classEntity.ToList().Select(c =>
{
testContext.Entry(c)
.Collection(p => p.Students)
.Query()
.Where(s => s.Gender == GenderEnum.Male)
.Load();
return c;
});
I think join is the right way to go about it as suggested by Manish Kumar.
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();

linq query one to one

trying to get the concert name, date and venue (which is stored on another table)
but it doesn't seem to be working, but it keeps pulling back null.
var test = from f in db.Concert
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
venues = from v in db.Venues
where v.ID == f.VenueID
select v.Address
}
Edit:
The relationship between Venue and Concert is that Concert has a VenueID that relates to the Venue ID. I need to pass a string back. something like
foreach (var e in test)
{
html += "<div> e.Name +":" + e.Address </div>;
}
You can use group join to get all Venues related to Concert
var test = from f in db.Concert
join v in db.Venues on f.VenueID equals v.ID into g
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
Venues = g.Select(x => x.Address)
};
Usage of results:
foreach (var e in test)
{
// e.Name
// e.StartDateTime
foreach(var address in e.Venues)
// address
}
It looks like it's safe to assume a 1:1 relationship between Concerts and Venues. Given that, you can use join instead.
var test = from f in db.Concert
join v in db.Venues on v.ID equals f.VenueID
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
v.Address
};
If you have a foreign key relationship set up, You don't need to manually query for venues. In that case you get the venues using f.Venue.

Help with QueryOver and WhereExists

I have a problem. I have Persons and Cats. Each Person has some Cats (there is a foreign key in Cats that points to the primary key in Persons). Each Cat has an Age. I want to select the Persons that have "Old" Cats. I want ALL the Cats of these persons, and not only the "Old" Cats.
I need to do it with the QueryOver syntax.
In T-SQL it would be something like:
SELECT P.*, C.*
FROM Persons P
LEFT JOIN Cats C
ON P.Id = C.OwnerId
WHERE EXISTS (
SELECT 1
FROM Cats C2
WHERE P.Id = C2.OwnerId AND C2.Age > 5)
I know I have to use the subqueries, and I could to easily with the "old" nhibernate syntax (the Criteria/DetachedCriteria), but I can't do it in QueryOver syntax.
I DON'T want an "IN" condition. My Primary Key is a complex key, so I can't do it with the IN.
var persons = session.QueryOver<Person>.WithSubquery.WhereExists( ??? );
Example taken from this page and adapted (tested with my own classes):
The trick seems to be using an alias.
Person personAlias = null;
IList<Person> persons =
session.QueryOver<Person>(() => personAlias).WithSubquery
.WhereExists(QueryOver.Of<Cat>()
.Where(c => c.Age > 5)
.And(c => c.Owner.Id == personAlias.Id)
.Select(c => c.Owner))
.List<Person>();

Categories