使用LINQ的IQueryable左外连接的扩展方法
我试图实现返回类型为IQueryable
Left outer join扩展方法。
我写的function如下
public static IQueryable LeftOuterJoin2( this IQueryable outer, IQueryable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) { return from outerItem in outer join innerItem in inner on outerKeySelector(outerItem) equals innerKeySelector(innerItem) into joinedData from r in joinedData.DefaultIfEmpty() select resultSelector(outerItem, r); }
它无法生成查询。 原因可能是:我使用了Func
而不是Expression
。 我也试过Expression
。 它在outerKeySelector(outerItem)
行上给出了一个错误,即outerKeySelector
是一个用作方法的变量
我发现了一些关于SO(例如这里 )和CodeProjects的讨论,但是那些适用于IEnumerable
类型的讨论不适用于IQueryable
。
介绍
这个问题非常有趣。 问题是Funcs是委托,而表达式是树 ,它们是完全不同的结构。 当您使用当前的扩展实现时,它使用循环并在每个元素的每个步骤上执行选择器,并且它运行良好。 但是当我们谈论entity framework和LINQ时,我们需要树遍历来将它转换为SQL查询。 所以它比Funcs更“难”(但我还是喜欢Expressions)并且下面描述了一些问题。
当你想做左外连接时你可以使用这样的东西(取自这里: 如何在JOIN扩展方法中实现左连接 )
var leftJoin = p.Person.Where(n => n.FirstName.Contains("a")) .GroupJoin(p.PersonInfo, n => n.PersonId, m => m.PersonId, (n, ms) => new { n, ms = ms.DefaultIfEmpty() }) .SelectMany(z => z.ms.Select(m => new { n = zn, m ));
这是好的,但它不是我们需要的扩展方法。 我想你需要这样的东西:
using (var db = new Database1Entities("...")) { var my = db.A.LeftOuterJoin2(db.B, a => a.Id, b => b.IdA, (a, b) => new { a, b, hello = "Hello World!" }); // other actions ... }
创建此类扩展有许多困难的部分:
脚步
考虑2个简单的表: A
(列:Id,文本)和B
(列Id,IdA,文本)。
外连接可以分3个步骤实现:
// group join as usual + use DefaultIfEmpty var q1 = Queryable.GroupJoin(db.A, db.B, a => a.Id, b => b.IdA, (a, b) => new { a, groupB = b.DefaultIfEmpty() }); // regroup data to associated list a -> b, it is usable already, but it's // impossible to use resultSelector on this stage, // beacuse of type difference (quite deep problem: some anonymous type != TOuter) var q2 = Queryable.SelectMany(q1, x => x.groupB, (a, b) => new { aa, b }); // second regroup to get the right types var q3 = Queryable.SelectMany(db.A, a => q2.Where(x => xa == a).Select(x => xb), (a, b) => new {a, b});
码
好吧,我不是一个好的出纳员,这是我的代码(对不起,我无法更好地格式化,但它有效!):
public static IQueryable LeftOuterJoin2( this IQueryable outer, IQueryable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) { // generic methods var selectManies = typeof(Queryable).GetMethods() .Where(x => x.Name == "SelectMany" && x.GetParameters().Length == 3) .OrderBy(x=>x.ToString().Length) .ToList(); var selectMany = selectManies.First(); var select = typeof(Queryable).GetMethods().First(x => x.Name == "Select" && x.GetParameters().Length == 2); var where = typeof(Queryable).GetMethods().First(x => x.Name == "Where" && x.GetParameters().Length == 2); var groupJoin = typeof(Queryable).GetMethods().First(x => x.Name == "GroupJoin" && x.GetParameters().Length == 5); var defaultIfEmpty = typeof(Queryable).GetMethods().First(x => x.Name == "DefaultIfEmpty" && x.GetParameters().Length == 1); // need anonymous type here or let's use Tuple // prepares for: // var q2 = Queryable.GroupJoin(db.A, db.B, a => a.Id, b => b.IdA, (a, b) => new { a, groupB = b.DefaultIfEmpty() }); var tuple = typeof(Tuple<,>).MakeGenericType( typeof(TOuter), typeof(IQueryable<>).MakeGenericType( typeof(TInner) ) ); var paramOuter = Expression.Parameter(typeof(TOuter)); var paramInner = Expression.Parameter(typeof(IEnumerable )); var groupJoinExpression = Expression.Call( null, groupJoin.MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), tuple), new Expression[] { Expression.Constant(outer), Expression.Constant(inner), outerKeySelector, innerKeySelector, Expression.Lambda( Expression.New( tuple.GetConstructor(tuple.GetGenericArguments()), new Expression[] { paramOuter, Expression.Call( null, defaultIfEmpty.MakeGenericMethod(typeof (TInner)), new Expression[] { Expression.Convert(paramInner, typeof (IQueryable )) } ) }, tuple.GetProperties() ), new[] {paramOuter, paramInner} ) } ); // prepares for: // var q3 = Queryable.SelectMany(q2, x => x.groupB, (a, b) => new { aa, b }); var tuple2 = typeof (Tuple<,>).MakeGenericType(typeof (TOuter), typeof (TInner)); var paramTuple2 = Expression.Parameter(tuple); var paramInner2 = Expression.Parameter(typeof(TInner)); var paramGroup = Expression.Parameter(tuple); var selectMany1Result = Expression.Call( null, selectMany.MakeGenericMethod(tuple, typeof (TInner), tuple2), new Expression[] { groupJoinExpression, Expression.Lambda( Expression.Convert(Expression.MakeMemberAccess(paramGroup, tuple.GetProperty("Item2")), typeof (IEnumerable )), paramGroup ), Expression.Lambda( Expression.New( tuple2.GetConstructor(tuple2.GetGenericArguments()), new Expression[] { Expression.MakeMemberAccess(paramTuple2, paramTuple2.Type.GetProperty("Item1")), paramInner2 }, tuple2.GetProperties() ), new[] { paramTuple2, paramInner2 } ) } ); // prepares for final step, combine all expressinos together and invoke: // var q4 = Queryable.SelectMany(db.A, a => q3.Where(x => xa == a).Select(x => xb), (a, b) => new { a, b }); var paramTuple3 = Expression.Parameter(tuple2); var paramTuple4 = Expression.Parameter(tuple2); var paramOuter3 = Expression.Parameter(typeof (TOuter)); var selectManyResult2 = selectMany .MakeGenericMethod( typeof(TOuter), typeof(TInner), typeof(TResult) ) .Invoke( null, new object[] { outer, Expression.Lambda( Expression.Convert( Expression.Call( null, select.MakeGenericMethod(tuple2, typeof(TInner)), new Expression[] { Expression.Call( null, where.MakeGenericMethod(tuple2), new Expression[] { selectMany1Result, Expression.Lambda( Expression.Equal( paramOuter3, Expression.MakeMemberAccess(paramTuple4, paramTuple4.Type.GetProperty("Item1")) ), paramTuple4 ) } ), Expression.Lambda( Expression.MakeMemberAccess(paramTuple3, paramTuple3.Type.GetProperty("Item2")), paramTuple3 ) } ), typeof(IEnumerable ) ), paramOuter3 ), resultSelector } ); return (IQueryable)selectManyResult2; }
用法
再次使用:
db.A.LeftOuterJoin2(db.B, a => a.Id, b => b.IdA, (a, b) => new { a, b, hello = "Hello World!" });
看看这个你可以想一下这个sql查询是什么? 这可能是巨大的。 你猜怎么着? 它很小:
SELECT 1 AS [C1], [Extent1].[Id] AS [Id], [Extent1].[Text] AS [Text], [Join1].[Id1] AS [Id1], [Join1].[IdA] AS [IdA], [Join1].[Text2] AS [Text2], N'Hello World!' AS [C2] FROM [A] AS [Extent1] INNER JOIN (SELECT [Extent2].[Id] AS [Id2], [Extent2].[Text] AS [Text], [Extent3].[Id] AS [Id1], [Extent3].[IdA] AS [IdA], [Extent3].[Text2] AS [Text2] FROM [A] AS [Extent2] LEFT OUTER JOIN [B] AS [Extent3] ON [Extent2].[Id] = [Extent3].[IdA] ) AS [Join1] ON [Extent1].[Id] = [Join1].[Id2]
希望能帮助到你。
接受的答案是解释左外连接背后复杂性的一个很好的开始。
我发现了三个相当严重的问题,特别是在采用这种扩展方法并在更复杂的查询中使用它时(将多个左外连接与正常连接链接,然后汇总/ max / count / …)在将所选答案复制到您的生产环境,请继续阅读。
考虑链接的SOpost中的原始示例,它代表LINQ中完成的任何左外连接:
var leftJoin = p.Person.Where(n => n.FirstName.Contains("a")) .GroupJoin(p.PersonInfo, n => n.PersonId, m => m.PersonId, (n, ms) => new { n, ms = ms }) .SelectMany(z => z.ms.DefaultIfEmpty(), (n, m) => new { n = n, m ));
使用表达式visitor和KeyValuePairHolder来避免使用元组,我在下面选择的答案的更新版本修复了三个问题,更短,并产生更短的SQL:
internal class QueryReflectionMethods { internal static System.Reflection.MethodInfo Enumerable_Select = typeof(Enumerable).GetMethods().First(x => x.Name == "Select" && x.GetParameters().Length == 2); internal static System.Reflection.MethodInfo Enumerable_DefaultIfEmpty = typeof(Enumerable).GetMethods().First(x => x.Name == "DefaultIfEmpty" && x.GetParameters().Length == 1); internal static System.Reflection.MethodInfo Queryable_SelectMany = typeof(Queryable).GetMethods().Where(x => x.Name == "SelectMany" && x.GetParameters().Length == 3).OrderBy(x => x.ToString().Length).First(); internal static System.Reflection.MethodInfo Queryable_Where = typeof(Queryable).GetMethods().First(x => x.Name == "Where" && x.GetParameters().Length == 2); internal static System.Reflection.MethodInfo Queryable_GroupJoin = typeof(Queryable).GetMethods().First(x => x.Name == "GroupJoin" && x.GetParameters().Length == 5); internal static System.Reflection.MethodInfo Queryable_Join = typeof(Queryable).GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).First(c => c.Name == "Join"); internal static System.Reflection.MethodInfo Queryable_Select = typeof(Queryable).GetMethods().First(x => x.Name == "Select" && x.GetParameters().Length == 2); public static IQueryable CreateLeftOuterJoin( IQueryable outer, IQueryable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) { var keyValuePairHolderWithGroup = typeof(KeyValuePairHolder<,>).MakeGenericType( typeof(TOuter), typeof(IEnumerable<>).MakeGenericType( typeof(TInner) ) ); var paramOuter = Expression.Parameter(typeof(TOuter)); var paramInner = Expression.Parameter(typeof(IEnumerable )); var groupJoin = Queryable_GroupJoin.MakeGenericMethod(typeof(TOuter), typeof(TInner), typeof(TKey), keyValuePairHolderWithGroup) .Invoke( "ThisArgumentIsIgnoredForStaticMethods", new object[]{ outer, inner, outerKeySelector, innerKeySelector, Expression.Lambda( Expression.MemberInit( Expression.New(keyValuePairHolderWithGroup), Expression.Bind( keyValuePairHolderWithGroup.GetMember("Item1").Single(), paramOuter ), Expression.Bind( keyValuePairHolderWithGroup.GetMember("Item2").Single(), paramInner ) ), paramOuter, paramInner ) } ); var paramGroup = Expression.Parameter(keyValuePairHolderWithGroup); Expression collectionSelector = Expression.Lambda( Expression.Call( null, Enumerable_DefaultIfEmpty.MakeGenericMethod(typeof(TInner)), Expression.MakeMemberAccess(paramGroup, keyValuePairHolderWithGroup.GetProperty("Item2"))) , paramGroup ); Expression newResultSelector = new ResultSelectorRewriter(resultSelector).CombinedExpression; var selectMany1Result = Queryable_SelectMany.MakeGenericMethod(keyValuePairHolderWithGroup, typeof(TInner), typeof(TResult)) .Invoke( "ThisArgumentIsIgnoredForStaticMethods", new object[]{ groupJoin, collectionSelector, newResultSelector } ); return (IQueryable)selectMany1Result; } }
如前面的答案中所述,当您希望将IQueryable转换为SQL时,您需要使用Expression而不是Func,因此您必须使用Expression Tree路由。
但是,这里有一种方法可以实现相同的结果,而无需自己构建表达式树。 诀窍是,您需要引用LinqKit (可通过NuGet获得)并在查询上调用AsExpandable() 。 这将负责构建底层表达式树(请参阅此处 )。
下面的示例使用GroupJoin和SelectMany以及DefaultIfEmpty()方法:
码
public static IQueryable LeftOuterJoin( this IQueryable outer, IQueryable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) { return outer .AsExpandable()// Tell LinqKit to convert everything into an expression tree. .GroupJoin( inner, outerKeySelector, innerKeySelector, (outerItem, innerItems) => new { outerItem, innerItems }) .SelectMany( joinResult => joinResult.innerItems.DefaultIfEmpty(), (joinResult, innerItem) => resultSelector.Invoke(joinResult.outerItem, innerItem)); }
样本数据
假设我们有以下EF实体, 用户和地址变量是对底层DbSet的访问:
public class User { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } public class UserAddress { public int UserId { get; set; } public string LastName { get; set; } public string Street { get; set; } } IQueryable users; IQueryable addresses;
用法1
让我们按用户ID加入:
var result = users.LeftOuterJoin( addresses, user => user.Id, address => address.UserId, (user, address) => new { user.Id, address.Street });
这转换为(使用LinqPad):
SELECT [Extent1].[Id] AS [Id], [Extent2].[Street] AS [Street] FROM [dbo].[Users] AS [Extent1] LEFT OUTER JOIN [dbo].[UserAddresses] AS [Extent2] ON [Extent1].[Id] = [Extent2].[UserId]
用法2
现在让我们使用匿名类型作为键来连接多个属性:
var result = users.LeftOuterJoin( addresses, user => new { user.Id, user.LastName }, address => new { Id = address.UserId, address.LastName }, (user, address) => new { user.Id, address.Street });
请注意,匿名类型属性必须具有相同的名称,否则您将收到语法错误。
这就是为什么我们有Id = address.UserId而不仅仅是address.UserId 。
这将被翻译为:
SELECT [Extent1].[Id] AS [Id], [Extent2].[Street] AS [Street] FROM [dbo].[Users] AS [Extent1] LEFT OUTER JOIN [dbo].[UserAddresses] AS [Extent2] ON ([Extent1].[Id] = [Extent2].[UserId]) AND ([Extent1].[LastName] = [Extent2].[LastName])
这是我去年想要简化.GroupJoin时创建的.LeftJoin扩展方法。 我好运。 我包含了XML注释,因此您可以获得完整的智能感知。 IEqualityComparer也有一个重载。 希望对你有帮助。
我的全套Join Extensions就在这里: https : //github.com/jolsa/Extensions/blob/master/ExtensionLib/JoinExtensions.cs
// JoinExtensions: Created 07/12/2014 - Johnny Olsa using System.Linq; namespace System.Collections.Generic { /// /// Join Extensions that .NET should have provided? /// public static class JoinExtensions { /// /// Correlates the elements of two sequences based on matching keys. A specified /// System.Collections.Generic.IEqualityComparer<T> is used to compare keys. /// /// The type of the elements of the first sequence. /// The type of the elements of the second sequence. /// The type of the keys returned by the key selector functions. /// The type of the result elements. /// The first sequence to join. /// The sequence to join to the first sequence. /// A function to extract the join key from each element of the first sequence. /// A function to extract the join key from each element of the second sequence. /// A function to create a result element from two combined elements. /// A System.Collections.Generic.IEqualityComparer<T> to hash and compare keys. /// /// An System.Collections.Generic.IEnumerable<T> that has elements of type TResult /// that are obtained by performing an left outer join on two sequences. /// /// /// Example: /// /// class TestClass /// { /// static int Main() /// { /// var strings1 = new string[] { "1", "2", "3", "4", "a" }; /// var strings2 = new string[] { "1", "2", "3", "16", "A" }; /// /// var lj = strings1.LeftJoin( /// strings2, /// a => a, /// b => b, /// (a, b) => (a ?? "null") + "-" + (b ?? "null"), /// StringComparer.OrdinalIgnoreCase) /// .ToList(); /// } /// } ///
/// public static IEnumerable LeftJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer comparer) { return outer.GroupJoin( inner, outerKeySelector, innerKeySelector, (o, ei) => ei .Select(i => resultSelector(o, i)) .DefaultIfEmpty(resultSelector(o, default(TInner))), comparer) .SelectMany(oi => oi); } /// /// Correlates the elements of two sequences based on matching keys. The default /// equality comparer is used to compare keys. /// /// The type of the elements of the first sequence. /// The type of the elements of the second sequence. /// The type of the keys returned by the key selector functions. /// The type of the result elements. /// The first sequence to join. /// The sequence to join to the first sequence. /// A function to extract the join key from each element of the first sequence. /// A function to extract the join key from each element of the second sequence. /// A function to create a result element from two combined elements. /// /// An System.Collections.Generic.IEnumerable<T> that has elements of type TResult /// that are obtained by performing an left outer join on two sequences. /// /// /// Example: /// /// class TestClass /// { /// static int Main() /// { /// var strings1 = new string[] { "1", "2", "3", "4", "a" }; /// var strings2 = new string[] { "1", "2", "3", "16", "A" }; /// /// var lj = strings1.LeftJoin( /// strings2, /// a => a, /// b => b, /// (a, b) => (a ?? "null") + "-" + (b ?? "null")) /// .ToList(); /// } /// } ///
/// public static IEnumerable LeftJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) { return outer.LeftJoin(inner, outerKeySelector, innerKeySelector, resultSelector, default(IEqualityComparer)); } } }
我之前回答的更新。 当我发布它时,我没有注意到问题在于转换为SQL。 此代码适用于本地项目,因此将首先拉取对象然后连接,而不是在服务器上执行外部联接。 但是要使用我之前发布的Join扩展来处理空值,这是一个例子:
public class Person { public int Id { get; set; } public string Name { get; set; } } public class EmailAddress { public int Id { get; set; } public Email Email { get; set; } } public class Email { public string Name { get; set; } public string Address { get; set; } } public static void Main() { var people = new [] { new Person() { Id = 1, Name = "John" }, new Person() { Id = 2, Name = "Paul" }, new Person() { Id = 3, Name = "George" }, new Person() { Id = 4, Name = "Ringo" } }; var addresses = new[] { new EmailAddress() { Id = 2, Email = new Email() { Name = "Paul", Address = "Paul@beatles.com" } }, new EmailAddress() { Id = 3, Email = new Email() { Name = "George", Address = "George@beatles.com" } }, new EmailAddress() { Id = 4, Email = new Email() { Name = "Ringo", Address = "Ringo@beatles.com" } } }; var joinedById = people.LeftJoin(addresses, p => p.Id, a => a.Id, (p, a) => new { p.Id, p.Name, a?.Email.Address }).ToList(); Console.WriteLine("rnJoined by Id:rn"); joinedById.ForEach(j => Console.WriteLine($"{j.Id}-{j.Name}: {j.Address ?? ""}")); var joinedByName = people.LeftJoin(addresses, p => p.Name, a => a?.Email.Name, (p, a) => new { p.Id, p.Name, a?.Email.Address }, StringComparer.OrdinalIgnoreCase).ToList(); Console.WriteLine("rnJoined by Name:rn"); joinedByName.ForEach(j => Console.WriteLine($"{j.Id}-{j.Name}: {j.Address ?? " "}")); }
@Licentia,这是我想出来解决你的问题。 我创建了类似于你向我展示的DynamicJoin
和DynamicLeftJoin
扩展方法,但我处理输出的方式不同,因为字符串解析容易受到许多问题的影响。 这不会加入匿名类型,但您可以调整它来执行此操作。 它也没有IComparable
重载,但可以很容易地添加。 属性名称必须与类型相同。 这与我上面的扩展方法一起使用(即没有它们就无法工作)。 我希望它有所帮助!
上述就是C#学习教程:使用LINQ的IQueryable左外连接的扩展方法分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
public class Person { public int Id { get; set; } public string Name { get; set; } } public class EmailAddress { public int PersonId { get; set; } public Email Email { get; set; } } public class Email { public string Name { get; set; } public string Address { get; set; } } public static void Main() { var people = new[] { new Person() { Id = 1, Name = "John" }, new Person() { Id = 2, Name = "Paul" }, new Person() { Id = 3, Name = "George" }, new Person() { Id = 4, Name = "Ringo" } }; var addresses = new[] { new EmailAddress() { PersonId = 2, Email = new Email() { Name = "Paul", Address = "Paul@beatles.com" } }, new EmailAddress() { PersonId = 3, Email = new Email() { Name = "George", Address = "George@beatles.com" } }, new EmailAddress() { PersonId = 4, Email = new Email() { Name = "Ringo" } } }; Console.WriteLine("rnInner Join:rn"); var innerJoin = people.DynamicJoin(addresses, "Id", "PersonId", "outer.Id", "outer.Name", "inner.Email").ToList(); innerJoin.ForEach(j => Console.WriteLine($"{j.Id}-{j.Name}: {j?.Email?.Address ?? ""}")); Console.WriteLine("rnOuter Join:rn"); var leftJoin = people.DynamicLeftJoin(addresses, "Id", "PersonId", "outer.Id", "outer.Name", "inner.Email").ToList(); leftJoin.ForEach(j => Console.WriteLine($"{j.Id}-{j.Name}: {j?.Email?.Address ?? " "}")); } public static class DynamicJoinExtensions { private const string OuterPrefix = "outer."; private const string InnerPrefix = "inner."; private class Processor { private readonly Type _typeOuter = typeof(TOuter); private readonly Type _typeInner = typeof(TInner); private readonly PropertyInfo _keyOuter; private readonly PropertyInfo _keyInner; private readonly List _outputFields; private readonly Dictionary _resultProperties; public Processor(string outerKey, string innerKey, IEnumerable outputFields) { _outputFields = outputFields.ToList(); // Check for properties with the same name string badProps = string.Join(", ", _outputFields.Select(f => new { property = f, name = GetName(f) }) .GroupBy(f => f.name, StringComparer.OrdinalIgnoreCase) .Where(g => g.Count() > 1) .SelectMany(g => g.OrderBy(f => f.name, StringComparer.OrdinalIgnoreCase).Select(f => f.property))); if (!string.IsNullOrEmpty(badProps)) throw new ArgumentException($"One or more {nameof(outputFields)} are duplicated: {badProps}"); _keyOuter = _typeOuter.GetProperty(outerKey); _keyInner = _typeInner.GetProperty(innerKey); // Check for valid keys if (_keyOuter == null || _keyInner == null) throw new ArgumentException($"One or both of the specified keys is not a valid property"); // Check type compatibility if (_keyOuter.PropertyType != _keyInner.PropertyType) throw new ArgumentException($"Keys must be the same type. ({nameof(outerKey)} type: {_keyOuter.PropertyType.Name}, {nameof(innerKey)} type: {_keyInner.PropertyType.Name})"); Func>> getResultProperties = (prefix, type) => _outputFields.Where(f => f.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) .Select(f => new KeyValuePair(f, type.GetProperty(f.Substring(prefix.Length)))); // Combine inner/outer outputFields with PropertyInfo into a dictionary _resultProperties = getResultProperties(OuterPrefix, _typeOuter).Concat(getResultProperties(InnerPrefix, _typeInner)) .ToDictionary(k => k.Key, v => v.Value, StringComparer.OrdinalIgnoreCase); // Check for properties that aren't found badProps = string.Join(", ", _resultProperties.Where(kv => kv.Value == null).Select(kv => kv.Key)); if (!string.IsNullOrEmpty(badProps)) throw new ArgumentException($"One or more {nameof(outputFields)} are not valid: {badProps}"); // Check for properties that aren't the right format badProps = string.Join(", ", _outputFields.Where(f => !_resultProperties.ContainsKey(f))); if (!string.IsNullOrEmpty(badProps)) throw new ArgumentException($"One or more {nameof(outputFields)} are not valid: {badProps}"); } // Inner Join public IEnumerable Join(IEnumerable outer, IEnumerable inner) => outer.Join(inner, o => GetOuterKeyValue(o), i => GetInnerKeyValue(i), (o, i) => CreateItem(o, i)); // Left Outer Join public IEnumerable LeftJoin(IEnumerable outer, IEnumerable inner) => outer.LeftJoin(inner, o => GetOuterKeyValue(o), i => GetInnerKeyValue(i), (o, i) => CreateItem(o, i)); private static string GetName(string fieldId) => fieldId.Substring(fieldId.IndexOf('.') + 1); private object GetOuterKeyValue(TOuter obj) => _keyOuter.GetValue(obj); private object GetInnerKeyValue(TInner obj) => _keyInner.GetValue(obj); private object GetResultProperyValue(string key, object obj) => _resultProperties[key].GetValue(obj); private dynamic CreateItem(TOuter o, TInner i) { var obj = new ExpandoObject(); var dict = (IDictionary)obj; _outputFields.ForEach(f => { var source = f.StartsWith(OuterPrefix, StringComparison.OrdinalIgnoreCase) ? (object)o : i; dict.Add(GetName(f), source == null ? null : GetResultProperyValue(f, source)); }); return obj; } } public static IEnumerable DynamicJoin(this IEnumerable outer, IEnumerable inner, string outerKey, string innerKey, params string[] outputFields) => new Processor(outerKey, innerKey, outputFields).Join(outer, inner); public static IEnumerable DynamicLeftJoin(this IEnumerable outer, IEnumerable inner, string outerKey, string innerKey, params string[] outputFields) => new Processor(outerKey, innerKey, outputFields).LeftJoin(outer, inner); }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1042880.html