Linq query giving inappropriate output - c#

I have two transaction tables named as ParentTransaction and ChildTransaction in which TransactionId of ParentTransaction will act as foreign to ChildTransaction of TransactionId.
Now I want to get all those TransactionId of ParentTransaction whose payamount is not completed.
From below output I want record of transaction Id 3 because only 1000 has been paid for transactionid 3 instead of 5000.
I have one table like this:
Transactionid(p.k) PayAmount
1 1000
2 3000
3 5000
4 6000
ChildTransaction
Id TransactionId(F.k) DepositAmount
1 1 600
2 1 400
3 2 1000
4 2 1000
5 2 1000
6 3 2000
This is my query:
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction on tmp.Transactionid equals
tmp1.Transactionid where tmp.PayAmount !=tmp1.DepositAmount
select tmp);
But here I am getting Transaction Id 1 and 2 although their transaction has been completed in two parts that is 600 and 400 for transaction id 1.

The general idea of query languages is to express the desired result, not how to get it.
Applying it to your scenario leads to a simple query like this
var query = context.ParentTransaction
.Where(t => t.PayAmount != context.ChildTransaction
.Where(ct => ct.TransactionId == t.TransactionId)
.Sum(ct => ct.DepositAmount));
If you are using EF and a proper model navigation properties, it would be even simple
var query = context.ParentTransaction
.Where(t => t.PayAmount != t.ChildTransactions.Sum(ct => ct.DepositAmount));
One may say the above would be inefficient compared to let say the one from #Vadim Martynov answer. Well, may be yes, may be not. Vadim is trying to force a specific execution plan and I can understand that - we have to do such things when in reality encounter a query performance issues. But it's not natural and should be a last resort only if we have a performance problems. Query providers and SQL query optimizers will do (and are doing) that job for us in most of the cases, so we don't need to think of whether we need to use a join vs subquery etc.

