Linq Full Outer Join on Two Objects - c#

I have two objects called CountryMobility that I believe I need to combine with a full outer join. How can I do this using linq?
public class CountryMobility
{
public string countryCode { get; set; }
public int inbound { get; set; }
public int outbound { get; set; }
}
I want to combine two of these objects like so:
inboundStudents:
countryCode | inbound | outbound
EG | 2 | 0
CA | 3 | 0
CH | 5 | 0
outboundStudents:
countryCode | inbound | outbound
PE | 0 | 1
CA | 0 | 4
CH | 0 | 5
-
-
-
-
V
combinedStudents:
countryCode | inbound | outbound
PE | 0 | 1
CA | 3 | 4
CH | 5 | 5
EG | 2 | 0
I have tried the following linq statements but have not been able to figure out the correct syntax. I am currently getting a syntax error near
temp.DefaultIfEmpty(new { first.ID, inbound = 0, outbound=0 }) in both statements.
var leftOuterJoin =
from first in inboundActivities
join last in outboundActivities
on first.countryCode equals last.countryCode
into temp
from last in temp.DefaultIfEmpty
(new { first.countryCode, inbound = 0, outbound=0 })
select new CountryMobility
{
countryCode = first.countryCode,
inbound = first.inbound,
outbound = last.outbound,
};
var rightOuterJoin =
from last in outboundActivities
join first in inboundActivities
on last.countryCode equals first.countryCode
into temp
from first in temp.DefaultIfEmpty
(new { last.countryCode, inbound = 0, outbound = 0 })
select new CountryMobility
{
countryCode = last.countryCode,
inbound = first.inbound,
outbound = last.outbound,
};
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);

After your latest information. It seems to me that you can do something much simpler. Namely a UNION ALL that you subsequently group by country code.
A UNION ALL can be created using the Concat method.
The sample below works for me (using in memory collections). The query is shown in the Run method.
public class CountryMobility
{
public string countryCode { get; set; }
public int inbound { get; set; }
public int outbound { get; set; }
}
public static class JoinedMobilityQuery
{
static CountryMobility[] inbound = {
new CountryMobility() { countryCode = "EG", inbound = 2 },
new CountryMobility() { countryCode = "CA", inbound = 3 },
new CountryMobility() { countryCode = "CH", inbound = 5 },
};
static CountryMobility[] outbound = {
new CountryMobility() { countryCode = "PE", outbound = 1 },
new CountryMobility() { countryCode = "CA", outbound = 4 },
new CountryMobility() { countryCode = "CH", outbound = 6 },
};
static IQueryable<CountryMobility> Inbound()
{
return inbound.AsQueryable();
}
static IQueryable<CountryMobility> Outbound()
{
return outbound.AsQueryable();
}
public static void Run()
{
var transfers = from t in Inbound().Concat(Outbound())
group t by t.countryCode into g
select new CountryMobility() {
countryCode = g.Key,
inbound = g.Sum(x => x.inbound),
outbound = g.Sum(x => x.outbound),
};
foreach (var transfer in transfers)
Console.WriteLine("{0}\t{1}\t{2}", transfer.countryCode, transfer.inbound, transfer.outbound);
}
}

Your DefaultIfEmpty is thropwing an error, because you are defining an anonymous object, but you are creating stronly typed objects in your select statement. They both have to be of the same type.
So define a default object like this:
var defaultActivity = new CountryMobility() { countryCode = String.Empty, outbound = 0, inbound = 0 };
After this, use it in your DefaultIfEmpty() method:
from last in temp.DefaultIfEmpty(defaultActivity)
select new CountryMobility
{
//...
};
last but not least, you have to do a groupby to get the desired results:
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin)
.GroupBy (oj => oj.countryCode)
.Select (oj => oj.FirstOrDefault());
Output:
Full Demo Code:
http://share.linqpad.net/u46gar.linq

