LINQ to SQL Right Outer Join - c#

I have a SQL Query that I'm having trouble converting to LINQ query:
SELECT DISTINCT Nodes.NodeName, NodeConfig.IPAddresses, NodeConfig.InSCOM, NodeConfig.InOrion, NodeConfig.OrionCustomerName, NodeConfig.OrionApplication, NodeConfig.NodeID
FROM Tags INNER JOIN
TagToNode ON Tags.TagID = TagToNode.TagID RIGHT OUTER JOIN
NodeConfig INNER JOIN
Nodes ON NodeConfig.NodeID = Nodes.NodeID ON TagToNode.NodeID = NodeConfig.NodeID
WHERE (NodeConfig.Session = '7/3/2014 1:46:33 PM') AND (NodeConfig.InSCOM = 0)
That returns 1076 rows.
I tried to write the LINQ equivalent:
var list1 = (from t in mldb.Tags
join tn in mldb.TagToNodes on t.TagID equals tn.TagID into tagJoin
from tj in tagJoin.DefaultIfEmpty()
join nc in mldb.NodeConfigs on tj.NodeID equals nc.NodeID
join n in mldb.Nodes on nc.NodeID equals n.NodeID
where (nc.Session == #"7/3/2014 1:46:33 PM") && (nc.InSCOM == 0)
select new { Customer = nc.OrionCustomerName, DeviceName = n.NodeName, DeviceType = nc.OrionApplication, IPAddress = nc.IPAddresses, NodeID = n.NodeID }).Distinct().ToList();
That returns 183 rows.
I have tried converting the query to inner joins as suggested by some when I searched for solutions on this site. The original query implements a SQL "RIGHT OUTER JOIN" which from what I've read left/right isn't supported in LINQ but joins can be done.
The tables that I'm pulling from have primary keys as follows:
[DataServiceKey(new string[] { "NodeID", "TagID" })]
public partial class TagToNode { }
[DataServiceKey(new string[] { "NodeID" })]
public partial class Node { }
[DataServiceKey(new string[] { "TagID" })]
public partial class Tag { }
[DataServiceKey(new string[] { "ConfigID" })]
public partial class NodeConfig { }
The relationship is that Nodes have many NodeConfigs, and many Nodes are Tagged with many tags.
Can someone help me with the query logic?

OK so I took a step back and restructured the query. I created an inner join first, and then left-joined that to the main query.
I have a list of computers in a data base that are "tagged" in my application. I wanted to be able to search the tag names in the database via the many-to-many relationship between a tag and a device, where I have a join table in the middle named "TagToNode".
The distinct selection just weeds out the dupes in the end, the idea is to get ALL of the computers (nodes) even if they're not tagged with anything.
LINQ
var tags = (from tn in mldb.TagToNodes
join t in mldb.Tags on tn.TagID equals t.TagID
select new { tn.TagID, tn.NodeID, t.TagName, t.AssocUser });
return (from nc in mldb.NodeConfigs
join n in mldb.Nodes on nc.NodeID equals n.NodeID
join t in tags on n.NodeID equals t.NodeID into nj
from tg in nj.DefaultIfEmpty()
where nc.Session == sc.SessionName && n.NodeActive == 1 && ((tg.TagName.Contains(sc.SearchTerm) && (tg.AssocUser.Contains(windowsId) || (tg.AssocUser == null || tg.AssocUser == ""))) || (n.NodeName.Contains(sc.SearchTerm)) || (nc.OrionCustomerName.Contains(sc.SearchTerm)) || (nc.IPAddresses.Contains(sc.SearchTerm)))
select new NodeInfo { Customer = nc.OrionCustomerName, DeviceName = n.NodeName, DeviceType = nc.OrionApplication, IPAddress = nc.IPAddresses, NodeID = n.NodeID }).Distinct().ToList();

Related

Have a Query want it in LINQ(Inner Join)

I'm having problem translating a query to LINQ in C# this is my query
select PDF.Name,PDF.Name
from PDF inner join PC
on PDF.Id=PC.Ref_PDF
having pc.Ref_Customer=_id
you should know that _id is something that i send to my method so I can find something with it
so far I did this which I don't think would work(cuase lot's of errors poped up)
Invalid expression term 'select'
and
Expected contextual keyword 'equals'
both at end of here join p in Context.PDFs on c.Ref_PDF
internal List<EF_Model.PDF> Customers_File(int _id)
{
using (var Context = new EF_Model.CoolerEntities())
{
var q = from c in Context.PCs
where c.Ref_Customer == _id
join p in Context.PDFs on c.Ref_PDF
select new { c.PDF.Id, c.PDF.Name, c.PDF.File };
return q;
}
}
How can we make it into a linq statement?
Fix the syntax for the query
List<EF_Model.PDF> Customers_File(int _id) {
using (var Context = new EF_Model.CoolerEntities()) {
var q = from c in Context.PCs
join p in Context.PDFs on c.Ref_PDF equals p.Id
where c.Ref_Customer == _id
select new EF_Model.PDF { Id = c.PDF.Id, Name = c.PDF.Name, File = c.PDF.File };
return q.ToList();
}
}
and the method expects to return a list so use the ToList() on the query when returning from the method.
UPDATE:
If the intention was just to return the PDF model then no need to create the anonymous object just return c.PDF
List<EF_Model.PDF> Customers_File(int _id) {
using (var Context = new EF_Model.CoolerEntities()) {
var q = from c in Context.PCs
join p in Context.PDFs on c.Ref_PDF equals p.Id
where c.Ref_Customer == _id
select c.PDF;
return q.ToList();
}
}
This should do the job for you
from pc in context.PCs where pc.Ref_Customer == _id
join p in context.PDFs on pc.Ref_PDF equals p.Ref_PDF
select new {pc.PDF.Id, pc.PDF.Name, pc.PDF.File }
Probably when you said errors, I assume you saw synactical errors
If you set up a navigation property, the query is:
var q =
from pc in Context.PCs
where pc.Ref_Customer == _id
from pdf in pc.PDFs
select pdf;
If you don't:
var q =
from pc in Context.PCs
where pc.Ref_Customer == _id
join pdf in Context.PDFs on pc.Ref_PDF equals pdf.Id
select pdf;
The main thing to know about the join syntax, it has the form
" join (a) in (b) on (c) equals (d) "
(a): the new range variable for a member of (b)
(b): the source of items you are joining to - the right side of the join.
(c): an expression in which the item from the left side of the join is in scope.
(d): an expression in which the item from the right side of the join is in scope - (a).

