Building linq query that collect records by group - c#

scenario is simple actually but handling it in linq require more exp than I have..
There is 3 table
Table1
ID Column
1 val1
2 val2
Table2
ID Column
1 val3
2 val4
Table3
ID Column
1 val5
2 val6
I need such a query that returns;
TableResult:
Row ID Column Type
1 1 val1 table1
2 2 val2 table1
3 1 val3 table2
4 2 val4 table2
5 1 val5 table3
6 2 val6 table3
Searched on net and started like below but cant figure how handle the tricks create "type", merge records etc..
from t1 in table1
join t2 in table2 on t1.id equals t2.id
join t3 in table3 on t1.id equals t3.id
select new {...}

You've already accepted an answer, so I don't know if this is what you WANT, but it generates the output you specified in your post.
Because you have only used Id values of 1 and 2, it's unclear whether you actually want to perform a Join or just get the set of all rows into a single result.
Anyway:
struct TableStructure
{
public int Id { get; set; }
public string Column { get; set; }
}
var t1 = new List<TableStructure>() { new TableStructure { Id = 1, Column = "val1" }, new TableStructure { Id = 2, Column = "val2" } };
var t2 = new List<TableStructure>() { new TableStructure { Id = 1, Column = "val3" }, new TableStructure { Id = 2, Column = "val4" } };
var t3 = new List<TableStructure>() { new TableStructure { Id = 1, Column = "val5" }, new TableStructure { Id = 2, Column = "val6" } };
var result = ((from row1 in t1 select new { row1.Id, row1.Column, SourceTable = "table1" })
.Union(from row2 in t2 select new { row2.Id, row2.Column, SourceTable = "table2" })
.Union(from row3 in t3 select new { row3.Id, row3.Column, SourceTable = "table3" }))
.AsEnumerable().Select((row, index) => new { RowNum = index + 1, row.Id, row.Column, row.SourceTable });
result.ToList().ForEach(row => Console.WriteLine($"{row.RowNum}, {row.Id}, {row.Column}, {row.SourceTable}"));
output:
1, 1, val1, table1
2, 2, val2, table1
3, 1, val3, table2
4, 2, val4, table2
5, 1, val5, table3
6, 2, val6, table3

Same as what you did try Distinct at the end. query syntax would be :
var List = (from t1 in dbContext.table1
join t2 in dbContext.table2 on t1.ID equals t2.ID
join t3 in dbContext.table3 on t1.ID equals t3.ID
select new
{
//t1.DesiredColumnName,
//t2.DesiredColumnName,
//t3.DesiredColumnName,
//so on
}).Distinct().ToList();

Related

Converting a LINQ Inner join with Left Join