You can do like this
List<CountryMobility> inboundStudents = new List<CountryMobility>{
new CountryMobility { countryCode="EG", inbound=2, outbound = 0},
new CountryMobility { countryCode="CA", inbound=3, outbound = 0},
new CountryMobility { countryCode="CH", inbound=5, outbound = 0}};
List<CountryMobility> outboundStudents = new List<CountryMobility>{
new CountryMobility { countryCode="PE", inbound=0, outbound = 1},
new CountryMobility { countryCode="CA", inbound=0, outbound = 4},
new CountryMobility { countryCode="CH", inbound=0, outbound = 5}};
var joinedList = inboundStudents.Concat(outboundStudents).GroupBy(item => new { item.countryCode});
var result = joinedList.Select(x => new
{
countryCode = x.Key.countryCode,
inbound = x.Sum(i => i.inbound),
outbound = x.Sum(i => i.outbound)
});

Related

Left join and count based on multiple values using linq c#

I have 2 lists. List A consists of this value,
status | level
-----------------
open | low
open | medium
open | high
closed | low
closed | medium
closed | high
List B consists of this value,
task | status | level
------------------------
A | open | low
B | open | medium
C | closed | high
D | closed | low
E | open | low
I want to do left join (all value inside list A must be in the new list), and count the number of tasks with related to status. I want the level value as well since it will be used later in my code. The expected output:
status | level | count
-------------------------
open | low | 2
open | medium | 1
open | high | 0
closed | low | 1
closed | medium | 0
closed | high | 1
I know there are many answers here which provides the ways to code, but I'm still stuck, because my code doesnt work, it seems like it does not do the group by method because when I count, the value shown is one for all status.
var joined3 = (from id1 in joined
join id2 in tr
on new { lev = id1.Key.ToString(), stat = id1.Value.ToString() } equals new { lev = id2.Level.ToString(), stat = id2.Status.ToString() } into grouped
from id2 in grouped.DefaultIfEmpty()
group id2 by new {level = id1.Key, status = id1.Value } into grouped
select new
{
level = grouped.Key.level,
status = grouped.Key.status,
count = grouped.Count()
}).ToList();
The problem is that because of the left-join semantics of DefaultIfEmpty(), you always have at least one row. So you need to add a predicate to the Count()
var joined3 = (
from id1 in joined
join id2 in tr
on new { lev = id1.Key, stat = id1.Value } equals new { lev = id2.Level, stat = id2.Status } into grouped
from id2 in grouped.DefaultIfEmpty()
group id2 by new {level = id1.Key, status = id1.Value } into grouped
select new
{
level = grouped.Key.level,
status = grouped.Key.status,
count = grouped.Count(id2 => id2.Key != null)
}).ToList();
Alternatively, a simpler method is: don't group, but instead use a correlated count of the other list
var joined3 = (
from id1 in joined
select new
{
level = id1.level,
status = id1.status,
count = tr.Count(id2 => id2.Key == id1.Key && id2.Value == id1.Value)
}).ToList();
I see no reason to use ToString here, and it is likely to impact performance. Key and Value should be the same type on each list/table respectively.
var list1 = new List<Type1>
{
new Type1() {Status = StatusEnum.Open, Level = LevelEnum.Low},
new Type1() {Status = StatusEnum.Open, Level = LevelEnum.Medium},
new Type1() {Status = StatusEnum.Open, Level = LevelEnum.High},
new Type1() {Status = StatusEnum.Closed, Level = LevelEnum.Low},
new Type1() {Status = StatusEnum.Closed, Level = LevelEnum.Medium},
new Type1() {Status = StatusEnum.Closed, Level = LevelEnum.High}
};
var list2 = new List<Type2>
{
new Type2() {TaskDescription = "A", Status = StatusEnum.Open, Level = LevelEnum.Low},
new Type2() {TaskDescription = "B", Status = StatusEnum.Open, Level = LevelEnum.Medium},
new Type2() {TaskDescription = "C", Status = StatusEnum.Closed, Level = LevelEnum.High},
new Type2() {TaskDescription = "D", Status = StatusEnum.Closed, Level = LevelEnum.Low},
new Type2() {TaskDescription = "E", Status = StatusEnum.Open, Level = LevelEnum.Low}
};
var list3 = new List<Type3>();
foreach (var t in list1)
{
list3.Add(new Type3()
{Level = t.Level, Status = t.Status, Count = list2.Count(x => x.Level == t.Level && x.Status == t.Status)});
}
foreach (var t in list3)
{
Console.WriteLine($"{t.Status}/{t.Level}/{t.Count}");
}
class Type1
{
public StatusEnum Status { get; set; }
public LevelEnum Level { get; set; }
}
class Type2 : Type1
{
public string TaskDescription { get; set; }
}
class Type3 : Type2
{
public int Count { get; set; }
}
public enum StatusEnum
{
Open,
Closed
}
public enum LevelEnum
{
Low,
Medium,
High
}

