Linq .Except behavior not as expected - asp.net-core

I have two tables: tableA and tableB, both have an attribute "CommissionNumber" which contains strings in the form of D123456789 (one letter followed by a fixed number of digits).
I need to find the commissionnumbers in table A, that are not in table B.
In SQL, this could look like:
SELECT *
FROM tableA
WHERE CommissionNumber NOT IN
(
select CommissionNumber from tableB
)
Which gives me no results. However, if i try this:
var tableA= dbContext.tableA.Select(x => x.CommissionNumber).ToList();
var tableB= dbContext.tableB.Select(x => x.CommissionNumber).ToList();
IEnumerable<string> missingFiles = tableA.Except(tableB);
I get 92 hits. I don't understand what's wrong, my SQL query of the use of the .Except function.
Any ideas?

You have created two LINQ queries which retrieves all data from two tables into the memory and applied Except. It is wrong if you care about performance.
If you want to create SQL in the same way, LINQ query should be written accordingly. LINQ equivalent for IN operator is Contains.
var query = dbContext.tableA
.Where(x => !dbContext.tableB.Select(b => b.CommissionNumber).Contains(x.CommissionNumber));
Or by EXISTS which have analogue Any
var query = dbContext.tableA
.Where(x => !dbContext.tableB.Any(b => b.CommissionNumber == x.CommissionNumber));
Also the same result you can achieve by LEFT JOIN
var query =
from a in dbContext.tableA
join b in dbContext.tableB on a.CommissionNumber equals b.CommissionNumber into gj
from b in gj.DefaultIfEmpty()
where b.CommissionNumber == null
select a;

gsharp was on the right track. I had some case sensitivity issues in my data which was ignored in the native SQL query but taken seriously by EF core.

Related

Slick function for SQL exists

I want to construct an SQL along this but try not to use sqlu.
select el.oid, el.name, el.res_cat from el
left join bk on (el.cat = bk.cat and bk.oid=100)
where not exists (select 1 from dates bd where
el.oid=bd.lots_oid and bd.bk_oid = bk.oid) and el.e_oid=bk.e_oid
Are there Slick functions for SQL exists or not exists? Thanks
Update 1
I realized my mistakes when I revisited my Slick code again. I want to apologize for the false alarm I set. This is not an answer and hopefully someone can help me to rectify my mistakes. For the time being, I am using Slick's plain SQL to continue my work.
The Slick query I constructed didn't work. It was close to the SQL I wanted. What I did was,
val elQuery = elTable.joinLeft(bkTable)
.on((el, bk) => el.cat === bk.cat && bk.oid === 100)
val query = for {
a <- elQuery if bdTable.filterNot(bd => a._2.map(_.oid === bd.bkOid).isDefined && a._1.oid === bd.elOid).exists
} yield a
finalQuery.result.statements.foreach(x => Logger.debug(s"xx => $x"))
I notice filterNot does not generate a SQL not exists. This is the other portion that lost me.
I don't have enough reputation to make comment yet. But I assume that you want to get all rows that doesn't exit in dates table. I would rewrite your query like below:
SELECT
el.oid, el.name, el.res_cat.cat
FROM
el
LEFT JOIN bk ON bk.cat = el.cat
AND bk.e_oid = el.e_oid
AND bk.oid = 100
LEFT JOIN dates bd ON bd.lots_oid = el.oid
AND bd.bk_oid = bk.oid
WHERE
bd.lots_oid IS NULL
Explanation:
Instead of taking NOT EXISTS, you can achieve the same thing by LEFT JOIN dates and specify on WHERE condition that the primary key (PK) for dates IS NULL. I don't know the PK for dates table, so I just add the column I know. You should adjust it to the PK of dates table.
LEFT JOINing and WHERE PK IS NULL ensures you that the row doesn't exist on the left joined table.

nhibernate group by and join query

I need nhiberante query (not HQL) equivalent following SQL:
SELECT ur.*
FROM (SELECT MAX(requestTime) rt, macAddress ma
FROM UpdateRequests
GROUP BY macAddress) mur
JOIN dbo.UpdateRequests ur
ON mur.ma = ur.macAddress AND mur.rt = ur.requestTime
I had no luck with other similar examples on stackoverflow.
Having UpdateRequest mapping, it seems that is not possible with Query API, how about QueryOver?
Finally one Guru suggested me to change SQL query without changing execution plan:
SELECT ur.*
FROM [dbo].[UpdateRequests] AS ur
WHERE ur.[RequestTime] = (SELECT MAX(mur.[RequestTime])
FROM [dbo].[UpdateRequests] mur
WHERE mur.[MacAddress] = ur.[MacAddress])
So in code it transforms into:
session
.Query<UpdateRequest>()
.Where(ur => ur.RequestTime == session.Query<UpdateRequest>()
.Where(mur => mur.MacAddress == ur.MacAddress)
.Max(mur => mur.RequestTime))
.ToList();
And this is exactly what i need.

Create a LINQ for SQL query