I'm not sure that != is a best value. Here is a solution with > check and grouping:
var expectedValue =
context.ChildTransaction
.GroupBy(t => t.TransactionId, (key, group) => new { TransactionId = key, Deposit = group.Sum(e => e.Deposit) })
.Join(context.ParentTransaction, grouped => grouped.TransactionId, transaction => transaction.TransactionId, (group, transaction) => new { Transaction = transaction, group.Deposit })
.Where(result => result.Transaction.PayAmount > result.Deposit)
.Select(result => result.Transaction);
This query can be read in a declare manner like next requirement:
Group collection of child transactions by TransactionId and for each group retrieve an anonymous type object with fields TransactionId = grouping key (== TransactionId) and Deposit which is sum of Deposits for rows with same TransactionId.
Join set from part 1 to the the table PaerntTransaction by TransactionId field. For each joined pair retrieve an anonymous type object with fields Transaction == transaction from ParentTransactions table and Deposit which is deposit from part 1 set which is sum of Deposits with the same TransactionId from the ChildTransactions table.
Filter from result set only objects where PayAmount greather than sum of deposits.
Return only ParentTransaction object for each filtered row.
This is SQL-optimized scenario because join, filter and grouping prevents nested queries which can be added to the actual execution plan in other cases and make worse performance.
UPDATE
To solve the problem with transaction that have no deposits you can use LEFT JOIN:
var expectedValue = from parent in context.ParentTransaction
join child in context.ChildTransaction on parent.TransactionId equals child.TransactionId into gj
from subset in gj.DefaultIfEmpty()
let joined = new { Transaction = parent, Deposit = subset != null ? subset.Deposit : 0 }
group joined by joined.Transaction
into grouped
let g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) }
where g.Transaction.PayAmount > g.Deposit
select g.Transaction;
The same query with LINQ method chain:
var expectedValue =
context.ParentTransaction
.GroupJoin(context.ChildTransaction, parent => parent.TransactionId, child => child.TransactionId, (parent, gj) => new { parent, gj })
.SelectMany(#t => #t.gj.DefaultIfEmpty(), (#t, subset) => new { #t, subset })
.Select(#t => new { #t, joined = new { Transaction = #t.#t.parent, Deposit = #t.subset != null ? #t.subset.Deposit : 0 } })
.GroupBy(#t => #t.joined.Transaction, #t => #t.joined)
.Select(grouped => new { grouped, g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) } })
.Where(#t => #t.g.Transaction.PayAmount > #t.g.Deposit)
.Select(#t => #t.g.Transaction);
Now you retrieve all parent transaction and join it with child transaction but if there is no children then use Deposit == 0 and group joined entities in a similar manner by ParentTransaction.

Problem
The issue lies on this statement:
where tmp.PayAmount != tmp1.DepositAmount //the culprit
And since the tmp1 is defined as a single child transaction, the statement would result in equating wrong values:
Visualizer:
1000 != 600 //(result: true -> selected) comparing parent 1 and child 1
1000 != 400 //(result: true -> selected) comparing parent 1 and child 2
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 3
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 4
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 5
5000 != 2000 //(result: true -> selected) comparing parent 2 and child 5
//However, you do not want it to behave like this actually
But what you want to have is rather:
Visualizer:
1000 != (600 + 400) //(result: false -> not selected) comparing parent 1 and child 1 & 2, based on the TransactionId
3000 != (1000 + 1000 + 1000) //(result: false -> not selected) comparing parent 2 and child 3, 4, & 5, based on the TransactionId
5000 != (2000) //(result: true -> selected) comparing parent 3 and child 6, based on the TransactionId
6000 != nothing paid //(result: true -> selected) comparing parent 3 with the whole childTransaction and found there isn't any payment made
Thus, you should make tmp1 is as a collection of children rather than single child.
Solution
Unpaid Transaction
Change your code like this:
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id
on tmp.TransactionId equals tmp1.Key //use the key
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount
select tmp)
.Union( //added after edit
(from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp)
);
Explanations
This line:
join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id
Making use of GroupBy in Linq, this line makes tmp1 a group of children rather than a single child and, rightfully, based on its foreign key, which is the TransactionId.
Then this line:
on tmp.TransactionId equals tmp1.Key //use the key
We simply equates tmp.TransactionId with the children's group key tmp1.Key
Then the next line:
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount
Get the sum value of the children's DepositAmount rather than single child's DepositAmount which is less than the PayAmount in the parent, and then
select tmp
Select all the parent transactions which satisfy all the criteria above. This way, we are half-done.
The next step is to consider transaction which occurs in the parent but not in the child(ren). This is considered as unpaid too.
We can combine the result of the first query with the second query using Union
.Union( //added after edit
(from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp)
);
This selects whatever exist in the parent transaction but doesn't exist at all in the child (and therefore considered unpaid).
And you would get the right data, consisting of your ParentTransaction rows which are not fully paid, both for the parent transaction whose TransactionId exists in the child or not.
Paid Transaction
As for the paid transaction, simply change the query from > to <=:
var datapaid = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction.GroupBy(y => y.TransactionId)
on tmp.TransactionId equals tmp1.Key
where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
select tmp);
Combined
We can further simplify the above query like this:
var grp = context.ChildTransaction.GroupBy(y => y.TransactionId);
var data = (from tmp in context.ParentTransaction
join tmp1 in grp //group this by transaction id
on tmp.TransactionId equals tmp1.Key //use the key
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount)
select tmp)
.Union((
from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp));
var datapaid = (from tmp in context.ParentTransaction
join tmp1 in grp
on tmp.TransactionId equals tmp1.Key
where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
select tmp);

List<int> obj = new List<int>();
using (DemoEntities context = new DemoEntities())
{
obj = (from ct in context.CTransactions
group ct by ct.Transactionid into grp
join pt in context.PTransactions on grp.Key equals pt.Transactionid
where grp.Sum(x => x.DepositAmount) < pt.PayAmount
select grp.Key).ToList();
}

You control only one child transaction. You must use Sum() operation and need to use > instead of != Pls try this.
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction on tmp.Transactionid equals into tmp1List
tmp1.Transactionid where tmp.PayAmount > tmp1List.Sum(l => l.DepositAmount)
select tmp);

Related

