2

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); 
joshua-philpott
  • 213
  • 2
  • 4
  • 12
  • possible duplicate of [LINQ - Full Outer Join](http://stackoverflow.com/questions/5489987/linq-full-outer-join) – Josh L. Apr 17 '15 at 04:09
  • Which linq statements have you tried, what did they produce? What about them was not correct that you need help with? – Alex Apr 17 '15 at 04:10
  • @Josh L. Tried that but I couldn't get it to work correctly with mine. I think the issue was that it was outputting an anonymous object and I could not get it to output as my "CountryMobility" object. – joshua-philpott Apr 17 '15 at 04:11
  • I don't see any reference to a `CoutryMobility` object in your question. Therefore anything involving `CountryMobility` things must surely be irrelevant to any solution. – Alex Apr 17 '15 at 04:13
  • You need to write 3 queries. One is for left, one is for right and last for concate. Check this http://stackoverflow.com/questions/5489987/linq-full-outer-join – Akhil Apr 17 '15 at 04:14
  • @Akhil I've tried that and can't quite seem to get the syntax right. I'm having trouble near this part - from first in temp.DefaultIfEmpty (new { last.countryCode, inbound = 0, outbound = 0 }) – joshua-philpott Apr 17 '15 at 04:27
  • Thanks for your edit. I am not certain that you need to do the joins this way. Could you also post the declaration of the two classes that you are trying to join? – Alex Apr 17 '15 at 04:41
  • @Alex - The two classes that I'm trying to join are both of CountryMobility – joshua-philpott Apr 17 '15 at 04:46
  • @joshua-philpott, you are creating a anonymous object in `DefaultIfEmpty()`, but a strongly typed object in your select statement. This mismatch is throwing an exception. – Marco Apr 17 '15 at 05:07

3 Answers3

1

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);
    }
}
Alex
  • 13,024
  • 33
  • 62
1

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: enter image description here

Full Demo Code:

http://share.linqpad.net/u46gar.linq

Marco
  • 22,856
  • 9
  • 75
  • 124
0

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)
                });
Akhil
  • 1,918
  • 5
  • 30
  • 74