Join for List<Array> in lambda expression chain syntax - c#

I am an amateur at Joins, but I am trying to use expression chain syntax instead of a LINQ statment. Most examples I find have used arrays or list of classes { List<class> }; but nothing for a list of arrays { List<array> }
List<int[]> needHelp = new List<int[]>{
new int[3] { 10, 20, 50},
new int[3] { 10, 21, 53},
new int[3] { 10, 22, 55},
new int[3] { 11, 20, 60},
new int[3] { 11, 22, 51} };
List<int[]> ghosts = new List<int[]>{
new int[3] { 10, 45, 65},
new int[3] { 11, 34, 60} };
The LINQ "query" syntax that works is:
List<int[]> result = (from h in needHelp join g in ghosts on h[0] equals g[0]
where h[1] == 21 select g).ToList();
But this "Method" expression chain syntax isn't working for me:
List<int[]> result = needHelp.Join(ghosts, x=>x[0], y=>y[0], (x,y) => y ).Where(x => x[1] == 21).ToList();
For those interested I have found that this lambda expression chain can print out a List of arrays without having to use two foreach loops.
result.ForEach( x => { Array.ForEach(x, e => Console.Write(e + " ") ); Console.WriteLine();} );
Ok well I think I found my own solution, and it was by changing the location of the where clause. But what is the reason the Where has to come before the join and not work after? I read on a post that if this was a database call the where class being before ".Join" then it is processed on the server and not the client. I assume then that trying to use it after the Join it is out-of-scope of the data that is left, which in this instance would be y (List<int[]> ghosts).

Joins and Wheres are not interchangeable in general.
If you read your expression chain "out loud", it would read something like this: first get me all arrays in ghosts that match up a first element with something in needHelp, and then select from that those arrays which have the second element equal to 21.
So at the point after the Join, you are only left with arrays that come from the ghosts List. Your WHERE clause then returns nothing, since nothing in ghosts has second element equal to 21. You are checking for that in the needHelp List.
I would recommend that you do swap the Join and the Where:
List<int[]> result = needHelp.Where(x => x[1] == 21).Join(ghosts, x=>x[0], y=>y[0], (x,y) => y ).ToList();
In case it was a source of confusion for you, don't think that the x and y variables in the functions mean anything over multiple functions. Your Where call related x to needHelp, but that doesn't mean that the x in the Join function relates to needHelp; it references the output of the Where, which are elements from ghosts.

Your Join method isn't the translation of the query syntax you gave.
Your query filter on h[1] (x[1] in the Join) but you select only y (g in your query) so the Where after that applies on the wrong thing.
Here is the direct translation of your query :
List<int[]> result =
needHelp
.Join (ghosts, x => x[0], y => y[0], (x, y) => new { x, y })
.Where (anon => anon.x[1] == 21)
.Select (anon => anon.y)
.ToList ();

The exact method syntax equivalent of this query syntax
from h in needHelp join g in ghosts on h[0] equals g[0]
where h[1] == 21
select g
is something like this:
needHelp.Join(ghosts, h => h[0], g => g[0], (h, g) => new { h, g })
.Where(x => x.h[1] == 21)
.Select(x => x.g);
The query syntax uses so called "transparent indentifiers" which hide those intermediate anonymous types, and that's why is preferable for queries involving joins.
Speaking about joins and where in general, IMO it's always better to apply the filters before joining, especially in LINQ to Objects, because database query optimizers will rearrange the filters and join operations anyway. So a better query (from both performance and memory allocations) would be:
needHelp.Where(h => h[1] == 21).Join(ghosts, h => h[0], g => g[0], (h, g) => g);

Related

SQL IN Operator to LINQ