Join Error | Unable to create a constant value of type only primitive

Very new with LINQ here.
I have the following data in my table (TableA):
ID Name SubmissionNo
1 Jim A-1
2 Andy A-2
3 Rick A-2
4 Mary A-3
5 Zim A-4
6 Loren A-1
I then need to create a query which will allow me to get from that table, those records which have duplicate submission numbers.
Here's my solution so far (Context is the database context):
var duplicates = (from tbl in Context.TableA.AsNoTracking()
group tbl by tbl.SubmissionNo into grp
select new { count = grp.Count(), submissionNo = grp.Key})
.Where(x => x.count > 1)
.OrderBy(y => y.submissionNo).ToList();
The variable duplicates then contains the record:
count submissionNo
2 A-1
2 A-2
I then write the main query which will allow me to get all the records from TableA which has duplicate submissionNo
var myList = (from tbl in Context.TableA.AsNoTracking()
join dup in duplicates on tbl.SubmissionNo equals dup.submissionNo
select new
{
ID = tbl.ID,
Name = tbl.Name,
SubmissionNo = tbl.SubmissionNo
})
.ToList();
I am then getting an error for the myList query with
Unable to create a constant value of type 'Anonymous Type'. Only primitive types or enumeration types are supported in this context.
I think there must be a better way to do this as from the TableA above, I practically want the following results:
ID Name SubmissionNo
1 Jim A-1
2 Andy A-2
3 Rick A-2
6 Loren A-1
Your first query, slightly modified, has all information you need:
var myList = from tbl in Context.TableA.AsNoTracking()
group tbl by tbl.SubmissionNo into grp
where grp.Count() > 1
from item in grp
select new
{
count = grp.Count(),
submissionNo = grp.Key,
item.Name,
);
The pattern group into grp - from item in grp is a commonly used query pattern to group items and then flatten the group again, while keeping in touch with the group data (like Count() and Key).
Now you don't need the join anymore and the exception doesn't occur. By the way, the exception tells you that EF can only handle joins with collections of primitive types (int etc.), because it has to translate the whole expression into SQL. There's simply no translation for rich objects like TableA.
By the way, the query can be improved by removing the repeated Count():
var myList = from tbl in Context.TableA.AsNoTracking()
group tbl by tbl.SubmissionNo into grp
let count = grp.Count()
where count > 1
from item in grp
select new
{
count = count,
submissionNo = grp.Key,
item.Name,
);
This will generate a more efficient SQL statement containing one COUNT instead of two.
Since Entity Framework does not support joining in-memory collections of objects with database collections, a common workaround for this is to filter using Contains.
First, you need to get the IDs to filter on:
var duplicates = (from tbl in Context.TableA.AsNoTracking()
group tbl by tbl.SubmissionNo into grp
select new { count = grp.Count(), submissionNo = grp.Key})
.Where(x => x.count > 1)
.OrderBy(y => y.submissionNo)
.ToList();
var duplicateIds = duplicates.Select(x => x.submissionNo).ToList();
And then change your query to perform a WHERE...IN instead of a JOIN:
var myList = (from tbl in Context.TableA.AsNoTracking()
where duplicateIDs.Contains(tbl.SubmissionNo)
select new
{
ID = tbl.ID,
Name = tbl.Name,
SubmissionNo = tbl.SubmissionNo
})
.ToList();

Complex Linq Query Update as DateTime

