LINQ, INNER JOIN (Join or GroupJoin)? - c#

I am kind of lost between Join and GroupJoin. Which way is the right way to do INNER JOIN? On one hand Join is doing the right job, but I have to call Distinct. On the other hand GroupJoin is grouping by itself, but gives me empty RHS.
Or there is a better way?
using System;
using System.Linq;
public class Foo
{
public string Name { get; set; }
public Foo(string name)
{
Name = name;
}
}
public class Bar
{
public Foo Foo { get; set; }
public string Name { get; set; }
public Bar(string name, Foo foo)
{
Foo = foo;
Name = name;
}
}
public class Program
{
public static Foo[] foos = new[] { new Foo("a"), new Foo("b"), new Foo("c"), new Foo("d") };
public static Bar[] bars = new[] { new Bar("1", foos[1]), new Bar("2", foos[1]) };
public static void Main(string[] args)
{
#if true
var res = foos.Join(
bars,
f => f,
b => b.Foo,
(f, b) => f
)
.Distinct();
#else
var res = foos.GroupJoin(
bars,
f => f,
b => b.Foo,
(f, b) => new { f, b }
)
.Where(t => t.b.Any())
.Select(t => t.f);
#endif
foreach (var r in res)
Console.WriteLine(r.Name);
}
}
Thanks!

The key to understanding this is to look at the types of the parameters for that last lambda you're passing in.
For Join, the b will be a single bar, and you will get a row for every bar that has a match.
While for GroupJoin, the b will be a collection of bar, and you will get a single row for every foo that has a match.
Both perform an inner join, but if you're looking for SQL's INNER JOIN, the Join method is what you want.

Related

How to have a changing name as Key and it's value as Count in select

So I'm not sure if this is possible, but I'm doing a final select query, and I've noticed that it would be easier for my data if I could do something like:
var finalQuery = selectQuery
.GroupBy(x => x.ProductName)
.Select(c => new
{
c.Key = c.Count()
}).ToList();
Therefore, I want the returned data to be something like:
[{
"Clothes" : 5,
"Shoes" : 7,
"Laptop" : 10
}]
My current query is:
var finalQuery = selectQuery
.GroupBy(x => x.ProductName)
.Select(c => new
{
ProductName = c.Key
ProductCount = c.Count()
}).ToList();
I thought something like c.Key.ToString() would do the trick, but it doesn't work. I'm guessing the "Key" must be a set value to work, and it can't necessarily be dynamically changed?
You can try this way
var finalQuery = selectQuery.GroupBy(p => p.ProductName).ToDictionary(g => g.Key, g => g.Count());
You should customize the string using your own code, or you can use an external library as NewtonSoft.Json.
Below is a sample code that does the trick for you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class Test
{
public static void Main()
{
var products = new List<Product>()
{
new Product("a", "Clothes"),
new Product("b", "Shoes"),
new Product("c", "Clothes"),
new Product("d", "Clothes"),
new Product("e", "Shoes"),
new Product("f", "Shoes"),
new Product("g", "Laptop"),
new Product("h", "Laptop"),
new Product("h", "Shoes"),
};
var result = products
.GroupBy(p => p.Type)
.Select(
group => new
ProductCount(group.Key, group.Count())
// You can use also an object as the commented code below
//{
// Type = group.Key,
// Count = group.Count()
//}
)
.ToList();
Console.WriteLine(ProductCount.ToFormatedString(result));
}
class Product
{
public string Name { get; set; }
public string Type { get; set; }
public Product(string name, string type)
{
this.Name = name;
this.Type = type;
}
}
class ProductCount
{
public string Type { get; set; }
public int Count { get; set; }
public ProductCount(string type, int count)
{
this.Type = type;
this.Count = count;
}
public override string ToString()
{
return $"\"{this.Type}\" : {this.Count}";
}
public static string ToFormatedString(IEnumerable<ProductCount> products) // if you need a more generic method, u can make this an extension method and use Reflection
// Or u can use a nuget package that formats objects to json (e.g: Newtonsoft is a good library)
{
var sb = new StringBuilder();
sb.AppendLine("[{");
foreach (var item in products)
sb.AppendLine(item.ToString());
sb.AppendLine("}]");
return sb.ToString();
}
}
}