C# LINQ statement with joins, group by and having then mapped into list object

I have a model called ElectricityBillSiteExceeding that looks like this:
public class ElectricityBillSiteExceeding
{
public string GroupInvoiceNumber { get; set; }
public int ElectricityBillMainId { get; set; }
public string SiteNo { get; set; }
public decimal BillSiteTotal { get; set; }
public decimal MaximumAmount { get; set; }
}
I want to create a list of this type and use it to feed a grid on one of my pages, the purpose is to show which site has bills that exceed the max amount allowed.
I have written the SQL which will give me this dataset, it looks like this:
SELECT SUM(ElectricityBillSiteTotal),
ebs.ElectricityBillMainId,
SiteNo,
ebm.GroupInvoiceNumber,
es.MaximumAmount
FROM dbo.ElectricityBillSites ebs
LEFT JOIN dbo.ElectricityBillMains ebm
ON ebs.ElectricityBillMainId = ebm.ElectricityBillMainId
LEFT JOIN dbo.ElectricitySites es
ON ebs.SiteNo = es.SiteNumber
GROUP BY ebs.ElectricityBillMainId, SiteNo, ebm.GroupInvoiceNumber, es.MaximumAmount
HAVING SUM(ElectricityBillSiteTotal) <> 0 AND SUM(ElectricityBillSiteTotal) > es.MaximumAmount
I'm now in my repository trying to write the method which will go to the database and fetch this dataset so that I can power my grid for the user to see.
This is where I'm struggling. I have written a basic LINQ statement to select from a couple of tables, however I'm unsure how I can incorporate the group by and having clause from my SQL and also how I can then turn this IQueryable object into my List<ElectricityBillSiteExceeding> object.
What I have so far
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var groupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals
billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
//TODO: group by total, mainId, siteNo, GroupInv, MaxAmt and Having no total = 0 and total > max
select new
{
groupInv = billMains.GroupInvoiceNumber,
mainId = billMains.ElectricityBillMainId,
siteNo = billSites.SiteNo,
total = billSites.ElectricityBillSiteTotal,
max = sites.MaximumAmount
};
//TODO: Map the result set of the linq to my model and return
throw new NotImplementedException();
}
Can anyone point me in the right direction here?
The correct Linq query for your sql is the following. See Left Join to understand the DefaultIfEmpty and also the notes there about the use of ?. in the following group by.
(About the having - in linq you just provide a where after the group by)
var result = from ebs in ElectricityBillSites
join ebm in ElectricityBillMains on ebs.ElectricityBillMainId equals ebm.ElectricityBillMainId into ebmj
from ebm in ebmj.DefaultIfEmpty()
join es in ElectricitySites on ebs.SiteNo equals es.SiteNumber into esj
from es in esj.DefaultIfEmpty()
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, ebm?.GroupInvoiceNumber, es?.MaximumAmount } into grouping
let sum = grouping.Sum(item => item.ebs.ElectricityBillSiteTotal)
where sum > 0 && sum > grouping.Key.MaximumAmount
orderby sum descending
select new ElectricityBillSiteExceeding
{
GroupInvoiceNumber = grouping.Key.GroupInvoiceNumber,
ElectricityBillMainId = grouping.Key.ElectricityBillMainId,
SiteNo = grouping.Key.SiteNo,
BillSiteTotal = sum,
MaximumAmount = grouping.Key.MaximumAmount
};
The error you get:
An expression tree lambda may not contain a null propagating operator
By reading this I conclude that you have an older versino of the provider and thus replace the group by code from the code above with the following:
let GroupInvoiceNumber = ebm == null ? null : ebm.GroupInvoiceNumber
let MaximumAmount = es == null ? 0 : es.MaximumAmount
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, GroupInvoiceNumber, MaximumAmount } into grouping
Before getting into grouping , you need to be aware that the default join in LINQ is always an INNER JOIN. Take a look at the MSDN page How to: Perform Left Outer Joins. However, in the solution I'm presenting below, I'm using INNER JOINs since you are using fields from the other tables in your grouping and having clauses.
For reference on grouping using LINQ, check out How to: Group Query Results on MSDN.
A solution specific to your case is going to look something like:
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var qryGroupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
where billSites.ElectricityBillSiteTotal != 0 && billSites.ElectricityBillSiteTotal > sites.MaximumAmount
group new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount }
by new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount } into eGroup
select eGroup.Key;
var inMemGroupedBillSitesThatExceed = qryGroupedBillSitesThatExceed.AsEnumerable();
var finalResult = inMemGroupedBillSitesThatExceed.Select(r => new ElectricityBillSiteExceeding()
{
BillSiteTotal = r.ElectricityBillSiteTotal,
ElectricityBillMainId = r.ElectricityBillMainId,
GroupInvoiceNumber = r.GroupInvoiceNumber,
MaximumAmount = r.MaximumAmount,
SiteNo = r.SiteNo,
});
return finalResult.ToList();
}
This probably will be enough. You could use AutoMapper. It will trivialize mapping to classes.
var resultList = groupedBillSitesThatExceed
.AsEnumerable() //Query will be completed here and loaded from sql to memory
// From here we could use any of our class or methods
.Select(x => new ElectricityBillSiteExceeding
{
//Map your properties here
})
.ToList(); //Only if you want List instead IEnumerable
return resultList;