There are A and B tables that are related to each other. I want to create a linq query that will update the Status value in the A table if the entire row of relationship lines with the AID column in the B table is equal to or smaller than today's date in the Date field.
For example, according to the table below, the Status values of the rows with ID value 1 (AAA) and 2 (BBB) in Table A will be 1. Its Status value will not change because the line with ID value 3 (CCC) is not smaller than the current date of all the related rows in the B table.
How can I write the most stable and performance linq query?
Today : 2018-7-10
A Table
ID Name Status
1 AAA 0
2 BBB 0
3 CCC 0
B Table
ID AID Date
6 1 2018-5-3
7 2 2018-6-2
8 2 2018-6-4
9 3 2018-10-12
10 3 2018-7-7
Grouping TableB on AID
Selecting the "Max" date in each group.(Each unique AID)
Compares the selected dates with the corresponding Id in Table A.
Sets the Status value to true if the date is less or equal to the current date.
TableB.GroupBy(x => x.AId).Select(group => new { identifier = group.Key, MaxDate = group.Max(m => m.Date) }).ToList().ForEach(y =>
{
if (y.MaxDate <= DateTime.Now.Date)
{
TableA.Where(g => g.Id == y.identifier).First().Status = true;
}
});
This will select AIDs from Table B where Date is samller than now.
we select records from table A where its ID is in List from
previous step
Then we update Status value
A.Where ( a => B.Where( b => b.Date <= DateTime.Now).Select(b => b.AID).Contains(a.ID)).ForEach( a => a.Status = 1 )
/*Fetching those aS Who meet the condition. */
var aList1=(from b in dbset.Bs.Where(x=>x.Date<DateTime.Now)//TimeZone may vary
join a in dbSet.As
on b.AID equals a.ID
select a);
/*Fetching those aS Who don't meet the condition. */
var aList2=(from b in dbset.Bs.Where(x=>x.Date>=DateTime.Now)//TimeZone may vary
join a in dbSet.As
on b.AID equals a.ID
select a);
/*Removing those aS from list1 which occured in list2 */
var aFinalList=(aList1.Except(aList2)).ToList();
/*Updating status */
aFinalList.ForEach(x=>x.Status=1);
aFinalList.SaveChanges();
You can use GroupJoin extension in Lambda to Join the A and B tables then use All extension with your condition (date <= Today or any condition) then update the Status. Something like,
var lstResult = lstA.GroupJoin(lstB, a => new { a.Id }, b => new { Id = b.AId }, (a, b) => new { a, b })
.Select(x =>
{
if (x.b.All(y => y.Date <= DateTime.Now)) //Actual condition here.
{
x.a.Status = true;
return x.a;
}
else return x.a;
});
C# fiddle with sample data.

c# linq to entities using method based queries - trying to select where the object appears only once

i have got this table that relates the Table hardware with a table Process..
this table is called processHardware.
this table is discribed by:
IDProcessHardware
IDProcess
IDHardware
State
the field state can have 3 states (1-Insert, 2-Remove,3-Substitute)..
so i can i have this:
IDProcessoHardware IDProcesso IDHardware State
1 10 1 1
2 10 2 1
3 10 1 2
what this tell me is that the hardware with id 1 was insert on the process with the id 10
then the user insert the hardware with id 2 on the process with the id 10, and the it remove the hardware with the id 1 from the process with the id 10
by giving the id of the process i want to get the id of the hardware that were insert, this is, the id of the hardware that were remove..
so in this case the record that i will get is record number 2..because was insert, but was not removed..
after getting the ids from this table i need to relate the ids with the table hardware, this table is described by idhardware, serial number, description..
i was using linq method base..
and this was something that i did, but didnt go further after this..
var ProcessoHardware = from procHardware in db.ProcessoHardwares
where procHardware.Rem == 0 && procHardware.IDProcesso == IDProcesso
group procHardware by procHardware.IDHardware into g
select new { IDHardware = g.Key, count = g.Count() };
the query above didnt work for me...
so i want to get the records that appears only once on the table, and then relate the ids that were obtained from this query and get the info about those ids like, serial number, description(these fields are on a table called Hardware).
thanks in advance..
in sql i manage to do the query ..
SELECT *
FROM
(SELECT IDHardware ,COUNT(IDHardware) nu
FROM dbo.ProcessoHardware
WHERE IDProcesso=47
Group By IDHardware) T WHERE nu=1
how do i pass this to linq?
Firstly your SQL statement would be clearer if you used the having clause so it becomes
SELECT IDHardware, COUNT(IDHardware) nu
FROM dbo.ProcessoHardware
WHERE IDProcesso=47
GROUP BY IDHardware
HAVING COUNT(IDHardware) = 1
secondly, your SQL statement doesn't mention a field called Rem, but your LINQ states where procHardware.Rem == 0. I'm going to assume that you need to keep that filter. If so then all you need to do is add a where clause to count your group, g. Try the following
var ProcessoHardware = from procHardware in db.ProcessoHardwares
where procHardware.Rem == 0 && procHardware.IDProcesso == IDProcesso
group procHardware by procHardware.IDHardware into g
where g.Count() == 1
select new { IDHardware = g.Key, count = g.Count() };
although the literal transformation of your statement (without the Rem and hard coded ID of 47) to LINQ would be
var ProcessoHardware = from procHardware in db.ProcessoHardwares
where procHardware.IDProcesso == 47
group procHardware by procHardware.IDHardware into g
where g.Count() == 1
select new { IDHardware = g.Key, count = g.Count() };