Perform a lookup between C# Lists and return matched where value is minimum

I have two lists:
var qtys = new List<InventoryQuantity>()
{
new InventoryQuantity() { WarehouseId = 1, QuantityInWarehouse = 0 },
new InventoryQuantity() { WarehouseId = 2, QuantityInWarehouse = 452 },
new InventoryQuantity() { WarehouseId = 3, QuantityInWarehouse = 184 },
new InventoryQuantity() { WarehouseId = 4, QuantityInWarehouse = 328 },
new InventoryQuantity() { WarehouseId = 5, QuantityInWarehouse = 0 },
};
var times = new List<WarehouseTransitTime>()
{
new WarehouseTransitTime() { WarehouseId = 1, TransitDays = 1 },
new WarehouseTransitTime() { WarehouseId = 2, TransitDays = 4 },
new WarehouseTransitTime() { WarehouseId = 3, TransitDays = 2 },
new WarehouseTransitTime() { WarehouseId = 4, TransitDays = 3 },
new WarehouseTransitTime() { WarehouseId = 5, TransitDays = 5 },
};
class InventoryQuantity
{
public int WarehouseId { get; set; }
public int QuantityInWarehouse { get; set; }
}
class WarehouseTransitTime
{
public int WarehouseId { get; set; }
public int TransitDays { get; set; }
}
I need to return the WarehouseId from qtys where the Quantity > 0 and the WarehouseId equals the minimum transit days WarehouseId in times.
I know I can do something like below but seems clunky and there must be an elegant solution.
public int NearestWarehouse()
{
var withQty = qtys.Where(i => i.QuantityInWarehouse > 0);
var orderedTransit = times.OrderBy(tt => tt.TransitDays).ToList();
//loop and compare
}
Example data:
qtys
WarehouseId | Quantity
1 | 0
2 | 452
3 | 184
4 | 328
5 | 0
times
WarehouseId | TransitTime
1 | 1
2 | 4
3 | 2
4 | 3
5 | 5
Expected output would be 3, because warehouse 3 has inventory and the shortest transit time (2)
It seems to me that the cleanest and simplest query is this:
var query =
from q in qtys
where q.QuantityInWarehouse > 0
join t in times on q.WarehouseId equals t.WarehouseId
orderby t.TransitDays
select q.WarehouseId;
var warehouseId = query.FirstOrDefault();
This gives me 3.
What you want is a group join:
Functional Syntax
var query1 = qtys.Where(q => q.QuantityInWarehouse > 0)
.GroupJoin(times, q => q.WarehouseId, t => t.WarehouseId, (q, t) => new { q.WarehouseId, TransitDays = t.DefaultIfEmpty().Min(grp => grp?.TransitDays) })
.OrderBy(g => g.TransitDays)
.FirstOrDefault();
Query Syntax
var query2 = from q in qtys
join t in times on q.WarehouseId equals t.WarehouseId into grp
where q.QuantityInWarehouse > 0
select new
{
q.WarehouseId,
TransitDays = grp.DefaultIfEmpty().Min(g => g?.TransitDays)
};
var result = query2.OrderBy(g => g.TransitDays)
.FirstOrDefault();
A group join will join two lists together on their corresponding keys--similar to a database join--and the associated values to those keys will be grouped into an enumerable. From that enumerable, you can derive the minimum value that you care about, TransitDays in this case.
There is no equivalent to "first or default" in query syntax. The easiest approach is just to apply the same OrderBy and FirstOrDefault against the query variable, as demonstrated above.
Well you mention an AND relation between the two, right?
I was thinking of databases with a forignkey... but Linq prety much does it if your lists aren't to big:
keys = qtys.Where(i => i.QuantityInWarehouse > 0).Select(i => i.WarehouseId).ToList();
// get the smallest not a list
var result = times.Where(tt => keys.Contains(tt.wharehouseId)).orderby(tt => tt.Transitdays).FirstOrDefault();
Otherwise you could have Dictionary with ID as key...
You can do it like this..
var withQty = (from q in qtys
join t in times on q.WarehouseId equals t.WarehouseId
where q.QuantityInWarehouse > 0
select new { q.WarehouseId, t.TransitDays })
.OrderBy(item => item.TransitDays).FirstOrDefault();
return withQty?.WarehouseId ?? 0;

