LINQ to SQL error on .Join() - c#

I'm trying to query a database and join two tables. I've never used Join() this way and I'm getting an error on the second Join():
var adjustments = data.Inventory_ARCHIVEs
.Where(i => i.Location == comboBox3.Text &&
upcCodes.Contains(i.UPCCode) &&
(i.AVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.AVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date) &&
(i.BVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.BVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date))
.GroupBy(i => new { i.UPCCode })
.Select(i => new
{
ID = i.Max(x => x.ID),
i.Key.UPCCode
})
.Join(data.Inventory_ARCHIVEs, a => a.ID,
b => b.ID, (a, b) => new { a, b })
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location }, (x, y) => new
{
ID = x.a.ID,
UPCCode = x.a.UPCCode,
Date = x.b.BVtime.Value.Date,
Description = y.Description,
BVamount = x.b.BVamount,
AVamount = x.b.AVamount,
Difference = x.b.AVamount - x.b.BVamount,
AverageCost = x.b.AverageCost,
ExtCost = (x.b.AVamount - x.b.BVamount) * x.b.AverageCost
});
x.a.UPCCode ,x.b.Location, y.UPC_Code, andy.Location are strings.
This is the error:
The type arguments for method 'System.Linq.Enumerable.Join<TOuter,TInner,TKey,TResult> (System.Collections.Generic.IEnumerable<TOuter>, System.Collections.Generic.IEnumerable<TInner>, System.Func<TOuter,TKey>, System.Func<TInner,TKey>, System.Func<TOuter,TInner,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
If I DO NOT include the join by "Location" column and just use "UPCCode", it works, but when I add the second join by column, I get the error

I suspect this is the problem - it's at least one problem:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location },
...)
You're trying to join using two different anonymous types as the key types. They've got different properties - one has UPCCode, the other has UPC_Code. You probably want:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { UPCCode = y.UPC_Code, y.Location },
...)
Or just be more consistent with your property names so that you use UPCCode or UPC_Code everywhere, rather than a mixture.

You must have most care about type of data on both side of 'equals' clause, They should be of same datatype like int and int , or string and string.
Or using lambda expression the second and third parameter must be same datatype in the Join clause.

Related

Using LINQ Lambda expression determining value by group by and where condition

So I have the following data table:
Region Class
Reg100 A
Reg100 B
Reg200 A
Reg300 B
Where I want to determine a region that has two classes A and B. In this case it would be Reg100. How could I write this in using lambda expression?
I have tried something like below but not getting what I want.
dt.Where(x => x.Class.Contains(listOfAandB).GroupBy(x=>x.Region).FirstOrDefault()
Lets define the input:
class Data
{
public string Region;
public string Class;
}
var dt = new[]
{
new Data { Region = "Reg100", Class = "A" },
new Data { Region = "Reg100", Class = "B" },
new Data { Region = "Reg200", Class = "A" },
new Data { Region = "Reg300", Class = "B" },
};
Now, using GroupBy we can group the input by Region.
dt.GroupBy(x => x.Region)
This yields { Reg100 (A, B), Reg200 A, Reg 300 B }. Now we look if we can find both A and B:
dt.GroupBy(x => x.Region)
.Where(g => g.Any(x => x.Class == "A") && g.Any(x => x.Class == "B"))
And finally as we are only interested in the region, we project to it:
dt.GroupBy(x => x.Region)
.Where(g => g.Any(x => x.Class == "A") && g.Any(x => x.Class == "B"))
.Select(g => g.Key);

Lambda syntax. How join by 1 to many keys

i'm trying rewrite query from native sql to linq lambda syntax (not linq query syntax)
LINQ (not work)
var result = _uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking(),
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
(b, a) => new SubSection
{
SubSectionName = b.CustomValue ?? a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? a.TabLabelCodeId,
})
.Where(a => a.ResourceId == 1);
SQL
SELECT [SubSectionName] = ISNULL(B.CUSTOMVALUE,A.TABLABELCODEID),
[SubSectionNote] = ISNULL(B.CUSTOMVALUE,A.TABNOTESCODEID)
FROM TableA as A LEFT JOIN
(SELECT CUSTOMVALUE, ELEMENTNAMEID FROM TableB WHERE DISPLAYSETTINGID = 1) as B
ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID)
WHERE A.RESOURCEID = 1
Q How to rewrite sql ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID) to lambda syntax
...
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
....
(doesn't work)
Normally I would suggest following my SQL conversion rules, but this is sufficiently complex I don't think it would help.
To use query comprehension syntax on a non-equijoin that is a left join, it seems easiest to use lambda syntax to express the join conditions, so I just combined the sub-query with the join conditions:
var ans = from A in TableA
where A.ResourceID == 1
from B in TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == A.TabNotesCodeID || b.ElementNameID == A.TabLabelCodeID).DefaultIfEmpty()
select new {
SubSectionName = (B.CustomValue ?? A.TabLabelCodeID),
SubSectionNote = (B.CustomValue ?? A.TabNotesCodeID)
};
The lambda equivalent of multiple from clauses to generate a cross join is SelectMany, so converting into lambda syntax:
var ans2 = TableA.Where(a => a.ResourceID == 1)
.SelectMany(a => TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == a.TabNotesCodeID || b.ElementNameID == a.TabLabelCodeID)
.DefaultIfEmpty(),
(a, b) => new {
SubSectionName = (b.CustomValue ?? a.TabLabelCodeID),
SubSectionNote = (b.CustomValue ?? a.TabNotesCodeID)
}
);
After countless experiments i've found out solution:
_uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking().Where(b => b.DisplaySettingId == 1),
a => new { note = a.TabNotesCodeId, label = a.TabLabelCodeId },
b => new { note = b.ElementNameId, label = b.ElementNameId },
(a, b) => new { a,b })
.Where(joinTables => joinTables.a.ResourceId == 1)
.SelectMany(
joinTables => joinTables.b.DefaultIfEmpty(),
(joinTables, b) => new SubSection()
{
LayoutTab = joinTables.a.LayoutTab,
SubSectionName = b.CustomValue ?? joinTables.a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? joinTables.a.TabNotesCodeId
});