How do I write an Entity Framework 4 query for the following SQL?

I have an Entity Framework 4 model in my application. I need it to generate the following SQL:
SELECT *
FROM Reads AS R
INNER JOIN Images AS I ON R.ReadId = I.ReadId AND I.ImageTypeId = 2
LEFT OUTER JOIN Alarms AS A ON R.ReadId = A.ReadId AND A.DomainId IN (103,102,101,2,1,12) AND A.i_active = 1
LEFT OUTER JOIN ListDetails AS L ON L.ListDetailId = A.ListDetailId
WHERE R.i_active = 1
Here's what I have now for this query in my code:
var items = from read in query
join plate in context.Images on read.ReadId equals plate.ReadId
join temp1 in context.Alarms on read.ReadId equals temp1.ReadId into alarms from alarm in alarms.DefaultIfEmpty()
join temp2 in context.ListDetails on alarm.ListDetailId equals temp2.ListDetailId into entries from entry in entries.DefaultIfEmpty()
where plate.ImageTypeId == 2 && plate.IActive == 1
select new { read, plate, alarm, entry.OfficerNotes };
How do I modify the Entity Framework query to get the SQL query I need?
For Left Joins - I suggest using a from with a where instead of join, I find it makes it cleaner
For Inner Joins with multiple join conditions - you need to join on two matching anonymous types, or use a from with where conditions
For x IN list, you need to specify your list outside the query and use the Any method or Contains method
List<int> domainIds = new List<int>() { 103,102,101,2,1,12 };
var items = from read in query
join plate in context.Images
on new { read.ReadId, ImageTypeId = 2 } equals new { plate.ReadId, plate.ImageTypeId }
from alarm in context.Alarms
.Where(x => x.IActive == 1)
.Where(x => domainIds.Any(y => y == x.DomainId))
.Where(x => read.ReadId == x.ReadId)
.DefaultIfEmpty()
from entry in context.ListDetails
.Where(x => alarm.ListDetailId == x.ListDetailId)
.DefaultIfEmpty()
select new { read, plate, alarm, entry.OfficerNotes };

How to return value from 2 tables in one linq query

please consider this table:
PK_Id Number Year Month Value
-------------------------------------------------------------------------
1 1 2000 5 100000
410 4 2000 6 10000
8888 1 2001 5 100
I Id=8888 and now I want to first select record with Id=8888 and second select previos year of that record*(I mean Id=1)*. How I can do this with linq and one query.
basically we have some queries that first it should find a value from a table (that may be not PK) and find Corresponding records in another tables. How I can do this with linq and one reference to database.
thanks
from a in Record
where a.PK_Id == 8888
from b in Record
where b.Number == a.Number && b.Year == a.Year - 1
select new { Current = a, Previous = b }
or
Record
.Where(a => a.PK_Id == 888)
.SelectMany(a =>
Record
.Where(b => b.Number == a.Number && b.Year == a.Year - 1)
.Select(b => new { Current = a, Previous = b })
If I understand your question right, then you need to filter the data of one table and join two tables.
You can join the tables and filter your data
var query = from c in Table1
join o in Table2 on c.Col1 equals o.Col2
where o.Col3 == "x"
select c;
or you can filter your data from one table and then join the tables (result will be the same)
var query = from c in Table1.Where(item => item.Col3 == "x")
join o in Table2 on c.Col1 equals o.Col2
select c;

Categories