Multiple LINQ Join conditions with differing comparison operators - c#

I am trying to build a LINQ query that will accomodate a dynamic list of predicates, but also provide multiple non-equity join conditions between two tables. The ORM I am using is Telerik Open Access/Data Access going against an Oracle database.
Here is the PL-SQL Query I am trying to build in Linq.
SELECT DISTINCT
"asset".asset_number
, "hdr".revision
, "hdr".syscfg_booth_num
, "hdr".message_id
, "hdr".msg_num
, "hdr".msg_create_date
, "hdr".msg_xmit_date
, "hdr".skytel_date
FROM xfe_rep.wi_transmits "hdr"
INNER JOIN xfe_rep.pin2pagerid "asset" ON
"hdr".pin = "asset".wireless_pager_pin
AND
"hdr".msg_create_date >= "asset".effective_start_date
AND
"hdr".msg_create_date <= "asset".effective_end_date
WHERE
"hdr".field_one = ??
AND
"hdr".field_two = ??;
The WHERE clause will be added dynamically from individual predicates provided in a list. My first pass looked something like this ...
IQueryable<WITransmits> query = from wiTransmits in ctx.WITransmits
join assetMap in ctx.AssetNumberMaps
on wiTransmits.PIN equals assetMap.PIN
where
assetMap.EffectiveStartDate <= wiTransmits.MessageCreateDate &&
assetMap.EffectiveEndDate >= wiTransmits.MessageCreateDate
select wiTransmits;
foreach (Expression<Func<WITransmits, Boolean>> filter in messagePredicate)
{
query = query.Where(filter);
}
The problem with the above code is that it raises the exception "ORA-01013: user requested cancel of current operation". I believe that this is due to the number of records being returned. I have learned from other tests that when the WHERE clause is added via both the expression and method syntax to the same query it generates multiple SQL Select statements and executes each separately against the database performing the actual join in memory instead. With millions of records this is latent and impractical, I believe giving rise to the exception.
Of course when I removed the WHERE clause from the Expression syntax only one SQL statement was created, and executes as expected.
IQueryable<WITransmits> query = from wiTransmits in ctx.WITransmits
join assetMap in ctx.AssetNumberMaps
on wiTransmits.PIN equals assetMap.PIN
select wiTransmits;
foreach (Expression<Func<WITransmits, Boolean>> filter in messagePredicate)
{
query = query.Where(filter);
}
But, of course I lose the JOIN filters on the dates. The other alternative I had considered was to add multiple conditions to the JOIN. This, however, only allows me to compare for EQUALITY between two object definitons. I need to compare each property with a different comparison operator ( ==, >=, <= ) though ... so again this does not work.
IQueryable<WITransmits> query = from wiTransmits in ctx.WITransmits
join assetMap in ctx.AssetNumberMaps on
new { wiTransmits.PIN, wiTransmits.MessageCreateDate, wiTransmits.MessageCreateDate } equals
new { assetMap.PIN, assetMap.EffectiveStartDate, assetMap.EffectiveEndDate }
select wiTransmits;
foreach (Expression<Func<WITransmits, Boolean>> filter in messagePredicate)
{
query = query.Where(filter);
}
The final thought I had was to simply add the conditions as a couple of additional WHERE filters. The problem here is that the WHERE clause is between two table values and not a static value provided by the consumer. The query does not return a Queryable typed for both tables involved in the comparison so this will not work either.
IQueryable<WITransmits> query = from wiTransmits in ctx.WITransmits
join assetMap in ctx.AssetNumberMaps
on wiTransmits.PIN equals assetMap.PIN
select wiTransmits;
var joinPredicate = new List<Expression<Func<WITransmits, AssetNumberMaps, Boolean>>>();
joinPredicate.Add((wiTransmits, assetMap) => assetMap.EffectiveStartDate <= wiTransmits.MessageCreateDate);
joinPredicate.Add((wiTransmits, assetMap) => assetMap.EffectiveEndDate >= wiTransmits.MessageCreateDate);
foreach (Expression<Func<WITransmits, AssetNumberMaps, Boolean>> filter in joinPredicate)
{
query = query.Where(filter); // DOES NOT WORK
}
foreach (Expression<Func<WITransmits, Boolean>> filter in messagePredicate)
{
query = query.Where(filter);
}
Anybody have any ideas on how to do this? I am fresh out of direction ....