Cannot use aggregate on a List<T>

I have a list which I filtered in this way:
var items = myList.Select(c => c.foo == 1)
.Aggregate((decimal s, decimal c) => s.prop + a.prop);
the compiler say:
Select does not contains a definition for Aggregate
what I did wrong??
I need to sum of the value of the list that match with 1, if I use only Where I get list does not contain a definition for aggregate which is my question.
Here is an approach that works with Aggregate.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var myList = new List<SomeObj>() { new SomeObj(1, 10), new SomeObj(1, 20) };
var sum = myList
// filter for values of foo that equal 1
.Where(c => c.foo == 1)
// accumulate those values
.Aggregate(0m, (decimal acc, SomeObj next) => acc += next.prop);
Console.WriteLine(sum);
}
}
public class SomeObj
{
public int foo { get; set; } = 1;
public int prop { get; set; } = 10;
public SomeObj(int foo, int prop)
{
this.foo = foo;
this.prop = prop;
}
}

LINQ order by delegate

I have a collection of objects to be ordered by an object's field value. Current problem is that the order depends on a business logic.
public enum Order : byte {
a = 1,
b = 2,
c = 3
}
public class Foo{
public long A {get;set;}
public long B {get;set;}
public long C {get;set;}
}
public class Worker(){
private Foo[] orderFoos(Foo[] foos, Func<Order, long> sort){
return foos.OrderByDescending(f => sort(f)).ToArray(foos.Length);
}
public void Work(){
Foo[] foos = getFoos();
var orderByA = orderFoos(foos, f => f.A);
var orderByB = orderFoos(foos, f => f.B);
var orderByC = orderFoos(foos, f => f.C);
}
}
Compiler throws an error that Argument 1: cannot convert from 'Foo' to 'Order'. Are there any workarounds or solutions?
It seems what you wanted to achieve is sorting on different fields. You may not need to have Order enum if it's only for that purpose and replace:
private Foo[] orderFoos(Foo[] foos, Func<Order, long> sort){
return foos.OrderByDescending(f => sort(f)).ToArray(foos.Length);
}
into
private Foo[] orderFoos(Foo[] foos, Func<Foo, long> sort){
return foos.OrderByDescending(sort).ToArray(foos.Length);
}
NB: I'm not sure your intention with adding foos.Length in the ToArray method, but supposedly that's out of the scope of the question.
The below code seems to work. It is a small change to the orderFoos method, with some sample code for you to test the results.
using System;
using System.Linq;
public enum Order : byte
{
a = 1,
b = 2,
c = 3
}
public class Foo
{
public long A { get; set; }
public long B { get; set; }
public long C { get; set; }
}
public class Worker
{
private Foo[] orderFoos(Foo[] foos, Func<Foo, long> sort)
{
return foos.OrderByDescending(sort).ToArray();
}
public void Work()
{
Foo[] foos = { new Foo() { A = 1, B = 2, C = 3 }, new Foo() { A = 10, B = 1, C = 2 }, new Foo() { A = -1, B = 1, C = 10 } };
var orderByA = orderFoos(foos, f => f.A);
var orderByB = orderFoos(foos, f => f.B);
var orderByC = orderFoos(foos, f => f.C);
Console.WriteLine(orderByA.First().A); // I expect the second to be first here so 10
Console.WriteLine(orderByB.First().A); // I expect the first to be first here so 1
Console.WriteLine(orderByC.First().A); // I expect the third to be first here so -1
}
}
class Program
{
static void Main(string[] args)
{
var worker = new Worker();
worker.Work();
Console.ReadLine();
}
}
#hsoesanto gave a good solution but it doesn't work the way I expected it would be.
So I've created temporary workaround.
private Func<Foo, long> GetOrderFunction(Order orderType)
{
switch (orderType)
{
case Order.A:
return (f) => f.A;
case Order.B:
return (f) => f.B;
case Order.C:
return (f) => f.C;
}
}
private Foo[] orderFoos(Foo[] foos, Order order)
{
var orderFunction = GetOrderFunction(order);
return foos
.OrderByDescending(f => orderFunction (f))
.ToArray(foos.Length);
}

