Entity Framework - LeftJoin on multiple columns - .net-core

I'm trying to write in C# a query with left join using Lambda Expression.
How can i get the wanted SQL query when i have a filter in the joined table.
I mean without subquery, with multiple verifications on 'ON' statement
public class Article
{
public int Id { get; set; }
public string ArticleName { get; set; }
// EF navigation property
public ICollection<UserArticleNames> UserArticleNames { get; set; }
}
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
}
public class UserArticleName
{
// FK Users
public int UserId { get; set; }
// FK Articles
public int ArticleId { get; set; }
public int UserArticleName { get; set; }
}
EF - LINQ query via navigation property: (get Articles for an user)
var join = context.Articles
.SelectMany(a => a.UserArticleNames
.Where(ua => ua.UserId == 1)
.DefaultIfEmpty(), (a, ua) => new {
Id = a.Id,
ArticleName = a.ArticleName ,
UserArticleName = ua.UserArticleName,
}
);
Resulting SQL code:
SELECT [a].[Id],
[a.ArticleName],
[ua].[UserArticleName]
FROM [dbo].[Articles] AS [a]
LEFT JOIN (
SELECT [a0].[UserId],
[a0].[ArticleId],
[a0].[UserArticleName],
FROM [dbo].[UserArticleName] AS [a0]
WHERE [a0].[UserId] = 1
) AS [ua] ON [a].[Id] = [ua].[Id]
Wanted SQL code:
SELECT [a].[Id],
[a.ArticleName],
[ua].[UserArticleName]
FROM [dbo].[Articles] AS [a]
LEFT JOIN [dbo].[UserArticleName] AS [ua]
ON [a].[Id] = [ua].[Id] AND [ua].[UserId] = 1

Related

How can I initialize one model in another model's controller action?