Misbehaving LINQ joins

I have a pretty large linq query driving a section of my system, and i am trying to add in a new feature, but the joins seem to be misbehaving.
The old query used to bring back a list of videos which i used to create a view model and only list each video once, with all of the metadata displayed, VisibleStaff, VisiblePlayers and VisibleTeams are all IEnumerable.
Since adding the par tthat drives VisibleStaff, any video with entries in VideosLinkings where the VideoInType flag is set to staff, displays once for each entry, rather than once, and giving me a list of staff members as metadata for VisibleStaff.
I think i am missing a grouping somewhere, but i have tried multiple groups in multiple places and cannot seem to get it right.
Does anyone have any idea where my joins have gone wrong and how i would return a single Video and multiple staff in each VideoModel?
Full Query
from video in Videos
where
video.ClubID == ClubId &&
(VideoFilter.Category == 0 || video.VideoCategoryID == VideoFilter.Category)
join userStaff in Database.Users on video.AddedByUserID.Value equals userStaff.UserID
join videoInTeams in VideoInTeams on video.VideoID equals videoInTeams.VideoID into teamsForVideo
join playerInVideo in Database.PlayersVideos on video.VideoID equals playerInVideo.VideoId into
playersForVideo
join soapVideoLink in Database.VideosLinkings on new {a = video.VideoID, b = VideoInType.SOAPNote}
equals new {a = soapVideoLink.VideoId, b = soapVideoLink.VideoInType} into soapVideoLinks
join staffVideoLink in Database.VideosLinkings on new {a = video.VideoID, b = VideoInType.Staff}
equals new {a = staffVideoLink.VideoId, b = staffVideoLink.VideoInType} into
staffVideoLinks
from svl in staffVideoLinks.DefaultIfEmpty()
join staff in Staff on svl.VideoInKeyId equals staff.StaffID into visibleStaff
let soapLinks = soapVideoLinks.Any(f => f.VideoInKeyId != -1)
let oldExtension =
video.H264Version == "Uploaded"
? ".mp4"
: (video.FlashVersion == "Uploaded" ? ".flv" : video.FileExtension)
where
VideoFilter.ShowSoapVideos || (VideoFilter.ShowSoapVideos == false && soapLinks == false)
orderby video.DateTimeUploaded descending
select new VideoModel
{
Video = video,
Category = video.VideoCategory,
Staff = userStaff.Staff,
ShowDeleteOption = VideoFilter.ShowDeleteOption,
VisibleTeams = teamsForVideo.Select(f => f.Team),
VisiblePlayers = playersForVideo.Select(f => f.Player),
Thumbnail =
video.ThumbnailURL != "" && video.ThumbnailURL != null
? video.ThumbnailURL
: "/Images/Videos/noimage.png",
IsNew = false,
IsMedicalVideo = soapLinks,
VisibleStaff = visibleStaff,
IsStaffVideo = staffVideoLinks.Any()
}
The New Lines
join staffVideoLink in Database.VideosLinkings on new {a = video.VideoID, b = VideoInType.Staff}
equals new {a = staffVideoLink.VideoId, b = staffVideoLink.VideoInType} into
staffVideoLinks
from svl in staffVideoLinks.DefaultIfEmpty()
join staff in Staff on svl.VideoInKeyId equals staff.StaffID into visibleStaff