Related

SQL query in linq to sql, group by trouble

The idea here is to add up all the ingredients from all of the recipes. This is my query
SELECT IngredientName,sum(Amount) as Amount,Unit
FROM RecipeIngredients Join Ingredients ON RecipeIngredients.IngredientID = Ingredients.IngredientsID
GROUP BY IngredientID,IngredientName,Unit
ORDER BY IngredientName
It works great. No issues. I'm getting confused with the linq to sql syntax.
This is what I have so far.
using (lDataContext db = new lDataContext())
{
return (from ri in db.RecipeIngredients join i in db.Ingredients on ri.IngredientID equals i.IngredientsID
group new {ri,i} by new {i.IngredientsID,i.IngredientName,ri.Unit }
into table
select new {
Name = table.IngredientName
Unit = table.Unit
Amount = table.Sum(i.amount) }
).ToList();
}
Can somone please help with my syntax?
Assuming you are not using LINQ to Entities but just LINQ to SQL,
For translating SQL to LINQ,
Translate subselects as separate variables
Translate each clause in LINQ clause order, leaving monadic operators (DISTINCT, TOP, etc) as functions applied to the whole LINQ query.
Use table aliases as range variables. Use column aliases as anonymous type field names.
Use anonymous types (new { }) for multiple columns
Left Join is simulated by using a join variable and doing another from from the join variable followed by .DefaultIfEmpty().
Replace COALESCE with the conditional operator and a null test.
SELECT * must be replaced with select range variable or for joins, an anonymous object containing all the range variables.
SELECT flds must be replaced with select new { ... } creating an anonymous object with all the desired fields or expressions.

Entity Framework LINQ to Entities Join Query Timeout

