Linq query with multiple OR conditions in multiple joins - c#

I have gone through a number of answers but haven't found anything that helps me with this. I understand that the "OR" part should happen in the Where clause, not the join itself, but unfortunately that doesn't look like a possibility for my query.
With these two tables, drastically simplified for this question:
Claim: FKeyID int, CKeyID int, ICode varchar(100), IName varchar(100)
Lookup: LFKeyID int, LCKeyID int, LICode varchar(100), LIName varchar(100)
I need to do the following join in linq method syntax:
select * from Claim c
left join Lookup l on
(l.LFKeyID = c.FKeyID OR l.LFKeyID = 0)
AND (l.LCKeyID = c.CKeyID OR l.LCKeyID = 0)
AND (l.LICode = c.ICode OR l.LIName = c.IName)
I've tried the following, but it's only joining on the initial key and doesn't have the OR clause for the lookup key = 0. I'm completely stumped.
query = _context.Claim.Join(_context.Lookup,
clm => clm.FKeyID, lk => lk.LFKeyID, (clm, lk) => new
{ ... fields go here ... })
.DefaultIfEmpty().ToList();
In case anyone else is trying to accomplish this using method syntax, here is the solution thanks to the comment given:
var query = _context.Claim.Where(i => (various initial filter criteria))
.SelectMany(clm => _context.Lookup, (clm, lk) = new { clm, lk })
.Where(lk => (lk.LFKeyID == clm.FKeyID || lk.LFKeyID == 0)
&& (lk.LCKeyID == clm.CKeyID || lk.LCKeyID == 0)
&& (lk.LICode == clm.ICode || lk.LIName == clm.IName))
.DefaultIfEmpty()
.Select(s => new
{
// list of fields
}).ToList();

You can use other technique for LEFT JOIN described in Complex Query Operators
var query =
from clm in _context.Claim
from lk in _context.Lookup
.Where(lk => (lk.LFKeyID == clm.FKeyID || lk.LFKeyID == 0)
&& (lk.LCKeyID == clm.CKeyID || lk.LCKeyID == 0)
&& (lk.LICode == clm.ICode || lk.LIName == clm.IName))
.DefaultIfEmpty()
select new
{
clm, // or specify fields precisely
lk
};

Related

The LINQ expression could not be translated - EF Core