How to use flags to check if something from the collection matches the flag

I'm trying to use flags to filter collection and retrive certain objects.
Perhaps the example will show the issue.
I defined a class and an enum.
public class ExampleFlagsDto
{
public int FlagId { get; set; }
public string Name { get; set; }
}
[Flags]
public enum FlagsTypes
{
None = 0,
Small = 1 << 0 ,
Medium = 1 << 1 ,
Normal = 1 << 2,
Large = 1 << 3,
LargeAndNormal = Normal | Large,
All = Normal | Medium | Small | Large,
}
Then I constructed a list as an example and tried to retrive 2 objects from the list.
var examples = new List<ExampleFlagsDto>()
{
new ExampleFlagsDto
{
FlagId = (int)FlagsTypes.Normal,
Name = "First"
},
new ExampleFlagsDto
{
FlagId = (int)FlagsTypes.Medium,
Name = "Second"
},
new ExampleFlagsDto
{
FlagId = (int)FlagsTypes.Large,
Name = "Third"
},
new ExampleFlagsDto
{
FlagId = (int)FlagsTypes.Small,
Name = "Forth"
},
};
var selected = examples.Where(C => C.FlagId == (int)FlagsTypes.LargeAndNormal).ToList();
foreach (var flag in selected)
{
Console.WriteLine(flag.Name);
}
Of course, it doesn't work. I know that when it comes to bits, (int)FlagTypes.LargeAndNormal would result in a sum of bits of Large and Normal. I have no idea how it has to look like bitwise, though.
I'm looking for a way to change the
examples.Where(C => C.FlagId == (int)FlagsTypes.LargeAndNormal).ToList();
to a solution that would result in selected having 2 objects from examples.
You could try this solution:
var selectedAll = examples.Where(C => (C.FlagId & (int)FlagsTypes.All) == (int)C.FlagId).ToList();

Creating a list by adding items when conditions are met

