Linq join with record of second table - c#

I have looked at many similar question on SO but seems its not that straight forward.
The problem is, most of them are dealing with IEnumerable where in my case I have two IQueryable dbsets.
The situation is somewhat similar to the question here.
Student
id Name
1 a1
2 b1
3 c1
Images
id Image StudentId Status ModifiedOn
1 1.jpg 1 Active 2021-03-12 02:02:32.580
2 2.jpg 1 Deleted 2021-03-12 02:01:32.580
3 3.jpg 2 Deleted 2021-03-12 02:02:32.580
4 4.jpg 2 Deleted 2021-03-12 02:01:32.580
Result should be
id Name Image
1 a1 1.jpg
2 b1 3.jpg
3 c1 NULL
I can do this with TSQL and nested WITH qqueries, where one selects Status = Active, and the other selects Status != Active, then merge these two and select the TOP 1.
But since the requirement is to write the equivalent LINQ, I started with the below query, since I don't know a good way to do a merge of CASE WHEN on Status = Active.
var aquery = context.Images;
var lquery = context.Students;
var result = from l in lquery
join a in aquery on l.Id equals a.StudentId into aGroup
from a in aGroup.OrderByDescending(m => m.ModifiedOn).Take(1)
select new {
l.id,
a.StudentId,
a.Status
};
This failed the dbsets are not IEnumerable. Any idea how to get the correct result?

This query should work:
var query =
from s in context.Students
from i in context.Images
.Where(i => i.StudentId = s.Id)
.OrderBy(i => i.Status == "Active" ? 0 : 1)
.ThenByDescending(i => i.ModifiedOn)
.Take(1)
.DefaultIfEmpty()
select new
{
s.Id,
s.Name,
i.Image
};

IQueryable<Image> images = context.Images.AsQueryable();
IQueryable<Student> students = context.Students;
var result = (from st in students
select new
{
Id = st.Id,
Name = st.Name,
ImageName = images
.OrderBy(x => x.ModifiedAt)
.Where(x => x.Status)
.Where(i=> i.StudentId == st.Id)
.Select(x=> x.ImageName)
.FirstOrDefault()
})
.ToList();
But the easiest option is to define navigation field for images inside Student class:
public class Student{
List<Image> Images {get; private set;}
}
and then:
context.Students
.Select(st=> new
{
Id = st.Id,
Name = st.Name,
ImageName = st.Images
.OrderBy(x => x.ModifiedAt)
.Where(x => x.Status)
.Where(i=> i.StudentId == st.Id)
.Select(x=> x.ImageName)
.FirstOrDefault()
})
.ToList();

Related

Linq to SQL | Get all records where a foreign key doesn't exist

I have 2 tables:
Schools
-------
pk_school_id,
title
and
Business_Hours
--------------
pk_id,
fk_school_id
I want pk_school_id and title from School Table for all pk_school_id that does not exist as fk_school_id in Business_Hours table.
var Schools = (from b in Db.Language_School_Business_Hours
join s in Db.Language_Schools on b.fk_school_id equals s.pk_school_id into lrs
from lr in lrs.DefaultIfEmpty()
select new
{
LeftID = b.fk_school_id,
RightId = ((b.fk_school_id == lr.pk_school_id) ? lr.pk_school_id : 0)
});
try this to achieve your goal, without join, just take elements that are not contained in Business_Hours table
var Schools = Db.Language_Schools
.Where(s => !Db.Language_School_Business_Hours
.Select(b => b.fk_school_id).ToList().Contains(s.pk_school_id))
.Select(x => new
{
x.pk_school_id,
x.school_title
});
I think we can a bit simplify the linq by removing .ToList() after the first .Select(...), removing the last .Select(...). Pls take a look at a below code.
var schools = Db.Language_Schools
.Where(w => !Db.Language_School_Business_Hours
.Select(s => s.fk_school_id)
.Contains(w.pk_school_id))
.ToList();

LINQ Method that returns set based on filtered sub-set