I'm trying to implement a left join into my query, at the moment I'm getting a 'Object reference is not set to an instance of an object'.
The query is working perfectly as an inner join, but I want to include all rows from the left table despite if matches are found. I have tried to follow some of the previous posts on this, most refer to DefaultIfEmpty() but I'm failing to figure it out.
INNER JOIN - SQL
SELECT TOP (1000)
FROM table1 as edc
inner join table2 as c on edc.Id = c.Id
inner join table3 as p on p.Id = c.Id
group by p.Description
INNER JOIN - SQL
SELECT TOP (1000)
FROM table1 as edc
inner join table2 as c on edc.Id = c.Id
left join table3 as p on p.Id = c.Id
group by p.Description
INNER JOIN - LINQ
from edc in table1
join q1 in table2 on __edc.Id equals q1__.Id
join q2 in _table3 on q2.Id equals q1.Id
group q1 by q2.Description
into grouped
select new MyObj
{
Label = grouped.Key,
Value = grouped.Count(),
}
LEFT JOIN - LINQ
from edc in table1
join q1 in table2 on __edc.Id equals q1__.Id
join q2 in _table3 on q2.Id equals q1.Id into leftJoin
from p in leftJoin.DefaultIfEmpty()
group q1 by p.Description
into grouped
select new MyObj
{
Label = grouped.Key,
Value = grouped.Count(),
}
Consider the following example. We have three tables, with a left join between table1 and table2, and a second left join to table3. You need to specify DefaultIfEmpty() on the two joins to include rows where there is no match in the right table.
public class Item
{
public int Id { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
var table1 = new List<Item>
{
new Item {Id = 1, Description = "a"},
new Item {Id = 2, Description = "b"},
new Item {Id = 3, Description = "c"},
new Item {Id = 4, Description = "d"}
};
var table2 = new List<Item>
{
new Item {Id = 1, Description = "e"},
new Item {Id = 2, Description = "f"},
new Item {Id = 4, Description = "g"}
};
var table3 = new List<Item>
{
new Item {Id = 1, Description = "h"},
new Item {Id = 4, Description = "h"},
new Item {Id = 5, Description = "i"},
new Item {Id = 6, Description = "j"}
};
var leftJoin = from t1 in table1
join t2 in table2 on t1.Id equals t2.Id into firstJoin
from x in firstJoin.DefaultIfEmpty()
join t3 in table3 on x?.Id equals t3.Id into secondJoin
from y in secondJoin.DefaultIfEmpty()
select new
{
Table1Id = t1?.Id,
Table1Description = t1?.Description,
Table2Id = x?.Id,
Table2Description = x?.Description,
Table3Id = y?.Id,
Table3Description = y?.Description
};
Console.WriteLine("Left Join:");
foreach (var i in leftJoin)
{
Console.WriteLine($"T1Id: {i.Table1Id}, T1Desc: {i.Table1Description}, " +
$"T2Id: {i.Table2Id}, T2Desc: {i.Table2Description}, " +
$"T3Id: {i.Table3Id}, T3Desc: {i.Table3Description}");
}
Console.WriteLine(string.Empty);
var grouped = from x in leftJoin
group x by x.Table3Description
into group1
select new
{
Label = group1.Key,
Count = group1.Count()
};
Console.WriteLine("Left Join Grouped:");
foreach (var i in grouped)
{
Console.WriteLine($"Label: {i.Label}, Count: {i.Count}");
}
Console.ReadLine();
}
}
Running the program yields the following output:
Left Join:
T1Id: 1, T1Desc: a, T2Id: 1, T2Desc: e, T3Id: 1, T3Desc: h
T1Id: 2, T1Desc: b, T2Id: 2, T2Desc: f, T3Id: , T3Desc:
T1Id: 3, T1Desc: c, T2Id: , T2Desc: , T3Id: , T3Desc:
T1Id: 4, T1Desc: d, T2Id: 4, T2Desc: g, T3Id: 4, T3Desc: h
Left Join Grouped:
Label: h, Count: 2
Label: , Count: 2
Hope this helps!
it's very easy, just change "from p" to "from q2":
from edc in table1
join q1 in table2 on __edc.Id equals q1__.Id
join q2 in _table3 on q2.Id equals q1.Id into leftJoin
from q2 in leftJoin.DefaultIfEmpty()
group q1 by p.Description
into grouped
select new MyObj
{
Label = grouped.Key,
Value = grouped.Count(),
}

LINQ OrderBy based on row values