I am executing the following LINQ to Entities query but it is stuck and does not return response until timeout. I executed the same query on SQL Server and it return 92000 in 3 sec.
var query = (from r in WinCtx.PartsRoutings
join s in WinCtx.Tab_Processes on r.ProcessName equals s.ProcessName
join p in WinCtx.Tab_Parts on r.CustPartNum equals p.CustPartNum
select new { r}).ToList();
SQL Generated:
SELECT [ I omitted columns]
FROM [dbo].[PartsRouting] AS [Extent1]
INNER JOIN [dbo].[Tab_Processes] AS [Extent2] ON ([Extent1].[ProcessName] = [Extent2].[ProcessName]) OR (([Extent1].[ProcessName] IS NULL) AND ([Extent2].[ProcessName] IS NULL))
INNER JOIN [dbo].[Tab_Parts] AS [Extent3] ON ([Extent1].[CustPartNum] = [Extent3].[CustPartNum]) OR (([Extent1].[CustPartNum] IS NULL) AND ([Extent3].[CustPartNum] IS NULL))
PartsRouting Table has 100,000+ records, Parts = 15000+, Processes = 200.
I tried too many things found online but nothing worked for me as to how I can achieve the result with same performance of SQL.
Based on the comments, looks like the issue is caused by the additional OR with IS NULL conditions in joins generated by the EF SQL translator. They were added in EF in order to emulate the C# == operator semantics which are different from SQL = for NULL values.
You can start by turning that EF behavior off through UseDatabaseNullSemantics property (it's false by default):
WinCtx.Configuration.UseDatabaseNullSemantics = true;
Unfortunately that's not enough, because it fixes the normal comparison operators, but they simply forgot to do the same for join conditions.
In case you are using joins just for filtering (as it seems), you can replace them with LINQ Any conditions which translates to SQL EXISTS and nowadays database query optimizers are treating it the same way as if it was an inner join:
var query = (from r in WinCtx.PartsRoutings
where WinCtx.Tab_Processes.Any(s => r.ProcessName == s.ProcessName)
where WinCtx.Tab_Parts.Any(p => r.CustPartNum == p.CustPartNum)
select new { r }).ToList();
You might also consider using just select r since creating anonymous type with single property just introdeces additional memory overhead with no advantages.
Update: Looking at the latest comment, you do need fields from joined tables (that's why it's important to not omit relevant parts of the query in question). In such case, you could try the alternative join syntax with where clauses:
WinCtx.Configuration.UseDatabaseNullSemantics = true;
var query = (from r in WinCtx.PartsRoutings
from s in WinCtx.Tab_Processes where r.ProcessName == s.ProcessName
from p in WinCtx.Tab_Parts where r.CustPartNum == p.CustPartNum
select new { r, s.Foo, p.Bar }).ToList();

Linq to SQL Slow Query

My ASP.Net application has the following Linq to SQL function to get a distinct list of height values from the product table.
public static List<string> getHeightList(string catID)
{
using (CategoriesClassesDataContext db = new CategoriesClassesDataContext())
{
var heightTable = (from p in db.Products
join cp in db.CatProducts on p.ProductID equals cp.ProductID
where p.Enabled == true && (p.CaseOnly == null || p.CaseOnly == false) && cp.CatID == catID
select new { Height = p.Height, sort = Convert.ToDecimal(p.Height.Replace("\"", "")) }).Distinct().OrderBy(s => s.sort);
List<string> heightList = new List<string>();
foreach (var s in heightTable)
{
heightList.Add(s.Height.ToString());
}
return heightList;
}
}
I ran Redgate SQL Monitor which shows that this query is using a lot of resources.
Redgate is also showing that I am running the following query:
select count(distinct [height]) from product p
join catproduct cp on p.productid = cp.productid
join cat c on cp.catid = c.catid
where p.enabled=1 and p.displayfilter = 1 and c.catid = 'C2-14'
My questions are:
A suggestion to change the function so that it uses less resources?
Also, how does linq to sql generate the above query from my function? (I did not write select count(distinct [height]) from product anywhere in the code)
There are 90,000 records in the products. This category which I am trying to get the distinct list of heights has 50,000 product records
Thank you in advance,
Nick
First of all your posted sql query and linq query doesn't match at all. it's not the LINQ query rather the underlying SQL query itself performing slow. Make sure, all the columns involved in JOIN ON clause and WHERE clause and ORDER BY clause are indexed properly in order to have a better execution plan; else you will end up getting a FULL Table Scan and a File Sort and query will deemed to perform slow.
The join multiplies the number of Products the query returns. To undo that, you apply Distinct at the end. It will certainly reduce db resources if you return unique Products right away:
var heightTable = (from p in db.Products
where p.CatProducts.Any(cp => cp.CatID == catID)
&& p.Enabled && (p.CaseOnly == null || !p.CaseOnly)
select new
{
Height = p.Height,
sort = Convert.ToDecimal(p.Height.Replace("\"", ""))
}).OrderBy(s => s.sort);
This changes the join into a where clause. It saves the db engine the trouble of deduplicating the result.
If that still performs poorly, you should try to do the conversion and ordering in memory, i.e. after receiving the raw results from the database.
As for the count. I don't know where it comes from. Such queries typically get generated by paging libraries such as PagedList, but I see no trace of that in your code.
Side note: you can return ...
heightList.Select(x => x.Height.ToString()).ToList()
... instead of creating the list yourself.

Unable to retrieve record from join table & conditional where clause

My case description:
In my C# and LINQ to SQL application, I am implementing FeserWard.Controls Intellibox. For a sales of handphone, user will type in handphone's IMEI in the intellibox and the box will do search in Table Handphone to look for the user input IMEI and finally display the exact match IMEI record.
Problem: I want to filter out all the (Handphone.IMEI) with status=Available (Item.I_Status="Available"), and from there, when user typing in IMEI, the intellibox list will do search only from the Available IMEI.
SQL
select h.HP_ID, h.IMEI, h.Colour, i.I_Status
from Item i, Handphone h
where i.I_ID = h.HP_ID AND i.I_Status='Available'
I want to replace IEnumerable DoSearch's LINQ with this but stuck.
var availableIMEISearch = from i in dataContext.Items.ToList()
join h in dataContext.Handphones.ToList()
on i.I_ID equals h.HP_ID
where(h.IMEI.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase)) && (i.I_Status=="Available")
select new { i, h };
return availableIMEISearch;
Current workable method: IEnumerable DoSearch
DataClasses1DataContext dataContext = new DataClasses1DataContext();
public IEnumerable DoSearch(string searchTerm, int maxResults, object extraInfo)
{
var imeiSearch = dataContext.Handphones.ToList()
.Where(h => h.IMEI.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase));
return imeiSearch;
}
Table1:
Item (PK = I_ID)
Table2:
Handphone (PK,FK1 = HP_ID), where Item.I_ID = Handphone.HP_ID
As Joh Skeet(A SO Hero) mentioned, you should not use ToList in query, because it will promt query to be executed, and you will be fetching full tables data to client joining them on client site. Let Linq generate query expression with join, and send it to DB in order to make join on DB side, delayed execution. Less sideeffects.
You didn't mentioned whether you need case sensitive search of searchterm, but I assume that insensitive is much more convenient, so just remove StringComparison.
As you only need Handphones info -> better exclude i from select term, select only what you will use.
And the last add ToList to return result in order to fetch data.
So here is your new query:
var availableIMEISearch = from i in dataContext.Items
join h in dataContext.Handphones
on i.I_ID equals h.HP_ID
where(h.IMEI.StartsWith(searchTerm) && (i.I_Status=="Available")
select h;
return availableIMEISearch.ToList();
If that still doesn't help.
Verify that you have the data in both tables with join condition;
Open SSMS, run the SQL Server Profiler and chech the query generated. Try to run that query manually to find out whether the data is fetched.

Removing the WHERE clause in a Linq query

I have a table of product information with many bit columns. This table can be queried from an interface which has a checkbox for each column. The checkboxes are grouped into several related groups.
For example, three of the columns describe the products suitability for various markets, being Automotive, Aviation and Marine.
If none of these checkboxes are checked, I would like the following SQL to be executed.
SELECT * FROM Products
if Automotive was checked the following SQL should be executed
SELECT * FROM Products WHERE Automotive = 1
and if more than one is checked I would like the options to be OR'd together
SELECT * FROM Products WHERE
Automotive = 1
OR
Aviation = 1
In good old C# & SQL I could achieve this logic by conditionally concating the SQL together but I'm having trouble producing the same logic with Linq.
My problem is how do I conditionally add the WHERE clause and the elements of it to my query.
I'd prefer to only have a single point where the query is executed so, if possible, I would like to avoid using C# if's to branch into different queries.
I would use a PredicateBuilder for this.
var query = db.GetTable<Products>().AsQueryable();
var predicate = PredicateBuilder.False<Products>();
if (automotive.Checked)
{
predicate = predicate.Or( p => p.Automotive == 1 );
}
if (aviation.Checked)
{
predicate = predicate.Or( p => p.Aviation == 1 );
}
query = query.Where( predicate );
Having the values be an "Or" vs. an "And" makes this problem a bit more difficult. If it were "And" then the following should do fine
public IEnumerable<Products> GetQuery() {
var query = db.GetTable<Products>();
if ( automotiveBox.Checked ) {
query = query.Where(x => x.Automotive == 1);
}
if ( aviation.Checked ) {
query = query.Where(x => x.Aviation == 1);
}
return query
}
With it being an "Or" though I think your only option is to build up the expression tree by hand and use it as a Where clause.
If you're trying to "OR" statements together that check a single column, you can use Contains to accomplish this. Here's a good blog post showing how this works.
However, if you're trying to dynamically check different columns, this will not work. In that case, multiple statements will probably be required.

Categories