In summary, I'm guessing I can't add any more complex calculations to the LINQ expression. Any tips are greatly appreciated!
This blazor project is using a messy employee table which contains two types of employees, both on the same table
Domestic employees, uses NRG number to identify them, but their NRG numbers are stored as string at NRG column, like "0356".
Foreign employees, also uses NRG to identify them, but their NRG column contains all NULL, their NRG numbers are inside their emails at AzureEmail column, like "johndoe.0356#aaa-bbb.com"
When domestic employee or foreign employee enter their sales records, they are the "Closer", it is required to enter the "Setter" NRG.
By using the "Setter" NRG number "closer" entered, I want to locate the "Setter" info from the same employee table:
public async Task Save_to_SalesForm()
{
await using var context3 = await DBContextFactory.CreateDbContextAsync();
{
if (salesForm.SetterNrg != null && salesForm.CsTransferCategory == "Local Team")
{
setterEmployee = context3.Employees.Where(
e => e.AzureAccountEnabled == 1
&&
(int?)(object?)e.Nrg == salesForm.SetterNrg
).OrderByDescending(e => e.EmployeeId).FirstOrDefault();
salesForm.SetterAgentFullName = setterEmployee.AzureFullName;
salesForm.SetterJobTitle = setterEmployee.AzureRole;
salesForm.SetterEmail = setterEmployee.AzureEmail;
salesForm.SetterTeam = setterEmployee.AzureTeam;
}
if (salesForm.SetterNrg != null && salesForm.CsTransferCategory == "CSR Team (Philippines)")
{
setterEmployee = context3.Employees.Where(
e => e.Nrg == null
&&
e.AzureAccountEnabled == 1
&&
e.AzureEmail.Contains("#aaa-bbb.com")
&&
(int?)(object?)e.AzureEmail.Split(new char[] { '.', '#' }, StringSplitOptions.RemoveEmptyEntries)[1] == salesForm.SetterNrg
).OrderByDescending(e => e.EmployeeId).FirstOrDefault();
salesForm.SetterAgentFullName = setterEmployee.AzureFullName;
salesForm.SetterJobTitle = setterEmployee.AzureRole;
salesForm.SetterEmail = setterEmployee.AzureEmail;
salesForm.SetterTeam = setterEmployee.AzureTeam;
}
}
context3.SalesForms.Add(salesForm);
await context3.SaveChangesAsync();
}
If the "Setter" is a domestic employee (Local Team), the above query works fine and be able to save the setter info to the table
If the "Setter" is a foreign employee (CSR Team (Philippines)), the above query won't work due to the .Split make the query too complex for LINQ expression. Error screenshot
I tried multiple ways to resolve the issue, but none seemed ideal.
I have rewritten your query to use EndsWith, which is translatable to the SQL:
public async Task Save_to_SalesForm()
{
await using var context3 = await DBContextFactory.CreateDbContextAsync();
if (salesForm.SetterNrg != null)
{
Employee? setterEmployee = null;
if (salesForm.CsTransferCategory == "Local Team")
{
setterEmployee = await context3.Employees
.Where(e => e.AzureAccountEnabled == 1
&& (int?)(object?)e.Nrg == salesForm.SetterNrg)
.OrderByDescending(e => e.EmployeeId)
.FirstOrDefaultAsync();
}
else if (salesForm.CsTransferCategory == "CSR Team (Philippines)")
{
var toCheck = $".{salesForm.SetterNrg}#aaa-bbb.com";
setterEmployee = await context3.Employees
.Where(e => e.Nrg == null && e.AzureAccountEnabled == 1
&& e.AzureEmail.EndsWith(toCheck))
.OrderByDescending(e => e.EmployeeId)
.FirstOrDefaultAsync();
}
if (setterEmployee != null)
{
salesForm.SetterAgentFullName = setterEmployee.AzureFullName;
salesForm.SetterJobTitle = setterEmployee.AzureRole;
salesForm.SetterEmail = setterEmployee.AzureEmail;
salesForm.SetterTeam = setterEmployee.AzureTeam;
}
}
context3.SalesForms.Add(salesForm);
await context3.SaveChangesAsync();
}
The problem is in e.AzureEmail.Contains("#aaa-bbb.com"), there is no equivalent in sql to this. Try EF.Functions.Like(e.AzureEmail, "%#aaa-bbb.com%"). Everything from your expression will work if you materialize your data with .ToList() or something and perform it on the client, but it is extremely inefficient.

Combine two list using Linq and add data as needed from different tables