Lets say we have two tables Parent "DocumentCodes" and Child "Documents".
DocumentCodes table have columns DID,DocumentName,PrintOrder and AscOrDesc
Documents table have columns ID,DID and EffectiveDate.We are getting datatable by joining these two tables.
We need to sort this datatable based on below rules.
Sort By "PrintOrder" column ascending.
If two or more rows have similar "DocumentNames" value then sort by "EffeciveDate" ascending or descending based on "AscOrDesc" value.
"AscOrDesc" column accepts only 'A' or 'D'. If value is 'A' we need to sort "EffectiveDate" ascending and if value is 'D' we need to sort "EffectiveDate" descending.
For example,
DocumentCodes
DID DocumentName PrintOrder AscOrDesc
1 Test1 1 D
2 Test2 2 A
3 Test3 3 D
Documents
ID DID EffectiveDate
1 2 7/9/2017
2 1 5/5/2017
3 2 7/8/2017
4 3 4/9/2017
After joining above two tables. We have DataTable.
ID DocumentName EffectiveDate PrintOrder AscOrDesc
1 Test2 7/9/2017 2 A
2 Test1 5/5/2017 1 D
3 Test2 7/8/2017 2 A
4 Test3 4/9/2017 3 D
Now After sorting this DataTable by using above rules. DataTable should look like this.
ID DocumentName EffectiveDate PrintOrder AscOrDesc
1 Test1 5/5/2017 1 D
2 Test2 7/8/2017 2 A
3 Test2 7/9/2017 2 A
4 Test3 4/9/2017 3 D
Note: EffectiveDate is in MM/DD/YYYY format.
I tried with below code but its not working.
var records2 = from q in datatable.AsEnumerable()
let sortorder= q.Field<string>("AscOrDesc") == "A" ?
"q.Field<DateTime>(\"EffectiveDate\") ascending":
"q.Field<DateTime>(\"EffectiveDate\") descending"
orderby q.Field<int>("PrintOrder"),sortorder
select q;
what I am doing wrong in above code ?
The situation is a fairly ugly one, given that two result rows could theoretically be compared which have the same PrintOrder but different AscOrDesc values. It's only the source of the data that's preventing that.
I do have a horrible hack that I believe should work, but I'm really not proud of it. Basically, imagine that the date is a number... ordering by descending date is equivalent to ordering by the negation of the "date number". For DateTime, we can just take the Ticks value, leading to:
var records2 = from q in datatable.AsEnumerable()
let ticks = q.Field<DateTime>("EffectiveDate").Ticks *
(q.Field<string>("AscOrDesc") == "A" ? 1 : -1)
orderby q.Field<int>("PrintOrder"), ticks
select q;
Ugly as heck, but it should work...
Pretty ugly, but couldnt figure out something better that fits your needs.
Maybe you have luck and #JonSkeet will come by again. :)
(Used LINQ To Object you would need to rewrite it fit your LINQ to SQL)
static void Main(string[] args)
{
var lstFoos = new List<Foo>() {
new Foo() { Id = 1, DocumentName = "Test2", EffectiveDate = new DateTime(2017, 7, 9), PrintOrder = 2, AscOrDesc = "A" },
new Foo() { Id = 2, DocumentName = "Test1", EffectiveDate = new DateTime(2017, 5, 5), PrintOrder = 1, AscOrDesc = "D" },
new Foo() { Id = 3, DocumentName = "Test2", EffectiveDate = new DateTime(2017, 7, 8), PrintOrder = 2, AscOrDesc = "A" },
new Foo() { Id = 4, DocumentName = "Test3", EffectiveDate = new DateTime(2017, 4, 9), PrintOrder = 3, AscOrDesc = "D" },
};
var result = lstFoos.OrderBy(x => x.PrintOrder).GroupBy(x => x.DocumentName).SelectMany(x =>
{
if (x.Count() > 1)
{
var ascOrDesc = x.First().AscOrDesc;
return new List<Foo>(ascOrDesc == "A" ? x.OrderBy(y => y.EffectiveDate) : x.OrderByDescending(y => y.EffectiveDate));
}
return new List<Foo>() {x.First()};
});
foreach (var foo in result)
Console.WriteLine(foo.ToString());
Console.ReadLine();
}
public class Foo
{
public int Id { get; set; }
public string DocumentName { get; set; }
public DateTime EffectiveDate { get; set; }
public int PrintOrder { get; set; }
public string AscOrDesc { get; set; }
public override string ToString()
{
return $"Id: {Id} | DocumentName: {DocumentName} | EffectiveDate: {EffectiveDate} | PrintOrder: {PrintOrder} | AscOrDesc: {AscOrDesc}";
}
}
Looks like a TYPO, Hope this works
var records2 = from q in datatable.AsEnumerable()
orderby q.Field<int>("PrintOrder")
orderby q.Field<string>("AscOrDesc") == "A" ? q.Field<DateTime>("EffectiveDate") : q.Field<DateTime>("EffectiveDate") descending
select q;
Usually my statement used to be like this
var result = from q in datatable.AsEnumerable()
orderby q.PrintOrder
orderby q.AscOrDesc== "A" ? q.EffectiveDate: q.EffectiveDate descending
select q;

LINQ LEFT JOIN not working on NULL values

