Entity Framework include with left join is this possible? - c#

I have the following tables
ClassRoom (ClassID,ClassName)
StudentClass (StudentID,ClassID)
Student (StudentID,StudentName,Etc..)
StudentDescription. (StudentDescriptionID,StudentID,StudentDescription)
I want to retrieve all the information on student==1
In sql I would do something like BELOW and get all the info about a student.
select * from Student s
join StudentClass sc on s.StudentID=sc.StudentID
join ClassRoom c on sc.ClassID=c.ClassID
left join StudentDescription sd on s.StudentID=sd.StudentID
where s.StudentID=14
Now my problem.Using EF4 I did something like this but cannot make it work.
Also can you do an include and a left join
Attempt 1
private static StudentDto LoadStudent(int studentId)
{
StudentDto studentDto = null;
using (var ctx = new TrainingContext())
{
var query = ctx.Students
.Include("ClassRooms")
.Include("StudentDescriptions")
.Where(x=>x.StudentID==studentId)
.SingleOrDefault();
studentDto = new StudentDto();
studentDto.StudentId = query.StudentID;
studentDto.StudentName = query.StudentName;
studentDto.StudentDescription = ??
}
return studentDto;
}
Attempt 2 again incomplete and wrong
using (var ctx = new TrainingContext())
{
var query = (from s in ctx.Students
.Include("ClassRooms")
join sd in ctx.StudentDescriptions on s.StudentID equals sd.StudentID into g
from stuDesc in g.DefaultIfEmpty()
select new
{
Name=s.StudentName,
StudentId=s.StudentID,
}).SingleOrDefault();
As you can see I dont know what I am doing here.
How can I convert that Sql into a EF Query?

Yes, it is possible.
Firstly, .Include does a LEFT OUTER JOIN, using the navigational property you pass through.
This is how you would explicitly do a LEFT JOIN between Student and StudentDescription:
var query = from s in ctx.Students
from sd in s.StudentDescriptions.DefaultIfEmpty()
select new { StudentName = s.Name, StudentDescription = sd.Description };
As you can see, it's performing the JOIN based on the entity association between Students and StudentDescriptions. In your EF model, you should have a navigational property called StudentDescriptions on your Student entity. The above code is simply using that to perform the join, and defaulting if empty.
The code is basically identical to .Include.
Please don't get confused with LEFT JOIN vs LEFT OUTER JOIN.
They are the same thing.
The "OUTER" keyword is optional, i believe it is there for ANSI-92 compatability.
Just .Include everything you need in your query:
using (var ctx = new TrainingContext())
{
studentDo = ctx.Students
.Include("ClassRooms")
.Include("StudentDescriptions")
.Where(x=>x.StudentID==studentId)
.Select(x => new StudentDto
{
StudentId = x.StudentId,
StudentName = x.StudentName
StudentDescription = x.StudentDescription.Description
})
.SingleOrDefault();
}
Basically, make sure all your FK's are expressed as navigational properties on your model, then if so, you don't need to do any joins. Any relationships you require can be done with .Include.

I just had this problem, in my case it was the EntityTypeConfiguration that was wrong
I had:
HasRequired(s => s.ClassRoom)
.WithMany()
.HasForeignKey(student => student.ClassRoomId);
Instead of:
HasOptional(s => s.ClassRoom)
.WithMany()
.HasForeignKey(student => student.ClassRoomId);
It seems HasRequired makes a INNER JOIN while HasOptional makes a LEFT JOIN.

Exactly:
If StudentDescription.StudentId is nullable -> EF performs a LEFT JOIN, i.e. select * from Student s LEFT JOIN StudentDescription sd on s.StudentID=sd.StudentID.
Otherwise EF does INNER JOIN.

The behavior of .Include:
The property is requeired: Always translates to INNER JOIN;
The type of foreign key is not nullable: translates to INNER JOIN in default, but you can add .IsRequired(false) with .HasForeignKey to turn it to be LEFT OUT JOIN.
The type of property is collection will always translates to LEFT OUT JOIN.

If you want a 1 to 1 relation, you can simply map your foreign Id and make it nullable.
public int? MyForeignClassId { get; set; }
public MyForeignClass MyForeignClass { get; set; }

Related

How to make EF efficiently call an aggregate function?

I'm trying to write a LINQ-to-entities query that will take an ICollection navigation property of my main object and attach some metadata to each of them which is determined through joining each of them to another DB table and using an aggregate function. So the main object is like this:
public class Plan
{
...
public virtual ICollection<Room> Rooms { get; set; }
}
And my query is this:
var roomData = (
from rm in plan.Rooms
join conf in context.Conferences on rm.Id equals conf.RoomId into cjConf
select new {
RoomId = rm.Id,
LastUsedDate = cjConf.Count() == 0 ? (DateTime?)null : cjConf.Max(conf => conf.EndTime)
}
).ToList();
What I want is for it to generate some efficient SQL that uses the aggregate function MAX to calculate the LastUsedDate, like this:
SELECT
rm.Id, MAX(conf.EndTime) AS LastUsedDate
FROM
Room rm
LEFT OUTER JOIN
Conference conf ON rm.Id = conf.RoomId
WHERE
rm.Id IN ('a967c9ce-5608-40d0-a586-e3297135d847', '2dd6a82d-3e76-4441-9a40-133663343d2b', 'bb302bdb-6db6-4470-a24c-f1546d3e6191')
GROUP BY
rm.id
But when I profile SQL Server it shows this query from EF:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[RoomId] AS [RoomId],
[Extent1].[ProviderId] AS [ProviderId],
[Extent1].[StartTime] AS [StartTime],
[Extent1].[EndTime] AS [EndTime],
[Extent1].[Duration] AS [Duration],
[Extent1].[ParticipantCount] AS [ParticipantCount],
[Extent1].[Name] AS [Name],
[Extent1].[ServiceType] AS [ServiceType],
[Extent1].[Tag] AS [Tag],
[Extent1].[InstantMessageCount] AS [InstantMessageCount]
FROM [dbo].[Conference] AS [Extent1]
So it is selecting everything from Conference and doing the Max() calculation in memory, which is very inefficient. How can I get EF to generate the proper SQL query with the aggregate function in?
The equivalent LINQ to Entities query which closely translates to the SQL query you are after is like this:
var roomIds = plan.Rooms.Select(rm => rm.Id);
var query =
from rm in context.Rooms
join conf in context.Conferences on rm.Id equals conf.RoomId
into rmConf from rm in rmConf.DefaultIfEmpty() // left join
where roomIds.Contains(rm.Id)
group conf by rm.Id into g
select new
{
RoomId = g.Key,
LastUsedDate = g.Max(conf => (DateTime?)conf.EndTime)
};
The trick is to start the query from EF IQueryable, thus allowing it to be fully translated to SQL, rather than from plan.Rooms as in the query in question which is IEnumerable and makes the whole query execute in memory (context.Conferences is treated as IEnumerable and causes loading the whole table in memory).
The SQL IN clause is achieved by in memory IEnumerable<Guid> and Contains method.
Finally, there is no need to check the count. SQL naturally handles nulls, all you need is to make sure to call the nullable Max overload, which is achieved with the (DateTime?)conf.EndTime cast. There is no need to check conf for null as in LINQ to Objects because LINQ to Entities/SQL handles that naturally as well (as soon the receiver variable is nullable).
Since plan.Rooms isn't IQueryable with a query provider attached, the join statement is compiled as Enumarable.Join. This means that context.Conferences is implicitly cast to IEumerable and its content is pulled into memory before other operators are applied to it.
You can fix this by not using join:
var roomIds = plan.Rooms.Select(r => r.Id).ToList();
var maxPerRoom = context.Conferences
.Where(conf => roomIds.Contains(conf.RoomId))
.GroupBy(conf => conf.RoomId)
.Select(g => new
{
RoomId = g.Key,
LastUsedDate = g.Select(conf => conf.EndTime)
.DefaultIfEmpty()
.Max()
}
).ToList();
var roomData = (
from rm in plan.Rooms
join mx in maxPerRoom on rm.Id equals mx.RoomId
select new
{
RoomId = rm.Id,
LastUsedDate = mx.LastUsedDate
}
).ToList();
This first step collects the LastUsedDate data from the context and then joins with the plan.Rooms collection in memory. This last step isn't even necessary if you're not interested in returning/displaying anything else than the room's Id, but that's up to you.

How to express LEFT JOIN when 2 entities are linked by a navigation properties

Let say I have these 2 Entities
public class Product
{
//..
public ICollection<Photo> Photos { get; set; }
= new List<Photo>();
}
public class Photo
{
//..
public PageLocation PageLocation { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
I use this code to select a list of products. Each of them will associated with an image given the location where the product will be displayed.
var productList = GetAll().Include(p => p.PhotoImages)
.Where(p =>
p.PhotoImages.Any(
i => i.PageLocation == PageLocation.Home_Slider));
The problem is that when a product has no image for that location, then the product is not selected.
Every examples that I've seen on left join show how to use 2 sources. In my case, I don't need 2 sources because I've a navigation property between Product and Image.
Is there a way to accomplish a left join without having to use 2 sources data? Something like this
SELECT *
FROM [dbo].[Products] p
LEFT JOIN
(
SELECT *
FROM [dbo].[PhotoImages]
WHERE PageLocation = 1
)img
ON p.Id = img.ProductId
Thanks for helping
Well, if you use the navigation property EF will generate a SQL statement that has an inner join though.
How Navigation property works
When we apply navigation property in our code, it means we are asking EF to automatically perform a join between the two tables.
source: Navigation Property With Code First (Navigation Property In EF)
So you could use the example you already found on MS docs - Perform left outer joins - to realize your query. Your SQL example has two sources too. First Products table and as second the PhotoImages table or I misunderstood what you mean.
An approach for your query could be:
var products = (
from p in _context.Products
join pi in _context.PhotoImages on p.Id equals pi.ProductId
into productsWithImages
from pwi in productsWithImages.Where(pwi => pwi.PageLocation == 1).DefaultIfEmpty()
select new ExtendedProduct
{
p.Id,
p.Name,
photoId = (int?)pwi.Id,
pwi.PageLocation,
pwi.Url
}
)
Is there a way to accomplish a left join without having to use 2 sources data?
I don't think there is. In your SQL query you are joining with a new set, not the photos related to your products.
I believe that the best you can do is something like this:
var q = from prod in ctx.Products.Include(p => p.Photos)
join img in (
from ph in ctx.Photos where ph.PageLocation == 1 select new { ph.ProductId, PageLocation = (int?)ph.PageLocation }
) on prod.Id equals img.ProductId into pjoin
from pj in pjoin.DefaultIfEmpty()
select new { prod, photo = pj, pj.PageLocation };
foreach (var p in q)
{
Console.WriteLine($"{p.prod.Id} - {p.photo} - {p.PageLocation}");
}
EDIT: There is only one join in this query, as can be seen from the generated SQL:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent2].[PageLocation] AS [PageLocation],
[Extent2].[ProductId] AS [ProductId]
FROM [dbo].[Product] AS [Extent1]
LEFT OUTER JOIN [dbo].[Photo] AS [Extent2] ON (1 = [Extent2].[PageLocation]) AND ([Extent1].[Id] = [Extent2].[ProductId])

Query in LINQ with self join

I have a table which contains columns among others like Id as Primary Key and LastMeterReadingId (Foreign Key which references to same table) - something like Parent Meter Reading.
I would like to get all rows which are not already used like Parent. I would like to avoid situation when meter reading is parent for more than one meter reading.
I know how to join to same table, but I have no idea how to choose only those records which aren't parent already. That's how query looks like without condition statement.
return (from m in uow.MeterReadingReadWriteRepository.Query()
join parent in uow.MeterReadingReadWriteRepository.Query() on m.Id equals parent.LastMeterReadingId
select new MeterReadingDto()
{
(...)
}).ToList();
Do you have any idea how to achieve it in efficient way?
Regards.
I would like to get all rows which are not already used like Parent
In other words, you want all rows that have no children. Note that the variable name parent in your query is misleading - when you do a join b on a.Id equals b.ParentId, a is the parent and b is the child.
Anyway, there are at least 3 ways to achieve your goal, IMO being equivalent from nowadays database query optimizers point of view (i.e. should be equally efficient):
(1) Using !Any(...) which is equivalent to SQL NOT EXISTS(...):
from m in uow.MeterReadingReadWriteRepository.Query()
where !uow.MeterReadingReadWriteRepository.Query().Any(child => m.Id == child.LastMeterReadingId)
select ...
(2) Using group join:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
where !children.Any()
select ...
(3) Using left outer antijoin:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
from child in children.DefaultIfEmpty()
where child == null
select ...
If this is EF (LINQ to Entities), the first two are translated to one and the same SQL NOT EXISTS based query. While the last is translated to the "traditional" SQL LEFT JOIN ... WHERE right.PK IS NULL based query.
You could just add
where !(from child in uow.MeterReadingReadWriteRepository.Query() where child.Id == m.LastMeterReadingId select child).Any()
Not sure how intelligently this would be optimised though. It would also be better to factor out uow.MeterReadingReadWriteRepository.Query().
Do you not have a Child relationship/collection in your Meter Reading entity from the foreign key constraint? - this would make the query much more straightforward.
var readings = uow.MeterReadingReadWriteRepository.Query();
var parents = readings
.Join(readings, child => child.Id, parent => parent.LastMeterReadingId,
(child, parent) => new {parent.Id})
.Distinct()
.ToDictionary(a => a.Id);
var result = (from m in readings
where !parents.Contains(m.Id)
select new
{
Id = m.Id
}).ToList();
Thanks #Ben Jackson
public class MeterReading : EntityBase
{
public long PropertyId { get; set; }
public long? LastMeterReadingId { get; set; }
public long? PaymentId { get; set; }
public Property Property { get; set; }
public MeterReading LastReading { get; set; }
public Payment Payment { get; set; }
}
That's how most value properties looks like. Maybe should I use T-SQL query with JOIN to CTE which mentioned before condition statement? I'll try your solution ASAP.

Dynamic Join of multiple entities based on some filter in Entity Framework

I am pretty new to Entity Framework and LINQ and I have an entity with more than 10+ other associated entities (one-to-many relationships). Now, I'm planning to make a search page in my application in which users could select which fields (i.e. those 10+ tables) they want to be considered when searching.
Now, I'm trying to write a query to achieve the above goal. Any help how I could sort this out using LINQ method syntax? I mean, to write a multiple join query based on user's choice. (i.e. which of Class1, Class2, ... to join with main Entity to finally have all the related fields in one place). Below is a sample code (Just a hunch, in fact)
if(somefilter#1)
result = db.Companies.Join(db.Channels, p => p.Id, k => k.CId,
(p, k) => new {Company = p, Channels=k});
if(somefilter#2)
result = result.Join(db.BusinnessType, ........);
if(somefilter#3)
result = result.Join(db.Values, .......);
For complex queries it may be easier to use the other LINQ notation. You could join multiple entities like this:
from myEntity in dbContext.MyEntities
join myOtherEntity in dbContext.MyOtherEntities on myEntity.Id equals myOtherEntity.MyEntityId
join oneMoreEntity in dbContext.OneMoreEntities on myEntity.Id equals oneMoreEntity.MyEntityId
select new {
myEntity.Id,
myEntity.Name,
myOtherEntity.OtherProperty,
oneMoreEntity.OneMoreProperty
}
You can join in other entities by adding more join statements.
You can select properties of any entity from your query. The example I provided uses a dynamic class, but you can also define a class (like MyJoinedEntity) into which you can select instead. To do it you would use something like:
...
select new MyJoinedEntity {
Id = myEntity.Id,
Name = myEntity.Name,
OtherProperty = myOtherEntity.OtherProperty,
OneMoreProperty = oneMoreEntity.OneMoreProperty
}
EDIT:
In case when you want to have conditional joins you can define MyJoinedEntity with all the properties you will need if you were to join everything. Then break up the join into multiple methods. Like this:
public IEnumerable<MyJoinedEntity> GetEntities() {
var joinedEntities = from myEntity in dbContext.MyEntities
join myOtherEntity in dbContext.MyOtherEntities on myEntity.Id equals myOtherEntity.MyEntityId
join oneMoreEntity in dbContext.OneMoreEntities on myEntity.Id equals oneMoreEntity.MyEntityId
select new MyJoinedEntity {
Id = myEntity.Id,
Name = myEntity.Name,
OtherProperty = myOtherEntity.OtherProperty,
OneMoreProperty = oneMoreEntity.OneMoreProperty
};
if (condition1) {
joinedEntities = JoinWithRelated(joinedEntities);
}
}
public IEnumerable<MyJoinedEntity> JoinWithRelated(IEnumerable<MyJoinedEntity> joinedEntities) {
return from joinedEntity in joinedEntities
join relatedEntity in dbContext.RelatedEntities on joinedEntity.Id equals relatedEntity.MyEntityId
select new MyJoinedEntity(joinedEntity) {
Comments = relatedEntity.Comments
};
}

How can I get a list of students from this database schema?

I have the following database:
Student
StudentID
Name
LastName
Grade
GradeId
Name
GradeInstance
GradeInstanceId
GradeId
Name
Year
StudentInstance
StudentInstanceId
GradeInstanceId
StudentInstanceId
How can I retrieve a list of students from a given grade instance?
I'm using Entity Framework as my ORM so what would the LINQ query be like?
Edit: I need to return Student objects, not StudentInstance object because those don't contain the attributes I need to fill the GUI with.
So something like this doesn't work for me, unless I'm missing something here.
dbContext.StudentInstances.Where(s => s.GradeInstanceId == 1);
from s in dbContext.Student
join si in dbContext.StudentInst on s.StudentID equals si.StudentInstanceID
join g in dbContext.Grade on si.GradeInstanceID equals g.GradeID
where g.Name = ...
select s;
This should help you get started....
public static List<Student> GetStudents(int gradeId)
{
using (var context = new Entities())
{
List<Student> myList = (from s in dbContext.Student
join si in dbContext.StudentInst on s.StudentID equals si.StudentInstanceID
join g in dbContext.Grade on si.GradeInstanceID equals g.GradeID
where g.GradeId = gradeId
select s).ToList();
return myList;
}
}
Using slightly modified query :D
you could add a navigation property to your GradeInstance Entity to the collection of Student entities related to it (you actually have the possibility to do that from the Add Association wizard), then you could acess it simply with: gradeInstance.Students
Hope this helps

Categories

Resources