Linq with EF method cannot be translated into a store expression(in repository pattern)

i am trying to access data from multiple model in repository pattern using Ling subquery. but when i try to access data in inner query using .GetQueryable() i receive following error.
ERRORLINQ to Entities does not recognize the method 'System.Collections.Generic.List1[<>f__AnonymousType1702[System.Int32,System.Int32]] ToList[<>f__AnonymousType1702](System.Collections.Generic.IEnumerable1[<>f__AnonymousType170`2[System.Int32,System.Int32]])' method, and this method cannot be translated into a store expression.
var query = (from i in
(from student in _tblStudents.GetQueryable()
join Sections in _tblStudentsSections.GetQueryable() on student.StudentID equals Sections.StudentID
join Class in _tblClasses.GetQueryable() on Sections.ClassID equals Class.ClassID
join Program in _tblPrograms.GetQueryable() on Class.ProgramID equals Program.ProgramID
//where student.IsForeign == false
select new
{
ProgramID = Program.ProgramID,
program = Program.ProgramName,
ClassIDs = Sections.ClassID,
TotalSeats = Program.NoOfAdmissions,
IsForeign = student.IsForeign
})
group i by new { i.ProgramID, i.IsForeign, i.TotalSeats, i.program } into grp
select new AdmissionSummaryReportModel
{
program = grp.Key.program,
TotalSeats = grp.Key.TotalSeats,
//SeatsFilled = grp.Select(m => m.ClassIDs).Count(),
AvailableForeignSeats = 22,
SeatsFilled = (int)(from student in _tblStudents.GetQueryable()
join StudentSections in _tblStudentsSections.GetQueryable() on student.StudentID equals StudentSections.StudentID
join Class in _tblClasses.GetQueryable() on StudentSections.ClassID equals Class.ClassID
join Program in _tblPrograms.GetQueryable() on Class.ProgramID equals Program.ProgramID
where student.IsForeign == false && Program.ProgramID == grp.Key.ProgramID
select new
{
StudentSections.ClassID
}).ToList().Count(),
ForeignSeatsFilled = (int)(from student in _tblStudents.GetQueryable()
join StudentSections in _tblStudentsSections.GetQueryable() on student.StudentID equals StudentSections.StudentID
join Class in _tblClasses.GetQueryable() on StudentSections.ClassID equals Class.ClassID
join Program in _tblPrograms.GetQueryable() on Class.ProgramID equals Program.ProgramID
where student.IsForeign && Program.ProgramID == grp.Key.ProgramID
select new
{
StudentSections.ClassID
}).ToList().Count()
}).ToList();
how to overcome this error with .GetQueryable() or provide me any alternative mean
The problem is using the ToList function in your LINQ query, that is not what you want to do, since that cannot be translated to a proper SQL query. You want to use ToList only outside the actual LINQ query. To get the count inside, use the LINQ Count function instead, for instance :
select new
{
StudentSections.ClassID
}).Count()
Queryable gets translated into sql query at runtime, in the subquery you are parsing with int which is known to c# compiler not to query translation.
Either user IEnumerable or remove int as count will return the int.