I need to change a process and have been struggling with it for a couple of days now.
The current task checks for all digits entered by the user in Table1. I don't have an issue with that since I can return it with this statement:
var itemsTable1 = db.Table1.Where(a =>
searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidFlag == 1
).ToList();
Now I need to look for the same digits on Table2 and make sure I bring those numbers as well. Although the tables will have the same columns for digits, they will not have the same number of columns in total. I could just right another statement as above for Table2, no problem there. However, I also need to bring the records that do not contain the digits but have the same Ids. So, my scenarios would be something like this:
Table1 = contains digits -> Table2 != contains digits
Table2 = contains digits -> Table1 != contains digits
Table1 = contains digits -> Table2 = contains digits
Finally, I need to display the data on either list in a descending order, which I assume, I'd would have to combine the two/three lists and return it to the model.
Is there a way of doing this with plain Linq? Or am I better off creating maybe a CTE in a stored procedure and pass the parameters there and then calling in the EF?
I assume you need this query:
var query =
from t1 in db.Table1
join t2 in db.Table2 on t1.Id equals t1.Id
let t1Contains = searchNumbers.Contains(t1.Digit1)
|| searchNumbers.Contains(t1.Digit2)
|| searchNumbers.Contains(t1.Digit3)
|| searchNumbers.Contains(t1.Digit4)
|| searchNumbers.Contains(t1.Digit5)
|| _Digit6 == t1.Digit6 && t1.ValidFlag == 1
let t2Contains = searchNumbers.Contains(t2.Digit1)
|| searchNumbers.Contains(t2.Digit2)
|| searchNumbers.Contains(t2.Digit3)
|| searchNumbers.Contains(t2.Digit4)
|| searchNumbers.Contains(t2.Digit5)
|| _Digit6 == t2.Digit6 && t2.ValidFlag == 1
where t1Contains != t2Contains || t1Contains && t2Contains
select
{
t1,
t2
};
Note, that you have not specified desired output and how to order result.
Following #Svyatoslav Danyliv suggestion. I have created the following:
//By using the list, we make sure that the search returns every single digit, regardless of position they occupy in the DB
var itemsT1 = db.Table1.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);
var itemsT2 = db.Table2.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);
//Create list to hold Ids from the records above
List<int?> t1Ids = new List<int?>();
List<int?> t2Ids = new List<int?>();
//Insert the Ids into the lists
foreach (var t1Id in t1Ids )
{
t1Ids.Add((int)t1Id.Id);
}
foreach (var t2Id in t2Ids)
{
t2Ids.Add((int)t2Id.Id);
}
//Get the records from opposite table that contains same Ids
var resultT1 = db.Table1.Where(r => t1Ids.Contains(r.Id)
);
var resultT2 = db.Table2.Where(r => t2Ids.Contains(r.Id)
);
//Combine the lists to pass to the view
var groupedT1 = itemsT1.Concat(resultT1).Distinct();
var groupedT2 = itemsT2.Concat(resultT2).Distinct();
using (db)
{
var vmT1T2 = new ViewModelTables
{
getTable1 = groupedT2.ToList(),
getTable2 = groupedT2.ToList()
};
return View(vmT1T2);
}
It worked out perfectly as far as bring the records that I needed.
Once again, thank you #Svyatoslav Danyliv for pointing me in the right direction. I appreciate and hope this can help someone else as well.

How to avoid multiple "ToLower" in this Linq query?

I am making a call to my database via Linq in which I am trying to filter the data which the database returns.
However, my query is ending up with multiple does not contains check in which each instance of the property has to be separately converted to lower case so that it is case insensitive.
I am not entirely sure if there is a performance impact but if there is a way to better frame the query or to optimize it.
I surely would like to factor that in.
var accounts = context.Accounts
.Where(x => !x.type.ToLower().Contains("distribution")
&& !x.type.ToLower().Contains("bonus")
&& !x.type.ToLower().Contains("dividend")
&& !x.type.ToLower().Contains("redemption")
&& !x.type.ToLower().Contains("institutional")
&& !x.type.ToLower().Contains("unclaimed")
&& !x.type.ToLower().Contains("segregated")
&& !x.type.ToLower().Contains("discontinued")
&& !x.type.ToLower().Contains("retail")
&& !x.type.ToLower().Contains("cumulative")
&& !x.type.ToLower().Contains("monthly payment option")
&& !x.type.ToLower().Contains("payout")
&& !x.type.ToLower().Contains("withheld")
&& !x.type.ToLower().Contains("pf")
&& !x.type.ToLower().Contains(" p f "))
.ToList();
You can put all your asserted strings in a collection and take advantage of the linq All method. Something like this:
private static readonly string[] Filters = new []
{
"distribution",
"bonus",
"dividend",
"redemption",
"institutional",
"unclaimed",
"segregated",
"discontinued",
"retail",
"cumulative",
"monthly payment option",
"payout",
"withheld",
"pf",
" p f "
};
var accounts = context.Accounts.Where(x => Filters.All(f => !x.ToLower().Contains(f)));
Or more optimized:
var accounts = context.Accounts.Where(x => Filters.All(f => !x.Contains(f, StringComparer.CurrentCultureIgnoreCase)));