I'm learning Linq and using MVC. I have written a SQL query which I need to convert to a LINQ query.
select TokenID,TokenAsset,packet from TokenTable where id = 6 and packet = ''
and TokenID not in (select TokenID from TokenTable where id=6 and packet <> '')
group by TokenID,TokenAsset,Packet
I kindly ask help to convert the above query to a LINQ query. I know that the SQL query isn't efficient. It would better if you can help me to fix it.
Try this one:
var result = Tokens.Where(x=>x.Id==6 &&
x.Packet=="" &&
!Tokens.Exists(y=>y.TokenID==x.TokenID &&
y.Id==6 &&
y.Packet!="")
)
.GroupBy(x=>x.ID)
.ThenGroupBy(x=>x.TokenAsset)
.ThenGroupBy(x=>x.Packet);
Note I suppose that collection Tokens holds all the tokens you have.
Firstly your SQL query can just be
select distinct TokenID, TokenAsset, packet
from TokenTable
where id = 6 and packet = ''
the group by is not that useful since there are no aggregated columns. All selected columns are in the group by clause. Use distinct to achieve the same.
the secondary AND condition for tokenid is also redundant. It is exclusive to the first condition and hence doesn't change the result.
use this LINQ query:
var results = dbcontext.TokenTables
.Where(t => t.id == 6 && t.Packet == "")
.Select(t => new { t.TokenId, t.TokenAsset, t.Packet }).Distinct();
project only columns you need for performant calls by avoiding extra data transfer.

LINQ not returning all child records

I have a query in the DB:
SELECT GreenInventoryBlendGradeID,bgx.blendgradeid,
bgX.GreenBlendGradeTypeID,[Description]
FROM [GreenInventory] gi
INNER JOIN [GreenInventoryBlendGradeXref] bgX
ON bgX.[GreenInventoryID] = gi.[GreenInventoryID]
INNER JOIN [BlendGrade] bg
ON bg.[BlendGradeID]=bgx.[BlendGradeID]
That returns 3 records:
TypeID Desc
1 XR
2 XR
1 XF2
The LINQ:
var GreenInventory = (from g in Session.GreenInventory
.Include("GreenInventoryBlendGradeXref")
.Include("GreenInventoryBlendGradeXref.BlendGrade")
.Include("GreenInventoryBlendGradeXref.GreenBlendGradeType")
.Include("GreenInventoryWeightXref")
.Where(x => x.GreenInventoryID == id && x.GreenInventoryBlendGradeXref.Any(bg=>bg.GreenBlendGradeTypeID > 0) )
select g);
I have tried different Where clauses including the simple - (x => x.GreenInventoryID == id)
but always have only the first 2 records returned.
Any Ideas?
If I try the following:
var GreenInventory = (from gi in Session.GreenInventory.Where(y => y.GreenInventoryID == id)
join bgX in Session.GreenInventoryBlendGradeXref.DefaultIfEmpty() on gi.GreenInventoryID equals bgX.GreenInventoryID
join bg in Session.BlendGrade.DefaultIfEmpty() on bgX.BlendGradeID equals g.BlendGradeID
select new { GreenInventory = gi, GreenInventoryBlendGradeXref = bgX, BlendGrade = bg });
I Get back 3 of each objects and the correct information is in the BlendGrade objects. It looks like the 3 GreenInventory objects are the same. They each include 2 of the GreenInventoryBlendGradeXref objects which show the the same 2 records as before.
So I not clear on what the original problem was. Also dont know if this is the best way to resolve it.
Thanks for the answers. If anyone has a further thoughts please let us know.
Based on the few details you present, I would assume that you are missing a join. I have no experience with EntityFramework (I assume that you use this ORM), but as far as I know, the ".Include" tries to ensure that the set of root entities will not change and will not contain duplicates.
Your manually created query seems to indicate that there is at least one 1:n relationship in the model. The result you get from LINQ show that only distinct GreenInventory entities are returned.
Therefore you need to adjust your query and explicitly declare that you want all results (and not only distinct root entities) - I would assume that with an explicit join EntityFramework will yield all expected results - or you need to adjust your mapping.
The first place I'd look in would be your model and joins you have defined between the entities. You might also want to check your generated SQL statement:
Trace.WriteLine(GreenInventory.Provider.ToString())
or use Visual Studio IntelliTrace to investigate what was sent to the database.

How to use SQL Count() in QueryOver

I'm trying to perform some simple SQL (using sql server 2005) using QueryOver in NHibernate. The query is to count how many times a serial number is repeatedly used in a list of items and then to select the serial numbers with only 1 use. Note that I don't want distinct serial numbers since I don't want to use serial numbers that have >1 use.
The SQL query to do this is:
SELECT SERNUM, expr1
FROM (SELECT SERNUM, COUNT(SERNUM) AS expr1
FROM ITEM
GROUP BY SERNUM) AS derivedtbl_1
WHERE (expr1 = 1)
What I have done so far using QueryOver is:
var query = session.QueryOver<Item>()
.Select(Projections.ProjectionList()
.Add(Projections.Count<Item>(x => x.Sernum))
.Add(Projections.GroupProperty("Sernum"))).List();
This code generates SQL (though it does not like putting the Count() column to a List). I'm not sure how to access the column of data generated by Count() to say only return where it is 1. The SQL that it does generates is:
SELECT count(this_.SERNUM) as y0_,
this_.SERNUM as y1_
FROM ITEM this_
GROUP BY this_.SERNUM
I'm not sure if I'm approaching this the right way but hopefully someone can point me to it.
var serials = session.QueryOver<Item>()
.Where(Restrictions.Eq(Projections.Count<Item>(i => i.Id), 1));
.Select(Projections.GroupProperty<Item>(i => i.Sernum))
.List();
should generate something like
SELECT SERNUM FROM ITEM GROUP BY SERNUM HAVING COUNT(*) = 1
to get the items, do something like
var subquery = QueryOver.Of<Item>()
.Where(Restrictions.Eq(Projections.Count<Item>(i => i.Id), 1));
.Select(Projections.GroupProperty<Item>(i => i.Sernum));
var items = QueryOver.Of<Item>()
.WithSubquery.Where(i => i.Sernum).In(subquery);
.List();