public class Connector
{
public double Id { get; set; }
public string Name { get; set; }
public double Len { get; set; }
public double Height { get; set; }
public double Count { get; set; }
}
I have a list of such facilities:
List<Connector> resutsList = new List<Connector>();
Below is an example of the contents of such a list:
1 | IZO | 1000 | 200 | 2
2 | IZO | 1000 | 200 | 4
3 | IZO | 600 | 200 | 5
4 | IZO | 1000 | 200 | 2
5 | IZO | 600 | 180 | 7
6 | IZO | 600 | 180 | 3
I need such a result: (This is the sum of the Count positions when the Len and Height conditions are met.)
1 | IZO | 1000 | 200 | 8
2 | IZO | 600 | 200 | 5
3 | IZO | 600 | 180 | 10
Is it possible to do any Linq combination?
Or another simple solution?
Here's my effort.
class Program
{
public class Connector
{
public Connector(double id, string name, double len, double height, double count)
{
Id = id;
Name = name;
Len = len;
Height = height;
Count = count;
}
public double Id { get; set; }
public string Name { get; set; }
public double Len { get; set; }
public double Height { get; set; }
public double Count { get; set; }
}
static void Main(string[] args)
{
var l = new List<Connector>
{
new Connector(1, "IZO", 1000, 200, 2),
new Connector(2, "IZO", 1000, 200, 4),
new Connector(3, "IZO", 600, 200, 5),
new Connector(4, "IZO", 1000, 200, 2),
new Connector(5, "IZO", 600, 180, 7),
new Connector(6, "IZO", 600, 180, 3)
};
var sums = from c in l
group c by new { c.Name, c.Len, c.Height } into g
select new { g.First().Id, g.Key.Name, g.Key.Len, g.Key.Height, Count = g.Sum(x => x.Count) };
}
}
```
Please note that the ids are not exactly like in your example. (1,2,3 vs 1,3,5)
I don't believe you can get the index with query expression syntax, but here is another Linq way to do it and get the desired indexes:
var sums = l.GroupBy(c => new { c.Name, c.Len, c.Height })
.Select((g, index) => new{
Id = index+1,
g.Key.Name,
g.Key.Len,
g.Key.Height,
Count = g.Sum(x => x.Count)
});
Please note the index + 1
What you're trying to do here is group your list by Name, Len & Height, which you can do using the LINQ GroupBy method.
Then, you want to project that group to a new object using Select and a Sum aggregation on the Count property. For example:
var result = list
.GroupBy(x => new { x.Name, x.Len, x.Height })
.Select(x => new { x.Key.Name, x.Key.Len, x.Key.Height, Count = x.Sum(y => y.Count) })
.ToList();
As for the ID - well it makes a limited amount of sense in an aggregate operation. You have basically 2 choices
Use an incrementing number as one of the other answers does
.Select( (x,i) => new { ID = i, ....
That the first ID from the group
.Select(x => new { ID = x.First().ID, ....
you can try is
Here we group the elements of resultList by three conditions Name,Len,Height.Then we create a new Connector object from that group by by using the Len,Height,Name,& Id then we Sum all the elements present in that group and assign Count var with the Sum.
var List = from result in resultList
group d by new { d.Name, d.Len , d.Height} into g
select new Connector
(
Id = g.First().ID,
Name = g.Key.Name,
Len = g.Key.Len,
Height = g.Key.Height,
count = g.Sum(s => s.Count)
);
Note:- this will not generate incrementing ID if you want that you may refer #tymtam's answer

How to combine many rows into single text?

I want to grouping phone number to the same section like this :
section | phone
1 | 1001
1 | 1002
2 | 1201
2 | 1202
and grouping them in like this :
section | phone
1 | 1001, 1002
2 | 1201, 1202
but i don't know syntax to groping them to that.
This code i do
var entgr = (from fx in MainOnline.MA_TelUsers
where fx.TE_SectionID != null
group fx by fx.TE_SectionID into id
from ids in id.DefaultIfEmpty()
select new
{
Section = ids.TE_SectionID,
TelPhone = ids.TE_Phone
});
How I grouping that and use it to join other table?
var entgr = (from fx in ph
group fx by fx.SectionId.ToString() into id
select new
{
Section = id.Key,
TelPhone = string.Join(", ",id.Select(s => s.PhoneNumber.ToString()))
});
try this query
var entgr = (from fx in MainOnline.MA_TelUsers
where fx.TE_SectionID != null
group fx by fx.TE_SectionID into ids
select new
{
Section = ids.TE_SectionID,
TelPhone =ids.Aggregate((a, b) =>
new {TelPhone = (a.TelPhone + ", " + b.TelPhone ) }).TelPhone
});
https://msdn.microsoft.com/en-us/library/vstudio/bb397696.aspx
See this link. If you want to it do it in single linq query then that I hope not possible.
But at evaluation time you can do like this
var ph = new List<Phone>();
ph.Add(new Phone() { SectionId = 1, PhoneNumber = 1001 });
ph.Add(new Phone() { SectionId = 1, PhoneNumber = 1002 });
ph.Add(new Phone() { SectionId = 2, PhoneNumber = 1201 });
ph.Add(new Phone() { SectionId = 2, PhoneNumber = 1202 });
var results = ph.GroupBy(i => i.SectionId).Select(i=> new {i.Key, i});
foreach (var phone in results)
{
int section = phone.Key;
string phoneNos = string.Join(",",phone.i.Select(i=>i.PhoneNumber));
}

Categories