Linq Join when second table will have multiple records - c#

Trying to create a Linq query that can do multiple searches in one command instead of having multiple search result pages. It is working great when I am trying to find multiple records that have a subject (in my case CHIEF_COMPLAINT) and comments with a specific word. The problem is when I want to search with serial numbers.
There are two issues here. One is that multiple pieces of equipment can be attached to a specific ticket and also a single piece of equipment can be associated with multiple tickets. When I query the table used to associate equipment to tickets (VIEW_WT_EQUIP, using the view because it is where the serial number is seen) I potential get multiple results with the same Ticket_ID.
This is the query that I have right now, but it returns no results when I put in a serial number that I know is in the system.
var query = from a in db.VIEW_WT_HEADERs
join c in db.VIEW_WT_EQUIPs on a.TICKET_ID equals c.TICKET_NUMBER into c_group
from c2 in c_group
join b in db.WT_EVENTs on a.TICKET_ID equals b.TICKET_ID
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) || c2.SERIAL_NUMBER.Contains(input)
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
I also tried a method where I used 2 linq queries and put all the ticket numbers from a serial number search into a list, but the second query didn't like that I was trying to compare an int array.
I think I am just going about this wrong. Join is probably not the right way to do this, but I don't know how to tell the main query to pull all the tickets associated with a piece of equipment.
Please let me know where I can clarify, because I know this explination is rough.

I would put this as comment instead of an answer, but I want to show you some code, so I had to choose "answer".
If you are using Linq to Entities, you probably have a relationship between the objects. It means that the join is not necessary. You should only use join when no navigation property is available.
I can't tell exactly what you should do, but here is some code the might be helpful:
var query = from a in db.VIEW_WT_HEADERs
from b in a.WT_EVENTs
from c in a.VIEW_WT_EQUIPs
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) ...
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
you can also use let to store a sub-expression:
var query = from a in db.VIEW_WT_HEADERs
from b in a.WT_EVENTs
from c in a.VIEW_WT_EQUIPs
let x = c.FirstOrDefault()
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) || x.SomeProperty ....
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
Those are just example, maybe it helps!

Related

LINQ - Left Join converting RIGHT results to List