I have 2 tables. 1 has entity's, 1 per row. Another is simply a mapping table of my EntitiesID and EmployeeID. I am trying to write a LINQ method that returns all Entities from the First Table where the EntityID is in the mapping table that is filtered by the EmployeeID.
Simplified Table Structure Example
TaskTable: ID, Description, Status
TaskViewTable: ID, TaskID, EmployeeID
So I want to return all Rows from TaskTable where the ID is in a SubQuery results of TaskViewTable based on EmployeeID.
Any help on doing this in LINQ? I have a 1 to Many set up between the two tables as well. I know there are similar questions am maybe I'm dense but they didn't seem to apply completely to what I was asking.(e.g. Linq Return Filtered Children)
Sorry forgot to show what I have so far:
IQueryable<tblTask> tTask=context.GetTable<tblTask>();
return tTask.Where(t => t.tblTasksViews.Where(v => v.EmployeeID == empID))
It, however, does not like my wherewith an unkown method Where(?)
Something like this should do the trick:
var tasks = tTask.Where(t =>
tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId).Contains(t.ID));
You could break up the above into two sections:
//1.) Get all task views for the employeeID and only select the mapped TaskId
var taskViews = tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId); //taskViews = IEnumerable<int>
//2.) Then get all tasks from the filtered task ids
var tasks = tTask.Where(t => taskViews.Contains(t.ID));
UPDATE
//3.) Project filtered results into IEnumerable<Task>
return tasks.Select(t => new Task()
{
ID = t.ID,
ActionableID = t.ActionableID,
StatusID = t.StatusID,
TypeID = t.TypeID,
Description = t.Description
});
You can, of course, string everything into a nice one-liner:
public List<Task> GetTasks(int empId)
{
return tTask
.Where(t => tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId).Contains(t.ID))
.Select(t => new Task()
{
ID = t.ID,
ActionableID = t.ActionableID,
StatusID = t.StatusID,
TypeID = t.TypeID,
Description = t.Description
}).ToList();
}
Try something like this:
var query =
from tt in TaskTable
join tvt in TaskViewTable on tt.ID equals tvt.TaskID into xs
where xs.Any(z => z.EmployeeID == empID)
select tt;

2 linq queries to same gridview

I would like to "combine" to linq queries into the same gridview. Though I don't think I could use innerjoin on this.
The queries get orders from the database, and I have 2 tables with orders from different places.
In the gridview I want to display all orders from first table and second table.
Like this:
OrderID - Item - Amount etc ---From table 1
OrderID - Item - Amount etc ---From table 2
etc..
My current query for getting orders from the first table are:
var query = from o in db.Orders
join y in db.OrderLines on o.OrderID equals y.OrderID
join x in db.Products on y.ItemNumber equals x.ItemNumber
where o.AccountNumber == AppSession.CurrentLoginTicket.AccountNumber
select new
{
o.OrderID,
o.AxaptaSalesId,
y.ItemNumber,
x.Name,
x.ProductFormatName,
y.Quantity,
y.Price,
Status = dltRep.getOrderStatus(o.OrderID, o.AxaptaSalesId, y.ItemNumber).Substring(0, dltRep.getOrderStatus(o.OrderID, o.AxaptaSalesId, y.ItemNumber).LastIndexOf("|")),
Levering = dltRep.getOrderStatus(o.OrderID, o.AxaptaSalesId, y.ItemNumber).Substring(dltRep.getOrderStatus(o.OrderID, o.AxaptaSalesId, y.ItemNumber).LastIndexOf("|")).Replace("|", "")
};
The other table would have the same information. It's named AxSale.
I hope this is possible and someone could help me out :)
EDIT: new "problem"
I wan't to get the variable createdDate to be the first element x.CreatedDate in either the first linq seq. or the second.
How do I do this?
var created = purchases.OrderByDescending(x => x.CreatedDate).
Select(x => new { x.CreatedDate, x.LineSupplierAccountNO }).
GroupBy(x => x.LineSupplierAccountNO);
if (created.Count() > 1)
{
created = purchases.OrderBy(x => x.CreatedDate).
Select(x => new { x.CreatedDate, x.LineSupplierAccountNO }).
GroupBy(x => x.LineSupplierAccountNO);
}
var createdDate = created.FirstOrDefault();
Solution code:
var created = purchases.OrderBy(x => x.CreatedDate).Select(x => x);
if (created.GroupBy(x => x.LineSupplierAccountNO).Count() > 1)
{
created = purchases.OrderByDescending(x => x.CreatedDate).Select(x => x);
}
var createdDate = created.First().CreatedDate;
Use UNION operator to join results with same fields. Here is example from http://code.msdn.microsoft.com/windowsdesktop/101-LINQ-Samples-3fb9811b
public void Linq49()
{
List<Product> products = GetProductList();
List<Customer> customers = GetCustomerList();
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
var uniqueFirstChars = productFirstChars.Union(customerFirstChars);
Console.WriteLine("Unique first letters from Product names and Customer names:");
foreach (var ch in uniqueFirstChars)
{
Console.WriteLine(ch);
}
}
I believe you are looking for Union
Try this link

How I can show all records with Linq To SQL?