Cannot access members of a class in the select linq method

static void Main(){
List<Foo> t = new List<Foo>{
new Foo(){Id=1,Name="A",Value=1},
new Foo(){Id=2,Name="B",Value=1},
new Foo(){Id=3,Name="C",Value=1},
new Foo(){Id=3,Name="D",Value=1}};
var x = t.GroupBy(gp => gp.Id).Select(sel => new Foo { Id = ,Name=,Value= });
}
public class Foo{
public string Name { get; set; }
public int Id { get; set; }
public int Value { get; set; }
}
In the var x I want to group all the Foo objects by their ID and get the SUM in the Value field.
The problem is that it seems I cannot access the members/fields of the class in the select method.
Thanks
After GroupBy you don't select an IEnumerable<Foo> but groups of them. You probably want:
var x = t.GroupBy(f => f.Id)
.Select(grp => new Foo {
Id = grp.Key,
Name = String.Join(",", grp.Select(f => f.Name)),
Value = grp.Sum(f => f.Value)
});
I'm using String.Join to concenate all names of each ID-group, the values are summed.
Try this way
var x = t.GroupBy(gp => gp.Name).OrderBy(group => group.Key).Select(group => Tuple.Create(group.Key, group.Count()));

How to intersect two different IEnumerable collections

i think this question has been asked before but i havent been able to deduce a clear answer. I am trying to find the best way (or a way) to intersect two completely different ienumerable collections.
class A:
int z1
int z2
int z3
string z4
class B:
int j5
int j6
T j7
T j8
string j9
..I want to intersect List<A> with List<B> on z2 == j6.
can this be done?
The question doesn't really make sense - what would the result type be? Intersections have to be performed on two sequences of the same type. It sounds like you don't so much want an intersection between two sets, as a filter of the first sequence based on possible values of z2. For example:
HashSet<int> validZ2 = new HashSet<int>(listB.Select(x => x.j6));
var filtered = listA.Where(x => validZ2.Contains(x.z2));
Or possibly as Gabe suggests, you want a join. For example:
var query = from a in listA
join b in listB on a.z2 equals b.j6
select new { a, b };
That will give you all pairings of values from the two lists which match on z2/j6.
You need to implement a custom equality comparer (see IEqualityComparer<T> interface) to pass it as a second argument to Intersect().
By using the intersect method, you can get common members between the two enumerables, like this example demonstrates:
[Test]
public void TestObjectIntersect()
{
var a = new List<object> { 1, 2, 3, "test", "test2" };
var b = new List<object> { 4, 5, 1, "test2" };
var c = a.Intersect(b);
Console.WriteLine(String.Join(",", c.Select(x => x.ToString()).ToArray()));
}
class Program
{
static void Main(string[] args)
{
var aList = (from item in Enumerable.Range(1, 10)
select new A { Z1 = item, Z2 = item * 2 }).ToList();
var bList = (from item in Enumerable.Range(10, 100)
select new B { J5 = item, J6 = item / 2 }).ToList();
var intersect = (from a in aList
join b in bList
on a.Z2 equals b.J6
select new { A = a, B = b }).ToList();
foreach (var item in intersect)
{
Console.WriteLine("A:{{{0}}}, B:{{{1}}}", item.A, item.B);
}
}
}
public class A
{
public int Z1 { get; set; }
public int Z2 { get; set; }
// other fields and properties
public override string ToString()
{
return string.Format("Z1={0}, Z2={1}", Z1, Z2);
}
}
public class B
{
public int J5 { get; set; }
public int J6 { get; set; }
// other fields and properties
public override string ToString()
{
return string.Format("J5={0}, J6={1}", J5, J6);
}
}

Categories

Resources