I'm struggling to find the correct combination of LINQ Methods to perform a multi-table left join with a one to many mapping that makes a list along with the grouped results.
Current Status
I have a Plan table, joined with other tables to get the columns I need to get my list of plans.
var plans = await (
from ubp in db.ViewUserBusinessPlan
join bp in db.ViewBusinessPlan on ubp.BusinessPlanId equals bp.BusinessPlanId
where bp.BusinessId == businessId
select new
{
ubp.UserBusinessPlanId,
ubp.BusinessPlanId,
bp.Name,
bp.PlanGroup,
bp.BusinessId,
ubp.BusinessLocationId,
ubp.StripeSubscriptionId,
ubp.UserId,
ubp.BusinessPlanPriceCents
}
Problem:
Now I need to ultimately get the applicable Tax Rates that are associated with those Plans. This is stored in 2 tables.
UserBusinessPlanTaxRates - (a mapping table) that contains UserBusinessPlanId and TaxRateId.
TaxRates - TaxRate Information with TaxRateId as PK
Some plans have tax rates, some do not, so need a LEFT JOIN type scenario. Also, some plans can have multiple tax rates so I need a list of TaxRates. I've tried various Group methods, subqueries, and left joins. But nothing seems to put them all together.
I want to get all plans, with a list of TaxRates.
You can add a sub-query in the select to do the necessary join:
TaxRates = db.UserBusinessPlanTaxRates.Where(ubptr => ubptr.UserBusinessPlanId == ubp.UserBusinessPlanId)
.GroupJoin(db.TaxRates, ubptr => ubptr.TaxRateId, tr => tr.TaxRateId, (ubptr, trj) => trj)
.SelectMany(trj => trj)
.ToList()
Whether it will translate properly (or optimally) to SQL depends on what LINQ to database you are using.

Linq To SQL equivalent group by with multiple table columns in the output

I have just started on a project that uses Linq To SQL (there are various reasons why this is so, but for the moment, that is what is being used, not EF or ANOther ORM).
I have been tasked with migrating old (and I'm talking VB6 here) legacy code.
I come from a predominantly T-SQL background, so I knocked up a query that would do what I want, but I have to use LINQ to SQL (c# 3.5), which I don't have much experience with.
Note that the database will be SQL Server 2008 R2 and/or SQL Azure
Here is the T-SQL (simplified)
SELECT TBS.ServiceAvailID, sum(Places) as TakenPlaces,MAX(SA.TakenPlaces)
FROM TourBookService TBS
JOIN TourBooking TB
ON TB.TourBookID=TBS.TourBookID
JOIN ServiceAvail SA
ON TBS.ServiceAvailID = SA.ServiceAvailID
WHERE TB.Status = 10
AND ServiceSvrCode='test'
GROUP BY TBS.ServiceAvailID
HAVING sum(Places) <> MAX(SA.TakenPlaces)
So, there is a TourBooking table which has details of a customer's booking. This hangs off the TourBookService table which has details of the service they have booked. There is also a ServiceAvail table which links to the TourBookService table. Now, the sum of the Places should equal the Taken places amount in the ServiceAvail table, but sometimes this is not the case. This query gives back anything where this is not the case. I can create the Linq to just get the sum(places) details, but I am struggling to get the syntax to also get the TakenPlaces (note that this doesn't include the HAVING clause either)
var q = from tbs in TourBookServices
join tb in TourBookings on tbs.TourBookID equals tb.TourBookID
join sa in ServiceAvails on tbs.ServiceAvailID equals sa.ServiceAvailID
where (tb.Status == 10)
&& ( tbs.ServiceSvrCode =="test")
group tbs by tbs.ServiceAvailID
into g
select new {g.Key, TotalPlaces = g.Sum(p => p.Places)};
I need to somehow get the sa table into the group so that I can add g.Max(p=>p.PlacesTaken) to the select.
Am I trying to force T-SQL thinking into LINQ ?
I could just have another query that gets all the appropriate details from the ServiceAvail table, then loop through both result sets and match on the key, which would be easy to do, but feels wrong (but that may just be me!)
Any comments would be appreciated.
UPDATE:
As per the accepted answer below, this is what Linqer gave me. I will have a play and see what SQL it actually creates.
from tbs in db.TourBookService
join sa in db.ServiceAvail on tbs.ServiceAvailID equals sa.ServiceAvailID
where
tbs.TourBooking.Status == 10
tbs.ServiceSvrCode == "test")
group new {tbs, sa} by new {
tbs.ServiceAvailID
} into g
where g.Sum(p => p.tbs.Places) != g.Max(p => p.sa.TakenPlaces)
select new {
ServiceAvailID = (System.Int32?)g.Key.ServiceAvailID,
TakenPlaces = (System.Int32?)g.Sum(p => p.tbs.Places),
Column1 = (System.Int32?)g.Max(p => p.sa.TakenPlaces)
}
In your case I would try to use some kind of converter in my personal experience I used this program http://sqltolinq.com/ it often works very well in convertitng sql to linq.

LINQ to entities comparing collections

I'm having issues with a Linq query that will actually do what I need it to. I've constructed a search that needs to find articles in a database (entity framework) and the search then uses a list of checkboxes to allow the user to only search within certain areas of practice. I've seen posts about linq joins & multiple collections, but my problem is that what I'm trying to select from is the parent table Article and I need to comppare the .Practices to a Practices list I've compiled from the checkbox list.
Before it was a checkbox list, it was a dropdown and this query worked to search for 1 practice:
ar = (from a in db.Articles
from p in a.Practices
where p.ID == practiceID
select a);
Now, I need the where clause to work like an "IN" from regular SQL. My collection of Practices compiled from a checkbox list need to be compared to db.Articles.Practices for my search.
Any suggestions or clarification needed?
Assuming: List practiseIds
ar = (from a in db.Articles
from p in db.Practices
where practiseIds.Contains(p.ID)
select a);
However some clarification is needed:
IN is not a first class supported SQL method in EF so the above code will in fact turn into a list of or's (this may have been resolved in the .net 4 release of EF - http://blogs.msdn.com/b/adonet/archive/2009/08/05/improvements-to-the-generated-sql-in-net-4-0-beta1.aspx)
Psuedo Code -
Select * From db.Articles
inner join db.Practices on <relation>
Where db.Practices.practiceid = #p0 || db.Practices.practiceid = #p1 || etc..
Just pass in the id collection as array practiceIds and use a Contains() query:
ar = (from a in db.Articles
from p in a.Practices
where practiceIds.Contains(p.ID)
select a);

Working with Cross Context Joins in LINQ-to-SQL

Initially I had written this query using LINQ-to-SQL
var result = from w in PatternDataContext.Windows
join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
join r in ResultDataContext.Results on p.PatternId equals r.PatternId
join fi in ResultDataContext.IclFileInfos on r.IclFileId equals fi.IclFileId
join sp in sessionProfileDataContext.ServerProfiles on fi.ServerProfileId equals sp.ProfileId
join u in infrastructure.Users on sp.UserId equals u.Id
where w.Process.Equals(processName)
select u.DistributedAppId;
And when I executed it, and saw result in the QuickWatch.., it showed this message:
the query contains references to items defined on a different data context
On googling, I found this topic at Stackoverflow itself, where I learned simulating cross context joins and as suggested there, I changed my query a bit to this:
var result = from w in PatternDataContext.Windows
join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
join r in SimulateJoinResults() on p.PatternId equals r.PatternId
join fi in SimulateJoinIclFileInfos() on r.IclFileId equals fi.IclFileId
join sp in SimulateJoinServerProfiles() on fi.ServerProfileId equals sp.ProfileId
join u in SimulateJoinUsers() on sp.UserId equals u.Id
where w.Process.Equals(processName)
select u.DistributedAppId;
This query is using these SimulateXyz methods:
private static IQueryable<Result> SimulateJoinResults()
{
return from r in SessionDataProvider.Instance.ResultDataContext.Results select r;
}
private static IQueryable<IclFileInfo> SimulateJoinIclFileInfos()
{
return from f in SessionDataProvider.Instance.ResultDataContext.IclFileInfos select f;
}
private static IQueryable<ServerProfile> SimulateJoinServerProfiles()
{
return from sp in sessionProfileDataContext.ServerProfiles select sp;
}
private static IQueryable<User> SimulateJoinUsers()
{
return from u in infrastructureDataContext.Users select u;
}
But even this approach didn't solve the problem. I'm still getting this message in QuickWatch...:
the query contains references to items defined on a different data context
Any solution for this problem? Along with the solution, I would also want to know why the problem still exists, and how exactly the new solution removes it, so that from next time I could solve such problems myself. I'm new to LINQ, by the way.
I've had to do this before, and there are two ways to do it.
The first is to move all the servers into a single context. You do this by pointing LINQ-to-SQL to a single server, then, in that server, create linked servers to all the other servers. Then you just create views for any tables you're interested from the other servers, and add those views to your context.
The second is to manually do the joins yourself, by pulling in data from one context, and using just the properties you need to join into another context. For example,
int[] patternIds = SessionDataProvider.Instance.ResultDataContext.Results.Select(o => o.patternId).ToArray();
var results = from p in PatternDataContext.Patterns
where patternIds.Contains(p.PatternId)
select p;
Though the first is easier to work with, it does have its share of problems. The problem is that you're relying on SQL Server to be performant with linked servers, something it is notoriously bad at. For example, consider this query:
var results = from p in DataContext.Patterns
join r in DataContext.LinkedServerResults on p.PatternId equals r.PatternId
where r.userId = 10;
When you enumerate this query, the following will occur (let's call the normal and linked servers MyServer and MyLinkedServer, respectively)
MyServer asks MyLinkedServer for the Results
MyLinkedServer sends the Results back to MyServer
MyServer takes those Results, joins them on the Patterns table, and returns only the ones with Results.userId = 10
So now the question is: When is the filtering done - on MyServer or MyLinkedServer? In my experience, for such a simple query, it will usually be done on MyLinkedServer. However, once the query gets more complicated, you'll suddenly find that MyServer is requesting the entire Results table from MyLinkedServer and doing the filtering after the join! This wastes bandwidth, and, if the Results tables is large enough, could turn a 50ms query into a 50 second query!
You could fix unperformant cross-server joins using stored procedures, but if you do a lot of complex cross-server joins, you may end up writing stored procedures for most of your queries, which is a lot of work and defeats part of the purpose of using L2SQL in the first place (not having to write a lot of SQL).
In comparison, the following code would always perform the filtering on the server containing the Results table:
int[] patternIds = (from r in SessionDataProvider.Instance.ResultDataContext.Results
where r.userId = 10
select r.PatternId).ToArray();
var results = from p in PatternDataContext.Patterns
where patternIds.Contains(p.PatternId)
select p;
Which is best for your situation is up to your best judgement.
Note that there is a third potential solution which I did not mention, as it is not really a programmer-solution: you could ask your server admins to set up a replication task to copy the necessary data from MyLinkedServer to MyServer once a day/week/month. This is only an option if:
Your program can work with slightly stale data from MyLinkedServer
You only need to read, never write, to MyLinkedServer
The tables you need from MyLinkedServers are not exorbitantly huge
You have the space/bandwidth available
Your database admins are not stingy/lazy
Your SimulateJoins can't work because they return IQueryable. Your current solution is exactly the same as your former one and that is the reason why you get the same exception. If you check the linked question again you will see that their helper methods return IEnumerable which is the only way to make cross context operations. As you probably already know it means that join will be performed in memory on the application server instead of the database server = it will pull all data from your partial queries and execute join as linq-to-objects.
Cross context join on database level is IMO not possible. You can have different connections, different connection strings with different servers, etc. Linq-to-sql does not handle this.
You could work around it by "escaping from" Linq to SQL on the second context, i.e., calling for instance .ToList() on ResultDataContext.Results and ResultDataContext.IclFileInfos so that your query ended up looking like:
var result = from w in PatternDataContext.Windows
join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
join r in ResultDataContext.Results.ToList()
on p.PatternId equals r.PatternId
join fi in ResultDataContext.IclFileInfos.ToList()
on r.IclFileId equals fi.IclFileId
join sp in sessionProfileDataContext.ServerProfiles on
fi.ServerProfileId equals sp.ProfileId
join u in infrastructure.Users on sp.UserId equals u.Id
where w.Process.Equals(processName)
select u.DistributedAppId;
Or AsEnumerable() as long as you "get out" of Linq to SQL and into Linq to Objects for the "offending" context.
Old question, but as I happened to have the same problem, my solution was to pass the manually crafted T-SQL cross-server query (with linked servers) directly to the provider through the ExecuteQuery method of the first context:
db.ExecuteQuery(Of cTechSupportCall)(strSql).ToList
This just saves you from having to create a view server side, and Linq to SQL still maps the results to the proper type. This is useful when there is that one query that is just impossible to formulate in Linq.

Joining a Many to Many table

I have a situation where I am providing a method to query for data in various ways. We provide the user with 4 different fitler criteria and they can mix and match as much as they want.
For example:
public Fruit GetFruit(Boolean isLocal, string storeName, string classificationType, string state);
This is simple when all of the attributes are on the table, but my issue arises as a result of complexity in the data model. Three of my attributes are simple, they are just joins, but i have one table that sits behind a one to many relationship. So in order to query for it I have to do a many to many join.
So lets say i am trying to determine all fruit a store offers. A store has a list of fruits and our classificationType sits behind a many relationship (FruitClassification)
alt text http://tinyurl.com/39q6ruj
The only successful way i have been able to query this in EF is by selecting all Fruits (by classification), and then selecting all stores that meet the filter criteria and then joining them.
You would think this query in ef would be functional:
var final = (
from s in Stores
join fc in FruitClassifications.Where(z=>z.Classifications.Code == classificationType && z.Classifications.Type.Code =="FRT").Select(x=>x.Fruit).Distinct()
on s.Fruits.Id equals f.Id
where s.Name=name && s.isLocal && s.State==state
select s
).ToList();
But it runs horrible(and looks the same when i profile it), Is there any way i can push this query down to the database? A better way to query?
I think this is what you want:
var final = (from s in Stores
where s.Name=name && s.isLocal && s.State==state
&& s.Fruits.Any(f =>
f.FruitClassifications.Any(fc => fc.Code == classificationType
&& fc.Type.Code == "FRT"))
select s).ToList();
http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/
this might help you. EF has the possibility to generate those relations with navigation properties from the designer, so you don't have to use the join.

Categories