I have two tables Student and Marks.
Student table have the following fields:
StudentID,Name,MarkID(Nullable).
Marks table have the following fields:
MarkID,Mark
Student table
StudentID Name MarkID
1 Mark 1
2 Mike NULL
3 John NULL
4 Paul 2
Mark table
MarkID Mark
1 80
2 100
If I use the left join then i getting only mark and paul records.
I want all the records in the left table(Student)
My Query is:
var query = (from s in Students
join m in Marks on s.MarkID equals m.MarkID
into mar from subMark in mar.DefaultIfEmpty()
where(m.Mark > 80)
Select s.Name)
.ToList()
Note: It is an Example only.
While joining two tables using left join and applying where condition on the second table ,If joined column value is null in first table,it won't bring the record from first table.
NULL comparisons are always false. That's the way SQL's three-valued logic works. If you want to match rows where the values are both null you should use a statement that checks both of them for null.
In a SQL statement you would write:
ON S.MARKID=M.MARKID OR (S.MARKID IS NULL AND M.MARKID IS NULL)
In C# you can use the comparison operator and your LINQ provider will convert this to IS NULL, eg:
on s.MarkID == m.MarkID || (s.MarkID == null && m.MarkID==null)
The problem is we use the where clause in Left join.So it will discard the null value records.
var sampleQuery= (from f in food
join j in juice on f.ID equals j.ID into juiceDetails from juice in juiceDetails.DefaultIfEmpty()
where(!j.deleted)
join fr in fruit on f.ID equals fr.ID into fruitDetails from fruit in fruitDetails.DefaultIfEmpty()
where(!fr.deleted)
select new
{
// codes
});
Instead of this we have to check the where clause in table itself.Like this
var sampleQuery= (from f in food
join j in juice.Table().where(x=>!x.deleted) on f.ID equals j.ID into juiceDetails from juice in juiceDetails.DefaultIfEmpty()
join fr in fruit.Table().where(x=>!x.deleted) on f.ID equals fr.ID into fruitDetails from fruit in fruitDetails.DefaultIfEmpty()
select new
{
// codes
});
It will work fine.
Thank you.
/EDIT: My first answer was using a FULL OUTER JOIN. this was way over the top and probably wrong or not compleltly correct.
The new answer uses a LEFT OUTER JOIN. I have created some sample data using LinqPad to get a working example. Ignore the .Dump() method if you are not using LinqPad.
var Students = new List<Student>() {
new Student() {StudentId = 1, Name ="John", MarkId = 1},
new Student() {StudentId = 1, Name ="Paul", MarkId = 1},
new Student() {StudentId = 1, Name ="Steve", MarkId = 1},
new Student() {StudentId = 1, Name ="John", MarkId = 2},
new Student() {StudentId = 1, Name ="Paul", MarkId = 3},
new Student() {StudentId = 1, Name ="Steve", MarkId = 1},
new Student() {StudentId = 1, Name ="Paul", MarkId = 3},
new Student() {StudentId = 1, Name ="John" },
new Student() {StudentId = 1, Name ="Steve" },
new Student() {StudentId = 1, Name ="John", MarkId = 1}
};
var Marks = new List<Mark>() {
new Mark() {MarkId = 1, Value = 60},
new Mark() {MarkId = 2, Value = 80},
new Mark() {MarkId = 3, Value = 100}
};
var StudentMarks = Students
.GroupJoin(
Marks,
st => st.MarkId,
mk => mk.MarkId,
(x,y) => new {
StudentId = x.StudentId,
Name = x.Name,
Mark = y.Select (z => z.Value).SingleOrDefault()
}
)
.Dump();
}
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public int MarkId { get; set; }
}
public class Mark
{
public int MarkId { get; set; }
public int Value { get; set; }
}
Output:
As you cann see in my Students list, there a 2 students without a MarkId. Those 2 get the default value assigned due to .SingleOrDefault(). I think this will solve your problem and gives you a good basis for further fiddeling.
references:
How do you perform a left outer join using linq extension methods
In your query you have written From in your Join statement while joining it.
Instead you should use in::
from s in Students
join m in Marks on s.MarkID equals m.ID into mar
from subMark in mar.DefaultIfEmpty()
Select s.Name).ToList()
I had the same problem. This solution only works if you have at least one row in subMark. The rows' ID doesn't matter.
var query = (from s in Students
join m in Marks on s.MarkID equals m.MarkID into fullM
into mar from subMark in mar.DefaultIfEmpty()
where(m.Mark > 80)
Select s.Name)
.ToList()
the keyword into does the magic. Adding it shows all rows, also those, which have NULL-Values in mar.

Optionally loading/selecting a column in linq

So lets say I have a linq query like so
var stuff = from t1 in TableOne
let t2 = t1.TableTwo
where t1.id = "someId"
select
new
{
column1 = t1.Col1,
column2 = t2.Col1
column3 = (from t3 in TableThree
where t3.id = "someId"
select new SomeObject
{
Field1 = t3.Col1,
Field2 = t3.Col2
}).ToList()
}
Now in some scenarios i want to load the data in column 3, as in when a parameter passed in to the method contains this is not null. And when the parameter is null i dont want to load it for effciencies sake. Any suggestions on how i can achieve the desired result?
In your subquery check if the parameter someParameter is not null.
column3 = (from t3 in TableThree
where
someParameter != null &&
t3.id == "someId"
select new SomeObject
{
Field1 = t3.Col1,
Field2 = t3.Col2
}).ToList()

Merging two tables and concatenating the values using linq

My table data looks like:
table 1:
Id CId Message
1 1 E:MMM
1 1 E:NNN
1 1 E:OOO
1 2 E:PPP
1 2 E:PPP
table 2:
Id CId Message
1 1 W:NNN
1 1 W:OOO
After merging two tables using linq my output table should be like:
Result table:
Id CId ErMessage WrMessage
1 1 E:MMM*E:NNN*E:OOO W:NNN*W.OOO
1 2 E:PPP*E:PPP
Please help me how to achieve.
var q =
from t1 in
table1.GroupBy(g => g.ID).Select(g => new
{
ID = g.Key,
Message = string.Join("*", g.Select(v => v.Message).ToArray())
})
join
t2 in
table2.GroupBy(g => g.ID).Select(g => new
{
ID = g.Key,
Message = string.Join("*", g.Select(v => v.Message).ToArray())
}) on t1.ID equals t2.ID
select new
{
ID = t1.ID,
ErMessage = t1.Message,
WrMessage = t2.Message
};

Categories