Lambda not equal on join

Table 1 called Category contains 70 records
Table 2 called FilterCategorys contains 0 records (currently).
my lambda join, I want to pull only records that don't match, so in this case I expect to get 70 records back. Here's my incorrect Lambda:
var filteredList = categorys
.Join(filterCategorys,
x => x.Id,
y => y.CategoryId,
(x, y) => new { catgeory = x, filter = y })
.Where(xy => xy.catgeory.Id != xy.filter.CategoryId)
.Select(xy => new Category()
{
Name = xy.catgeory.Name,
Id = xy.catgeory.Id,
ParentCategoryId = xy.catgeory.ParentCategoryId
})
.ToList();
Whats the correct syntax I need here?
Not sure if you have a requirement of using lambdas (rather than query syntax), but I prefer query syntax for statements that have outer joins.
This should be equivalent:
var filteredList = (
from c in Categorys
join fc in FilterCategorys on c.Id equals fc.CategoryId into outer
from o in outer.DefaultIfEmpty()
select new
{
Category = new Category
{
Name = c.Name,
Id = c.Id,
ParentCategoryId = c.ParentCategoryId
},
Exists = (o != null)
})
.Where(c => !c.Exists)
.Select(c => c.Category);
If you want to do it in purely lambda:
var match = categorys.Join(filterCategorys, x => x.Id, y => y.CategoryId, (x, y) => new { Id = x.Id });
var filteredList = categorys.Where(x => !match.Contains(new {Id = x.Id}));
I haven't measured the performance of this, but for 70 records, optimization is not an issue.
Well I came up with a solution that takes away the need for the join.
var currentIds = filterCategorys.Select(x => x.Id).ToList();
var filteredList = categorys.Where(x => !currentIds.Contains(x.Id));
very similar to #Zoff Dino answer, not sure about performance, maybe someone would like to check.
Try this:
var categories= ...
var filteredCategories=...
var allExceptFiltered = categories.Except(filteredCategories, new CategoryComparer()).ToList();
If you don't provide a custom Comparer that framework has no way of knowing that 2 Category objects are the same(even if they have the same ID),it just thinks that they are different objects (it checks for reference equality )
so you must add this class to your project:
public class CategoryComparer: IEqualityComparer<Category>
{
public bool Equals(Category x, Category y)
{
if (x == null && y == null)
return true;
if (x == null)
return false;
if (y == null)
return false;
return x.CategoryId.GetHashCode() == y.CategoryId.GetHashCode();
}
public int GetHashCode(Category obj)
{
return obj.CategoryId.GetHashCode();
}
}
update
Also check out Wyatt Earp's answer,it is very useful to know how to do an outer join
update 2
Your problem is the Join method.
The Where clause is "called" after the join.so after you have joined the listed based on the ID you select those which have different IDs,that's why you get no resuts
Could you draw bracket and it should work.
....Where(xy => (xy.catgeory.Id != xy.filter.CategoryId))

LINQ Group by and "count-if"

I want to count all items in a list if a value is true and the count the false values.
I have this:
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(g=>g.isScheduled),
UnscheduledItems = g.Count(g=> !g.isScheduled)
}).ToList();
this gives me the following compilation error:
Cannot convert lambda expression to type
'System.Collections.Generic.IEqualityComparer' because it is not
a delegate type
as if the it expects a different method overload
when i do this everything seems to be okay...
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(),
UnscheduledItems = g.Count()
}).ToList();
why is it when i remove the g=> !g.isScheduled expression from the count method it accepts it ?
Found it ... ARGH !
when I can use "g" as the variable for my inner lambda expression inside the group becuse it refers to the original group "g". so i changed g=>g.isScheduled toi=>i.isScheduled
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(i=>i.isScheduled),
UnscheduledItems = g.Count(i=> !i.isScheduled)
}).ToList();
and now everything is okay

How to left join two queries in linq

I want to left join two queries. In that first query will contains 4 records and second query will contain 0 or more records up to 4. I want to join both these and want to get 4 records. If there are any records in second query then i want to the permission from the second table. I have written code like.
var finalquery = values.GroupJoin(records, i => i.typeid, j => j.typeid, (i, j) => new { i, j }).SelectMany(i => i.j.DefaultIfEmpty(), (i, j) => new {
id = i.i.typeid,
Permission = (j.premission ==null)?null:j.premission
}).ToArray();
But it is throwing an exception
You have NullReferenceException on this condition:
j.premission ==null
That will throw when j is null (your default case).
var finalquery = values.GroupJoin(records,
v => v.typeid,
r => r.typeid,
(v, g) => new { v, g })
.SelectMany(x => x.g.DefaultIfEmpty(),
(x, r) => new {
id = x.v.typeid,
Permission = r == null ? null : r.permission
})
.ToArray();
Or with query syntax (converting to array omitted for beauty):
var finalquery =
from v in values
join r in records on v.typeid equals r.typeid into g
from r in g.DefaultIfEmpty()
select new {
id = v.typeid,
Permission = r == null ? null : r.permission
};

Categories