I created a web-app in Asp.net MVC and it has an order action. I have these two models for Order
public class Order
{
public int Id { get; set; }
public DateTimeOffset OrderTime { get; set; }
[InverseProperty("Order")]
public ICollection<OrderDetail> OrderDetails { get; set; }
}
and for OrderDetail
public class OrderDetail
{
public int Id { get; set; }
public int OrderId { get; set; }
public ICollection<Order> Order { get; set; }
public int MenuId { get; set; }
public int RestaurantId { get; set; }
public Menu Menu { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
And I created tables for them.
Also I created a controller for Order. It contains Index and Details actions. Index acction shows the list of order and every order has its own Detail link which should contain information of Order and related OrderDetail
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Order order = db.Orders.Find(id);
if (order == null)
{
return HttpNotFound();
}
return View(order);
}
And the problem is that OrderDetails is null. Can you suggest me how I can initialize OrderDetail in Details action?
You have to tell EntityFramework which navigation properties you want to include.
Order order = db.Orders
.Where( o => o.Id == id )
.Include( o => o.OrderDetails )
.SingleOrDefault();
But you cannot use Find method any more

Merging multiple List<CustomType> when grouping in LINQ

I have this models:
public class AudienceInfo
{
public int Id { get; set; }
public string Departments { get; set; }
public List<CountOfDestination> CountOfDestinations { get; set; }
}
public sealed class CountOfDestination
{
public string DestinationName { get; set; }
public int? CountRoom { get; set; }
public int? CountOfFiles { get; set; }
}
And this table in DB.
public class AudienceInfo : IModelWithId
{
public int Id { get; set; }
....
public RoomPurpose RoomPurpose { get; set; }
public List<AudienceInfo_File> Files { get; set; }
}
After selecting the "Departments" (where condition), I get a list of data. Then, I GroupBy "RoomPurpose" and get
"CountRoom" for every row = DestinationName. This part work correctly.
Also, i need to get the CountOfFiles ... don't know how to do this
return dbAudInfo
.Where(x => x.RightOfPreferentialUse.Id == Id)
.Select(x => new AudienceInfo
{
Departments = x.RightOfPreferentialUse.Name,
Date = date1,
CountOfDestinations = dbAudInfo
.GroupBy(z => new { z.RoomPurpose })
.Select(y => new CountOfDestination
{
DestinationName = y.Key.RoomPurpose.Name,
CountRoom = y.Count(z => z.RightOfPreferentialUse.Id == Id),
CountOfFiles = ?????????????????????????
}).ToList()
}).FirstOrDefaultAsync();
How can i connect a list in LINQ query with GroupBy.
Also, i need to get the CountOfFiles ... don't know how to do this
Use:
CountOfFiles = y.ToList().First(a => a.Id == y.Id).Files.Count()
How can i connect a list in LINQ query with GroupBy.
Can you elaborate?

LINQ Group By Type for each user

I have a list of objects with following structure
public class Jobs
{
public string EmployeeName { get; set; }
public string JobNumber { get; set; }
public string JobPriority { get; set; }
public string JobType { get; set; }
public string Description { get; set; }
public string Status { get; set; }
public string CreatedByUser { get; set; }
public DateTime DueDate { get; set; }
public DateTime UpdateDate { get; set; }
}
What I would like to do is to return a List of objects which contains
EmployeeName,
Total Count of Jobs assigned to him,
Job Type & Count of each job type assigned to an emploee.
So in essence, I would need list of records to appear like the attached image below. I will use the result from the LINQ query and bind it to a asp.net GridView
Andrew
Total Jobs: 12
Build Jobs: 3
Config Jobs: 4
Delivery Jobs: 3
How can I achieve a result using the linq from the initial list that i have. I guess for pros, this would be a very simple and straight-forwad LINQ query.
EDIT: Added for final structure that i want:
I would like to get the result from LINQ into following structure. i.e. List of UserJob
public class UserJob
{
public string EmployeeName { get; set; }
public int TotalJobs { get; set; }
public List<UserJobGroup> Tickets { get; set; }
}
public class UserJobGroup
{
public string JobType { get; set; }
public int JobCount { get; set; }
}
dasblinkenlight already explained it. With your specific classes, it's even easier - all you need is to follow the steps he described and translate them to LINQ. You should really be able to do that yourself, especially with query syntax:
var result =
(from job in jobList
group job by job.EmployeeName into employeeNameJobs
let jobTypeJobs =
(from job in employeeNameJobs
group job by job.JobType into jobTypeJobs
select new UserJobGroup
{
JobType = jobTypeJobs.Key,
JobCount = jobTypeJobs.Count()
})
.ToList()
select new UserJob
{
EmployeeName = employeeNameJobs.Key,
TotalJobs = jobTypeJobs.Sum(item => item.JobCount),
Tickets = jobTypeJobs
})
.ToList();
Let's walk through the structure of the query that you are trying to build:
At the top level you need groups by employee
At the employee level you need two related, but separate, things:
You need separate counts by job, and
You also need the total of these counts
Now let's build the query. Start with GroupBy for the top level:
var empGroups = jobList.GroupBy(j => j.EmployeeName);
Convert each group to a dictionary with EmployeeName serving as the key, and job group counts stored as values:
var jobCountsByEmployee = empGroups
.ToDictionary(
g => g.Key
, g => g.GroupBy(j => j.JobType)
.ToDictionary(jg => jg.Key, jg => jg.Count())
);
Finally, add totals to construct the result that you want:
var res = jobCountsByEmployee
.ToDictionary(
p => p.Key
, p => new {
Total = p.Value.Sum(jc => jc.Value)
, CountByJob = p.Value
}
);

EF7 generating incorrect sql with optional relationships

Consider the following models:
public class News
{
public int Id { get; set; }
public string Title { get; set; }
public Branch Branch { get; set; }
public int? BranchId { get; set; }
}
public class NewsDto
{
public int Id { get; set; }
public string Title { get; set; }
public BranchDto Branch { get; set; }
}
public class Branch
{
public int Id { get; set; }
public string Name { get; set; }
public List<News> News { get; set; } = new List<News>();
}
public class BranchDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Here we have an optional relationship between News and Branch (1-to-many).
We're issuing the following query:
context.News
.Select(dto => new NewsDto()
{
Id = dto.Id,
Title = dto.Title,
Branch = dto.Branch == null ? null : new BranchDto()
{
Id = dto.Branch.Id,
Name = dto.Branch.Name
}
}).ToList();
EF6
The generated query is:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1],
[Extent1].[BranchId] AS [BranchId],
[Extent2].[Name] AS [Name]
FROM [dbo].[News] AS [Extent1]
LEFT OUTER JOIN [dbo].[Branches] AS [Extent2] ON [Extent1].[BranchId] = [Extent2].[Id]
This is how it should be: a left outer join after a CASE WHEN to handle null references to the Branches table.
EF7 (RC1)
The same query is generated as:
SELECT
[dto].[Id],
[dto].[Title],
[dto].[BranchId] IS NULL,
[dto].[BranchId],
[dto.Branch].[Name]
FROM [News] AS [dto]
INNER JOIN [Branch] AS [dto.Branch] ON [dto].[BranchId] = [dto.Branch].[Id]
I don't really understand why this is happening. The relationship is clearly optional so inner joins are just plain wrong. Other than that, the line [dto].[BranchId] IS NULL is giving a System.Data.SqlClient.SqlException: Incorrect syntax near the keyword 'IS' so I guess this isn't even valid sql.
This is only happening when the relationship is optional. What's going on?
So I believe this is related to issue #3186. The EF team recognizes this as a high priority bug and it should be fixed in RC2.

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Resources