I need this query to be translated to Linq query
SELECT DISTINCT (pf.Id)
FROM
PF pf
LEFT JOIN FA fa on pf.id = fa.PFId
LEFT JOIN Fan f ON pf.FId = f.Id
WHERE
pf.PId=2 AND fa.AId IN (1,26) AND fa.AId NOT IN(27)
This is the LINQ query I have so far as requested
var allFansSavedAsLeads = _dbContext.PF
.Where(e => e.F.S != null &&
e.A.Any(x => x.AId==27 &&
x.AId.Equals(1) /*&&
x.AId != 27*/) &&
e.PId == pId);
I get zero results with this.
I suggest you Create two lists of Ids representing the Activities that can be included and activities which needs to be excluded. use them like the following:
List<int> IncludedIds = new List<int>(){1,26};
List<int> ExcludedIds = new List<int>(){27};
_dbContext.ProfileFans.Where(e => e.Fan.SNUrl != null &&
e.Activities.Any(x => IncludedIds.Any(x.ActivityId) &&
!ExcludedIds.Any(x.ActivityId) &&
e.ProfileId == profileId);
Please note: I used List<int> because of the example that you are given, you have to create the lists based on the data type of ActivityId
You can create a temporary ActivityList AS
var List<int> ActivityList = new List<int>() {1, 26}
and use something like
ActivityList.Contains(x => x.ActivityId)
But see sujith's answer for a more complete solution.
You don't need a whitelist and a blacklist. It's either one or the other. So I'm making a whitelist. If the allowed ActivityId is 1 or 26, then by definition it is definitely not 27, so there is no need to try and exclude it. I'm using int[] instead of List<int> given that the whitelist is likely to be static, but feel free to change this to a List<int> if you want to dynamically modify it.
int[] whiteList = { 1, 26 };
var allFansSavedAsLeads = _dbContext.ProfileFans.Where(pf =>
pf.Fan.SNUrl.HasValue &&
pf.Activities.Any(fa => whiteList.Contains(fa.ActivityId)));
If you want the JOINs as well, you may want to look into .Include(), but from your original SQL query you seem like you're not going to actually need the contents of the joined tables.

Linq to SQL left outer join using Lambda syntax and joining on 2 columns (composite join key)

I am trying to make an Inner Join on 2 columns with Linq to SQL as a Lambda expression. The normal query would look like this.
SELECT * FROM participants
LEFT OUTER JOIN prereg_participants ON prereg_participants.barcode = participants.barcode
AND participants.event_id = prereg_participants.event_id
WHERE (participants.event_id = 123)
I am succeeding in making a Left Outer Join on one column with the following code.
var dnrs = context.participants.GroupJoin(
context.prereg_participants,
x => x.barcode,
y => y.barcode,
(x, y) => new { deelnr = x, vi = y })
.SelectMany(
x => x.vi.DefaultIfEmpty(),
(x, y) => new { deelnr = x, vi = y })
.Where(x => x.deelnr.deelnr.event_id == 123)
.ToList();
The problem is that with the above Lambda I get too many results because it is missing the AND participants.event_id = prereg_participants.event_id part. But whatever I try i'm not getting the correct amount of participants.
I looked at the following existing questions, but none solved my problem in writing the correct lambda. And most of the solutions are nog in lambda-format or not a Left outer join on multiple columns.
How to do joins in LINQ on multiple fields in single join
LINQ to SQL - Left Outer Join with multiple join conditions
Group By using more than two columns by Lambda expression
And most of these from this Google search
Query:
var petOwners =
from person in People
join pet in Pets
on new
{
person.Id,
person.Age,
}
equals new
{
pet.Id,
Age = pet.Age * 2, // owner is twice age of pet
}
into pets
from pet in pets.DefaultIfEmpty()
select new PetOwner
{
Person = person,
Pet = pet,
};
Lambda:
var petOwners = People.GroupJoin(
Pets,
person => new { person.Id, person.Age },
pet => new { pet.Id, Age = pet.Age * 2 },
(person, pet) => new
{
Person = person,
Pets = pet,
}).SelectMany(
pet => pet.Pets.DefaultIfEmpty(),
(people, pet) => new
{
people.Person,
Pet = pet,
});
See code, or clone my git repo, and play!
I was able to get this LEFT OUTER JOIN on the composite foreign key pair barcode, event_id working in both Linq2Sql, and Entity Framework, converting to lambda syntax as per this query syntax example.
This works by creating an anonymous projection which is used in match of the left and right hand sides of the join condition:
var dnrs = context.participants.GroupJoin(
context.prereg_participants,
x => new { JoinCol1 = x.barcode, JoinCol2 = x.event_id }, // Left table join key
y => new { JoinCol1 = y.barcode, JoinCol2 = y.event_id }, // Right table join key
...
Notes
This approach relies on the automagic equality given to identical anonymous classes, viz:
Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashCode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal.
So for the two projections for the join keys need to be of the same type in order to be equal, the compiler needs to see them as the same anonymous class behind the scenes, i.e.:
The number of joined columns must be the same in both anonymous projections
The field types must be of the same type compatable
If the field names differ, then you will need to alias them (I've used JoinColx)
I've put a sample app up on GitHub here.
Sadly, there's no support yet for value tuples in expression trees, so you'll need to stick to anonymous types in the projections.
If it is a LEFT OUTER JOIN, where the left entity can have zero or maximally one connection with right entity, you can use:
// Let's have enumerables "left" and "right"
// and we want to join both full entities with nulls if there's none on the right.
left.GroupJoin(
right,
l => l.LeftKey,
r => r.RightKey,
(l, r) => new { Left = l, Right = r.FirstOrDefault() });
If you want to join left with just one attribute of right:
// Let's have enumerables "left" and "right"
// and we want to join right's attribute RightId and to set 0 for those having no Id.
left.GroupJoin(
right,
l => l.LeftKey,
r => r.RightKey,
(l, r) => new { Left = l, RightId = r.FirstOrDefault()?.RightId ?? 0 });
You can do this by making use of anonymous types.
Example:
var result = from a in context.participants
join b context.prereg_participants on new { X = a.barcode, Y = a.event_id } equals new { X = b.barcode, Y = b.event_id } into A
from b in A.DefaultIfEmpty()
where a.event_id = 123

How to Convert Linq to Lambda Expression

var getr = (from d in _context.DR
join r in _context.R on d.RID equals r.RID
where HID == r.HID && cI >= d.DRD && cO < d.DRD
group d by new {d.RID, d.RGID} into g
select g);
How to convert Linq to lambda? This is what I got:
var getr = _context.DR.Join(_context.R, x => x.RID, y => y.RID, (x, y) => new { R= x, DR= y}).Where(z => z.DR.RID== y.RID);
Are there any pros and cons of using either one?
In terms of performance : there is no performance difference whatsoever between two.
Which one should use is mostly personal preference, but its important to bear in mind that there are situation where one will be better suited the other.
int[] ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// using Query expression
var evensQuery = from i in ints where isEven(i) select i;
// using Lambda expression
var evensLambda = ints.Where(isEven);
There so many function that available with lambda ie. single(), First(), Take(), Skip()..
Although you can mix and match the two by calling the Lambda-only methods at the end of the query:
// mix and match query and Lambda syntax
//Example ver :1
var query = (from person in people
join pet in pets on person equals pet.Owner
select new { OwnerName = person.Name, Pet = pet.Name }).Skip(1).Take(2);
or, for better readability :
//Example ver :2
var query = from person in people
join pet in pets on person equals pet.Owner
select new { OwnerName = person.Name, Pet = pet.Name };
var result = query.Skip(1).Take(2);
Both example version return the same output without performance differences because of delayed(or Deferred ) execution, that means query is not executing at the point of declaration, but it will execute when try to iterate through the result variable.
BUT, if you don’t want delayed execution, or need to use one of the aggregate functions such as Average() or Sum(), for example, you should be aware of the possibility of the underlying sequence being modified between the assignments to query and result. In this case,I’d argue it’s best to use Lambda expressions to start with or add the Lambda-only methods to the query expression.

How to skip repeated ids from table and select remains using linq

In a table i have the following,
GroupId
3786
3787
3788
3788
So i need to take 3786, 3787 id's only. If i use distinct() it will take 3788 also. I don't know which method should use in linq.
IQueryable<Sub> subDetails=
from carSub in this.UnitOfWork.Repository<CarSub>().Queryable()
//from pcs in carSub.ConfirmedCarrier.CarrierCandidate.ProductCarrierScores
join p in this.UnitOfWork.Repository<ProductGroup>().Queryable() on carSub.Submission.PlacementID equals p.PlacementID
join pg in this.UnitOfWork.Repository<ProductGroupMember>().Queryable() on p.ProductGroupID equals pg.ProductGroupID
join pcs in this.UnitOfWork.Repository<ProductCarrierScore>().Queryable() on p.ProductGroupID equals pg.ProductGroupID
inside of that JOIN join pcs in this.UnitOfWork.Repository<ProductCarrierScore>().Queryable() on p.ProductGroupID equals pg.ProductGroupID here only i have to use this
pg means ProductGroupMember. in that Member i have to pass the only not repeated values only. Please guide me. I am stuck very much
i would make it like this:
Group them by GroupId;
Get only those which have 1 element in group;
The code will look like this:
var groupIds = subDetails.GroupBy(x=>x.GroupId).Where(x=>x.Count()==1).Select(x=>x.Key)
P.S. There might be some other faster solutions for this, but this is what came to mind first.
You can group by, then narrow down the results to items with count less than 2
For example,
var ids = new List<int> {1, 2, 3, 2, 5, 3, 4};
var itemsNotDuplicate = ids.GroupBy(f => f, t => t,
(k, items) => new {val = k, count = items.Count()}).Where(g => g.count < 2);
you can also you TakeWith
var list = new List<int> { 3786, 3787, 3788, 3788};
var onlyOne=list.TakeWhile(t=> list.Count(l=>l==t)==1);
its a similar approach to using GroupBy, but you only need to use a single lambda expression.

How to do a WHERE...IN... clause in LinqToSql?

How can I select multiple elements using a WHERE...IN... type of clause as in
select * from orders where orderid in (1, 4, 5)
in LinqToSql? I'd prefer not to have a lambda expression since they scare me.
LINQ has "Contains" which is like "IN" but expressed the other way round - an element isn't "in" a set, a set "contains" an element.
int[] validIds = { 1, 4, 5 };
var query = from order in db.Orders
where validIds.Contains(order.Id)
select order
This is more simply expressed (IMO) with a lambda though:
int[] validIds = { 1, 4, 5 };
var query = db.Orders.Where(order => validIds.Contains(order.Id));
I realise lambdas are "new" and therefore scary to some extent, but it's really well worth grabbing hold of them with both hands. They're lovely.
int[] arry = new int[] {1,4,5};
var q = from r in orders
where Array.IndexOf(array, orderid) != -1
select r;
or
List<int> lst = new List<int>(new int[] {1,4,5});
var q = from r in orders
where lst.Contains(orderid);
select r;

Categories

Resources