Convert SQL query to LINQ or lambda expression in C# and use in EF Core

I have 3 table
Tbl_City , Tbl_GroupCities , Tbl_CtrCar .
I want to convert this SQL query to LINQ or lambda expression in C#
declare #fk_group uniqueidentifier
SELECT #fk_group= FK_Group
FROM dbo.Tbl_User
WHERE UserName='meysam'
SELECT dbo.Tbl_City.ID_City, dbo.Tbl_City.Name_City,COUNT( dbo.Tbl_CtrCar.Cur_year)
FROM dbo.Tbl_City
INNER JOIN dbo.Tbl_CtrCar ON dbo.Tbl_City.ID_City = dbo.Tbl_CtrCar.FK_City
WHERE ID_City IN (SELECT FK_City
FROM dbo.Tbl_GroupCities
WHERE Active=1 AND ID_Group=#fk_group)
GROUP BY ID_City , Name_City
I try it but it's not work
var model = _TblUser.FirstOrDefault(x => x.UserName == "sampleUserName");
var q = _TblGroupCities.Where(x => x.IdGroup == model.FkGroup && x.Active == true);
var sample2 =
(from x in _TblCity
join a in _TblGroupCities on x.IdCity equals a.FkCity
where a.Active == true && a.IdGroup == model.FkGroup
select new
{
x.IdCity,
x.NameCity
}).ToList();
Please take a look here the features you have in your query are not yet implemented. GroupBy and i think also subselects will do an
SELECT * FROM TableName
And in memory it will do the group by or even for each row a new SQL query.
Better to use the RawSql method for this purpose.
But if you realy want to learn LINQ and convert your SQL take a look at LINQPad
This issue is done. I found my problem, I don't Understand use two joins and use group by in Linq
I use this linq for the solution and run
var model = _TblUser.SingleOrDefault(x => x.UserName == type.UserName);
var q = _TblGroupCities.Where(x => x.IdGroup == model.FkGroup && x.Active == true);
tblCityViewModel = new List<MohasebKhodro.ViewModels.TblCityViewModel>();
var sample2 =
(from x in _TblCity
join a in _TblGroupCities on x.IdCity equals a.FkCity
where a.Active == true && a.IdGroup == model.FkGroup
select new
{
x.IdCity,
x.NameCity
}).ToList();
foreach (var item in sample2)
{
var er = _TblCtrCar.Where(x => x.FkCity == item.IdCity).Max(x => x.CurYear);
tblCityViewModel.Add(new MohasebKhodro.ViewModels.TblCityViewModel
{
IdCity = item.IdCity,
NameCity = item.NameCity,
MaxCurrentYear = Convert.ToString(er)
});
}

Linq to Entities Where clause compare value that can be int or string

I have a drop down list that will provide either a numeric or the word ANY. I need to create a LINQ SELECT containing a WHERE clause that can mimic the following SQL:
var p varchar2(3);
select ... from ...
where (
( (:p = 'ANY') and id in (select distinct id from Ids) )
or
(:p='1' and id = 42)
)
ps: I will be using an expression tree to handle the OR aspect :-)
Somthing like this?
string input = /***/
var result = Context.Entities
.Where(ent => (input == "ANY"
&& Context.UserIds.Select(usr => isr.Id)
.Distinct()
.Contains(ent.Id))
|| (input == "1" && ent.Id == 42))
.Select(ent => /***/);
Disclaimer: written from memory, can contain compile-time errors (typo mistakes etc)

Categories