Linq to Entity Join and Group By

I am new to Linq to Entity and here is my test scenario:
There are Users
Users have Photo Albums. An album belongs to only one User
Albums have Photos. A Photo belongs to only one Album
Photos have Tags. A Tag may belong to many Photos
I want to write a method which displays Count and Name of Tags used in Albums of a particular User using Linq.
Here is the method that I wrote and it works fine
public static void TestStatistics(int userId)
{
// select AlbumTitle, TagName, TagCount where UserId = userId
var results = from photo in dbSet.PhotoSet
join album in dbSet.AlbumSet on photo.AlbumId equals album.Id into albumSet
from alb in albumSet
where alb.UserId == userId
join photoTag in dbSet.PhotoTagSet on photo.Id equals photoTag.PhotoId into photoTagSet
from pt in photoTagSet
join tag in dbSet.TagSet on pt.TagId equals tag.Id
group new { alb, tag } by new { alb.Title, tag.Name }
into resultSet
orderby resultSet.Key.Name
select new
{
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
};
foreach (var item in results)
{
Console.WriteLine(item.AlbumTitle + "\t" + item.TagName + "\t" + item.TagCount);
}
}
And this is the standart T-SQL query which does the same
SELECT a.Title AS AlbumTitle, t.Name AS TagName , COUNT(t.Name) AS TagCount
FROM TblPhoto p, TblAlbum a, TblTag t, TblPhotoTag pt
WHERE p.Id = pt.PhotoId AND t.Id = pt.TagId AND p.AlbumId = a.Id AND a.UserId = 1
GROUP BY a.Title, t.Name
ORDER BY t.Name
It is pretty obvious that standard T-SQL query is much simpler than the Linq query.
I know Linq does not supposed to be simpler than T-SQL but this complexity difference makes me think that I am doing something terribly wrong. Besides the SQL query generated by Linq is extremly complex.
Is there any way to make the Linq query simpler?
UPDATE 1:
I made it a little simpler without using joins but using a approach like used in T-SQL. Actually it is now as simple as T-SQL. Still no navigation properties and no relations on db.
var results = from photo in dbSet.PhotoSet
from album in dbSet.AlbumSet
from photoTag in dbSet.PhotoTagSet
from tag in dbSet.TagSet
where photo.AlbumId == album.Id && photo.Id == photoTag.PhotoId &&
tag.Id == photoTag.TagId && album.UserId == userId
group new { album, tag } by new { album.Title, tag.Name } into resultSet
orderby resultSet.Key.Name
select new {
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
};
If every photo has at least one tag , then try
var results = (from r in PhotoTag
where r.Photo.Album.UserID == userId
group r by new { r.Photo.Album.Title, r.Tag.Name } into resultsSet
orderby resultsSet.Key.Name
select new
{
AlbumTitle = resultsSet.Key.Title ,
TagName = resultsSet.Key.Name ,
TagCount = resultsSet.Count()
}
);
First things first, you need to setup foreignkeys in your database then rebuild EF and it will 'know' (i.e. navigation properties) about the relationships, which then allows you to omit all of your joins and use something along the lines of the following:
List<AlbumTag> query = (from ps in dbSet.PhotoSet
where ps.Album.UserId = userId
group new { album, tag } by new { ps.Album.Title, ps.PhotoTag.Tag.Name } into resultSet
orderby resultSet.Key.Name
select new AlbumTag()
{
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
}).ToList();

Categories