I have 2 tables to join in a query as follows:
var query = (from c in Amenites_TBLs
join p in AmenitesContact_TBLs on c.AmenitesCodeID
equals p.AmenitesCodeID
// group c by p.AmenitesCodeID
into g
from cc in g.DefaultIfEmpty()
select new
{
AmenitiesCode = Amenites_TBLs.SingleOrDefault(a => a.AmenitesCodeID == cc.AmenitesCodeID).AmenitesCode,
CountryCode = Amenites_TBLs.SingleOrDefault(a => a.AmenitesCodeID == cc.AmenitesCodeID).CountryCode,
Director = AmenitesContact_TBLs.Where(a => a.TypeOfContact.StartsWith("Dir")).FirstOrDefault(a => a.AmenitesCodeID == cc.AmenitesCodeID).ContactName});
In the Table AmenitesContact_TBLs there are just 3 records. In the table Amenites_TBLs there are 300 records but the result of the query gives only 3 records and the other 297 rows are null but the fields AmenitiesCode and CountryCode are not null in the database (they get a value).
How can I modify my query to show all 300 records?
Try this:
Amenites_TBLs.Join(AmenitesContact_TBLs , c => c.AmenitesCodeID , p => p.AmenitesCodeID,(p,o) =>
new{ AmenitiesCode = c.AmenitesCode,CountryCode = c.CountryCode,Director = p.Where(a => a.TypeOfContact.StartsWith("Dir")).ContactName });

LINQ : Left Join And Right Join with a double selection

I am looking for a solution to have all the content of the table PART (by adding a right/left join I suppose) in the following LINQ query :
var query = (from p in db.PARTS
join oc in db.OUTPUT_CONTROLS on p.id equals oc.partid
join f in db.FCT on p.fct equals f.id
select new
{ p.id, p.plant, p.unit, p.type, p.num, f.name, oc.datetime, oc.ncr }
into x
group x by new
{ x.id, x.plant, x.unit, x.type, x.num, x.name }
into g
select new
{ g.Key.id, g.Key.plant, g.Key.unit, g.Key.type, g.Key.num, g.Key.name, startdate = g.Min(oc => oc.datetime), endate = g.Max(oc => oc.datetime), sumncr = g.Sum(oc => oc.ncr) })
.OrderByDescending(oc => oc.startdate);
Thanks
I found the solution on my own thanks to this post : LINQ Left Join And Right Join
The solution :
var query = (from p in db.PARTS
join oc in db.OUTPUT_CONTROLS on p.id equals oc.partid into joined
join f in db.FCT on p.fct equals f.id
from j in joined.DefaultIfEmpty()
select new
{ p.id, p.plant, p.unit, p.type, p.num, f.name, j.datetime, j.ncr } into x
group x by new { x.id, x.plant, x.unit, x.type, x.num, x.name } into g
select new { g.Key.id, g.Key.plant, g.Key.unit, g.Key.type, g.Key.num, g.Key.name, startdate = g.Min(oc => oc.datetime), endate = g.Max(oc => oc.datetime), sumncr = g.Sum(oc => oc.ncr) })
.OrderByDescending(oc => oc.startdate);
If you have a SQL where you see a join followed by a GroupJoin, consider using the LINQ GroupJoin.
Quite often you'll see this in situations where you want "Schools with their Students", "Customers with their Orders", "Zoos with their Animals"
It seems that you have 3 tables: Parts, OutputControls and Fcts.
Every Part has zero or more OutputControls, and every OutputControl belongs to exactly one Part, using foreign key PartId: a straightforward one-to-many relation
A Part has a foreign key FctId, that points to the Fct of the part.
You want (some properties of) the Parts, with their OutputControls and its Fct
var result = parts.GroupJoin(outputControls, // GroupJoin Parts and OutputControls
part => part.Id, // from every part take the Id
outputControl => outputControl.PartId, // from every outputControl take the PartId
// result selector: when these Ids match,
// use the part and all its matching outputControls to make one new object:
(part, outputControlsOfThisPart) => new
{
// select the part properties you plan to use:
Id = part.id,
Plant = part.plant,
Unit = part.unit
// the output controls of this part:
OutputControls = outputControlsOfThisPart.Select(outputControl => new
{
// again, select only the output control properties you plan to use
Id = outputControl.Id,
Name = outputControl.Name,
...
})
.ToList(),
// For the Fct, take the Fct with Id equal to Part.FctId
Fct = Fcts.Where(fct => fct.Id == part.Fct)
.Select(fct => new
{
// select only the Fct properties you plan to use
Id = fct.Id,
Name = fct.Name,
...
})
// expect only one such Fct
.FirstOrDefault(),
});

Categories

Resources