How to perform Dynamic Join using LINQ

前端 未结 1 424
没有蜡笔的小新
没有蜡笔的小新 2021-01-13 18:59

I have two collections. Each collection contains instances of a specific type. I need to join these two collections using one of the properties of the instances from each of

相关标签:
1条回答
  • 2021-01-13 19:09

    The first clue to solving this is to re-write your join using lamda syntax for the Join

    var query = fMyTranList.Join(fYourTranList,
                   a => a.MyAmountGBP, 
                   b => b.YourAmountGBP, 
                   (c,d) => new
                        {
                            MyId = c.Id,
                            YourId = d.Id,
                            MyAmtUSD = c.MyAmountUSD,
                            MyAmtGBP = c.MyAmountGBP,
                            YourAmtUSD = d.YourAmountUSD,
                            YourAmtGBP = d.YourAmountGBP
                        });
    

    Live: http://rextester.com/OGC85986

    Which should make it clear that making this dynamic would require passing in to your "generic" join function 3 functions

    1. a Func<MyTran,TKey> function for selecting the key for LHS
    2. a Func<YourTran,TKey> function for selecting the key for RHS
    3. a Func<MyTran,YourTran,TResult> function for selecting the result

    So from there you could use reflection to make this all a bit dynamic:

    public static class DynamicJoinExtensions
    {
        public static IEnumerable<dynamic> DynamicJoin(this IEnumerable<MyTran> myTran, IEnumerable<YourTran> yourTran, params Tuple<string, string>[] keys)
        {
            var outerKeySelector = CreateFunc<MyTran>(keys.Select(k => k.Item1).ToArray());
            var innerKeySelector = CreateFunc<YourTran>(keys.Select(k => k.Item2).ToArray());
    
            return myTran.Join(yourTran, outerKeySelector, innerKeySelector, (c, d) => new
            {
                MyId = c.Id,
                YourId = d.Id,
                MyAmtUSD = c.MyAmountUSD,
                MyAmtGBP = c.MyAmountGBP,
                YourAmtUSD = d.YourAmountUSD,
                YourAmtGBP = d.YourAmountGBP
            }, new ObjectArrayComparer());
        }
    
        private static Func<TObject, object[]> CreateFunc<TObject>(string[] keys)
        {
            var type = typeof(TObject);
            return delegate(TObject o)
            {
                var data = new object[keys.Length];
                for(var i = 0;i<keys.Length;i++)
                {
                    var key = type.GetProperty(keys[i]);
                    if(key == null)
                        throw new InvalidOperationException("Invalid key: " + keys[i]);
                    data[i] = key.GetValue(o);
                }
                return data;
            };
        }
    
        private class ObjectArrayComparer : IEqualityComparer<object[]>
        {
    
            public bool Equals(object[] x, object[] y)
            {
                return x.Length == y.Length
                       && Enumerable.SequenceEqual(x, y);
            }
    
            public int GetHashCode(object[] o)
            {
                var result = o.Aggregate((a, b) => a.GetHashCode() ^ b.GetHashCode());
                return result.GetHashCode();
            }
        }
    }
    

    Usage to match your example would then be:

    var query = fMyTranList.DynamicJoin(fYourTranList, 
                   Tuple.Create("MyAmountGBP", "YourAmountGBP"));
    

    but as the keys are params you can pass as many as you like:

    var query = fMyTranList.DynamicJoin(fYourTranList, 
                  Tuple.Create("MyAmountGBP", "YourAmountGBP"), 
                  Tuple.Create("AnotherMyTranProperty", "AnotherYourTranProperty"));
    

    Live example: http://rextester.com/AAB2452

    0 讨论(0)
提交回复
热议问题