Mercurial > pub > bltoolkit
comparison Source/Data/Linq/Builder/JoinBuilder.cs @ 0:f990fcb411a9
Копия текущей версии из github
| author | cin |
|---|---|
| date | Thu, 27 Mar 2014 21:46:09 +0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:f990fcb411a9 |
|---|---|
| 1 using System; | |
| 2 using System.Collections.Generic; | |
| 3 using System.Linq; | |
| 4 using System.Linq.Expressions; | |
| 5 | |
| 6 namespace BLToolkit.Data.Linq.Builder | |
| 7 { | |
| 8 using BLToolkit.Linq; | |
| 9 using Data.Sql; | |
| 10 | |
| 11 class JoinBuilder : MethodCallBuilder | |
| 12 { | |
| 13 protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
| 14 { | |
| 15 if (!methodCall.IsQueryable("Join", "GroupJoin") || methodCall.Arguments.Count != 5) | |
| 16 return false; | |
| 17 | |
| 18 var body = ((LambdaExpression)methodCall.Arguments[2].Unwrap()).Body.Unwrap(); | |
| 19 | |
| 20 if (body.NodeType == ExpressionType .MemberInit) | |
| 21 { | |
| 22 var mi = (MemberInitExpression)body; | |
| 23 bool throwExpr; | |
| 24 | |
| 25 if (mi.NewExpression.Arguments.Count > 0 || mi.Bindings.Count == 0) | |
| 26 throwExpr = true; | |
| 27 else | |
| 28 throwExpr = mi.Bindings.Any(b => b.BindingType != MemberBindingType.Assignment); | |
| 29 | |
| 30 if (throwExpr) | |
| 31 throw new NotSupportedException(string.Format("Explicit construction of entity type '{0}' in join is not allowed.", body.Type)); | |
| 32 } | |
| 33 | |
| 34 return true; | |
| 35 } | |
| 36 | |
| 37 protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
| 38 { | |
| 39 var isGroup = methodCall.Method.Name == "GroupJoin"; | |
| 40 var outerContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[0], buildInfo.SqlQuery)); | |
| 41 var innerContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[1], new SqlQuery())); | |
| 42 var countContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[1], new SqlQuery())); | |
| 43 | |
| 44 var context = new SubQueryContext(outerContext); | |
| 45 innerContext = isGroup ? new GroupJoinSubQueryContext(innerContext, methodCall) : new SubQueryContext(innerContext); | |
| 46 countContext = new SubQueryContext(countContext); | |
| 47 | |
| 48 var join = isGroup ? innerContext.SqlQuery.WeakLeftJoin() : innerContext.SqlQuery.InnerJoin(); | |
| 49 var sql = context.SqlQuery; | |
| 50 | |
| 51 sql.From.Tables[0].Joins.Add(join.JoinedTable); | |
| 52 | |
| 53 var selector = (LambdaExpression)methodCall.Arguments[4].Unwrap(); | |
| 54 | |
| 55 context.SetAlias(selector.Parameters[0].Name); | |
| 56 innerContext.SetAlias(selector.Parameters[1].Name); | |
| 57 | |
| 58 var outerKeyLambda = ((LambdaExpression)methodCall.Arguments[2].Unwrap()); | |
| 59 var innerKeyLambda = ((LambdaExpression)methodCall.Arguments[3].Unwrap()); | |
| 60 | |
| 61 var outerKeySelector = outerKeyLambda.Body.Unwrap(); | |
| 62 var innerKeySelector = innerKeyLambda.Body.Unwrap(); | |
| 63 | |
| 64 var outerParent = context. Parent; | |
| 65 var innerParent = innerContext.Parent; | |
| 66 var countParent = countContext.Parent; | |
| 67 | |
| 68 var outerKeyContext = new ExpressionContext(buildInfo.Parent, context, outerKeyLambda); | |
| 69 var innerKeyContext = new InnerKeyContext (buildInfo.Parent, innerContext, innerKeyLambda); | |
| 70 var countKeyContext = new ExpressionContext(buildInfo.Parent, countContext, innerKeyLambda); | |
| 71 | |
| 72 // Process counter. | |
| 73 // | |
| 74 var counterSql = ((SubQueryContext)countContext).SqlQuery; | |
| 75 | |
| 76 // Make join and where for the counter. | |
| 77 // | |
| 78 if (outerKeySelector.NodeType == ExpressionType.New) | |
| 79 { | |
| 80 var new1 = (NewExpression)outerKeySelector; | |
| 81 var new2 = (NewExpression)innerKeySelector; | |
| 82 | |
| 83 for (var i = 0; i < new1.Arguments.Count; i++) | |
| 84 { | |
| 85 var arg1 = new1.Arguments[i]; | |
| 86 var arg2 = new2.Arguments[i]; | |
| 87 | |
| 88 BuildJoin(builder, join, outerKeyContext, arg1, innerKeyContext, arg2, countKeyContext, counterSql); | |
| 89 } | |
| 90 } | |
| 91 else if (outerKeySelector.NodeType == ExpressionType.MemberInit) | |
| 92 { | |
| 93 var mi1 = (MemberInitExpression)outerKeySelector; | |
| 94 var mi2 = (MemberInitExpression)innerKeySelector; | |
| 95 | |
| 96 for (var i = 0; i < mi1.Bindings.Count; i++) | |
| 97 { | |
| 98 if (mi1.Bindings[i].Member != mi2.Bindings[i].Member) | |
| 99 throw new LinqException(string.Format("List of member inits does not match for entity type '{0}'.", outerKeySelector.Type)); | |
| 100 | |
| 101 var arg1 = ((MemberAssignment)mi1.Bindings[i]).Expression; | |
| 102 var arg2 = ((MemberAssignment)mi2.Bindings[i]).Expression; | |
| 103 | |
| 104 BuildJoin(builder, join, outerKeyContext, arg1, innerKeyContext, arg2, countKeyContext, counterSql); | |
| 105 } | |
| 106 } | |
| 107 else | |
| 108 { | |
| 109 BuildJoin(builder, join, outerKeyContext, outerKeySelector, innerKeyContext, innerKeySelector, countKeyContext, counterSql); | |
| 110 } | |
| 111 | |
| 112 builder.ReplaceParent(outerKeyContext, outerParent); | |
| 113 builder.ReplaceParent(innerKeyContext, innerParent); | |
| 114 builder.ReplaceParent(countKeyContext, countParent); | |
| 115 | |
| 116 if (isGroup) | |
| 117 { | |
| 118 counterSql.ParentSql = sql; | |
| 119 counterSql.Select.Columns.Clear(); | |
| 120 | |
| 121 var inner = (GroupJoinSubQueryContext)innerContext; | |
| 122 | |
| 123 inner.Join = join.JoinedTable; | |
| 124 inner.CounterSql = counterSql; | |
| 125 return new GroupJoinContext( | |
| 126 buildInfo.Parent, selector, context, inner, methodCall.Arguments[1], outerKeyLambda, innerKeyLambda); | |
| 127 } | |
| 128 | |
| 129 return new JoinContext(buildInfo.Parent, selector, context, innerContext) | |
| 130 #if DEBUG | |
| 131 { | |
| 132 MethodCall = methodCall | |
| 133 } | |
| 134 #endif | |
| 135 ; | |
| 136 } | |
| 137 | |
| 138 protected override SequenceConvertInfo Convert( | |
| 139 ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo, ParameterExpression param) | |
| 140 { | |
| 141 return null; | |
| 142 } | |
| 143 | |
| 144 static void BuildJoin( | |
| 145 ExpressionBuilder builder, | |
| 146 SqlQuery.FromClause.Join join, | |
| 147 IBuildContext outerKeyContext, Expression outerKeySelector, | |
| 148 IBuildContext innerKeyContext, Expression innerKeySelector, | |
| 149 IBuildContext countKeyContext, SqlQuery countSql) | |
| 150 { | |
| 151 var predicate = builder.ConvertObjectComparison( | |
| 152 ExpressionType.Equal, | |
| 153 outerKeyContext, outerKeySelector, | |
| 154 innerKeyContext, innerKeySelector); | |
| 155 | |
| 156 if (predicate != null) | |
| 157 join.JoinedTable.Condition.Conditions.Add(new SqlQuery.Condition(false, predicate)); | |
| 158 else | |
| 159 join | |
| 160 .Expr(builder.ConvertToSql(outerKeyContext, outerKeySelector, false)).Equal | |
| 161 .Expr(builder.ConvertToSql(innerKeyContext, innerKeySelector, false)); | |
| 162 | |
| 163 predicate = builder.ConvertObjectComparison( | |
| 164 ExpressionType.Equal, | |
| 165 outerKeyContext, outerKeySelector, | |
| 166 countKeyContext, innerKeySelector); | |
| 167 | |
| 168 if (predicate != null) | |
| 169 countSql.Where.SearchCondition.Conditions.Add(new SqlQuery.Condition(false, predicate)); | |
| 170 else | |
| 171 countSql.Where | |
| 172 .Expr(builder.ConvertToSql(outerKeyContext, outerKeySelector, false)).Equal | |
| 173 .Expr(builder.ConvertToSql(countKeyContext, innerKeySelector, false)); | |
| 174 } | |
| 175 | |
| 176 class InnerKeyContext : ExpressionContext | |
| 177 { | |
| 178 public InnerKeyContext(IBuildContext parent, IBuildContext sequence, LambdaExpression lambda) | |
| 179 : base(parent, sequence, lambda) | |
| 180 { | |
| 181 } | |
| 182 | |
| 183 public override SqlInfo[] ConvertToSql(Expression expression, int level, ConvertFlags flags) | |
| 184 { | |
| 185 return base | |
| 186 .ConvertToSql(expression, level, flags) | |
| 187 .Select(idx => | |
| 188 { | |
| 189 var n = SqlQuery.Select.Add(idx.Sql); | |
| 190 | |
| 191 return new SqlInfo(idx.Members) | |
| 192 { | |
| 193 Sql = SqlQuery.Select.Columns[n], | |
| 194 Index = n | |
| 195 }; | |
| 196 }) | |
| 197 .ToArray(); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 internal class JoinContext : SelectContext | |
| 202 { | |
| 203 public JoinContext(IBuildContext parent, LambdaExpression lambda, IBuildContext outerContext, IBuildContext innerContext) | |
| 204 : base(parent, lambda, outerContext, innerContext) | |
| 205 { | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 internal class GroupJoinContext : JoinContext | |
| 210 { | |
| 211 public GroupJoinContext( | |
| 212 IBuildContext parent, | |
| 213 LambdaExpression lambda, | |
| 214 IBuildContext outerContext, | |
| 215 GroupJoinSubQueryContext innerContext, | |
| 216 Expression innerExpression, | |
| 217 LambdaExpression outerKeyLambda, | |
| 218 LambdaExpression innerKeyLambda) | |
| 219 : base(parent, lambda, outerContext, innerContext) | |
| 220 { | |
| 221 _innerExpression = innerExpression; | |
| 222 _outerKeyLambda = outerKeyLambda; | |
| 223 _innerKeyLambda = innerKeyLambda; | |
| 224 | |
| 225 innerContext.GroupJoin = this; | |
| 226 } | |
| 227 | |
| 228 readonly Expression _innerExpression; | |
| 229 readonly LambdaExpression _outerKeyLambda; | |
| 230 readonly LambdaExpression _innerKeyLambda; | |
| 231 private Expression _groupExpression; | |
| 232 | |
| 233 interface IGroupJoinHelper | |
| 234 { | |
| 235 Expression GetGroupJoin(GroupJoinContext context); | |
| 236 } | |
| 237 | |
| 238 class GroupJoinHelper<TKey,TElement> : IGroupJoinHelper | |
| 239 { | |
| 240 public Expression GetGroupJoin(GroupJoinContext context) | |
| 241 { | |
| 242 // Convert outer condition. | |
| 243 // | |
| 244 var outerParam = Expression.Parameter(context._outerKeyLambda.Body.Type, "o"); | |
| 245 var outerKey = context._outerKeyLambda.Body.Convert( | |
| 246 e => e == context._outerKeyLambda.Parameters[0] ? context.Lambda.Parameters[0] : e); | |
| 247 | |
| 248 outerKey = context.Builder.BuildExpression(context, outerKey); | |
| 249 | |
| 250 // Convert inner condition. | |
| 251 // | |
| 252 var parameters = context.Builder.CurrentSqlParameters | |
| 253 .Select((p,i) => new { p, i }) | |
| 254 .ToDictionary(_ => _.p.Expression, _ => _.i); | |
| 255 var paramArray = Expression.Parameter(typeof(object[]), "ps"); | |
| 256 | |
| 257 var innerKey = context._innerKeyLambda.Body.Convert(e => | |
| 258 { | |
| 259 int idx; | |
| 260 | |
| 261 if (parameters.TryGetValue(e, out idx)) | |
| 262 { | |
| 263 return | |
| 264 Expression.Convert( | |
| 265 Expression.ArrayIndex(paramArray, Expression.Constant(idx)), | |
| 266 e.Type); | |
| 267 } | |
| 268 | |
| 269 return e; | |
| 270 }); | |
| 271 | |
| 272 // Item reader. | |
| 273 // | |
| 274 // ReSharper disable AssignNullToNotNullAttribute | |
| 275 | |
| 276 var expr = Expression.Call( | |
| 277 null, | |
| 278 ReflectionHelper.Expressor<object>.MethodExpressor(_ => Queryable.Where(null, (Expression<Func<TElement,bool>>)null)), | |
| 279 context._innerExpression, | |
| 280 Expression.Lambda<Func<TElement,bool>>( | |
| 281 Expression.Equal(innerKey, outerParam), | |
| 282 new[] { context._innerKeyLambda.Parameters[0] })); | |
| 283 | |
| 284 // ReSharper restore AssignNullToNotNullAttribute | |
| 285 | |
| 286 var lambda = Expression.Lambda<Func<IDataContext,TKey,object[],IQueryable<TElement>>>( | |
| 287 Expression.Convert(expr, typeof(IQueryable<TElement>)), | |
| 288 Expression.Parameter(typeof(IDataContext), "ctx"), | |
| 289 outerParam, | |
| 290 paramArray); | |
| 291 | |
| 292 var itemReader = CompiledQuery.Compile(lambda); | |
| 293 | |
| 294 return Expression.Call( | |
| 295 null, | |
| 296 ReflectionHelper.Expressor<object>.MethodExpressor(_ => GetGrouping(null, null, default(TKey), null)), | |
| 297 new[] | |
| 298 { | |
| 299 ExpressionBuilder.ContextParam, | |
| 300 Expression.Constant(context.Builder.CurrentSqlParameters), | |
| 301 outerKey, | |
| 302 Expression.Constant(itemReader), | |
| 303 }); | |
| 304 } | |
| 305 | |
| 306 static IEnumerable<TElement> GetGrouping( | |
| 307 QueryContext context, | |
| 308 List<ParameterAccessor> parameterAccessor, | |
| 309 TKey key, | |
| 310 Func<IDataContext,TKey,object[],IQueryable<TElement>> itemReader) | |
| 311 { | |
| 312 return new GroupByBuilder.GroupByContext.Grouping<TKey,TElement>(key, context, parameterAccessor, itemReader); | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 public override Expression BuildExpression(Expression expression, int level) | |
| 317 { | |
| 318 if (expression == Lambda.Parameters[1]) | |
| 319 { | |
| 320 if (_groupExpression == null) | |
| 321 { | |
| 322 var gtype = typeof(GroupJoinHelper<,>).MakeGenericType( | |
| 323 _innerKeyLambda.Body.Type, | |
| 324 _innerKeyLambda.Parameters[0].Type); | |
| 325 | |
| 326 var helper = (IGroupJoinHelper)Activator.CreateInstance(gtype); | |
| 327 | |
| 328 _groupExpression = helper.GetGroupJoin(this); | |
| 329 } | |
| 330 | |
| 331 return _groupExpression; | |
| 332 } | |
| 333 | |
| 334 return base.BuildExpression(expression, level); | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 internal class GroupJoinSubQueryContext : SubQueryContext | |
| 339 { | |
| 340 //readonly MethodCallExpression _methodCall; | |
| 341 | |
| 342 public SqlQuery.JoinedTable Join; | |
| 343 public SqlQuery CounterSql; | |
| 344 public GroupJoinContext GroupJoin; | |
| 345 | |
| 346 public GroupJoinSubQueryContext(IBuildContext subQuery, MethodCallExpression methodCall) | |
| 347 : base(subQuery) | |
| 348 { | |
| 349 //_methodCall = methodCall; | |
| 350 } | |
| 351 | |
| 352 public override IBuildContext GetContext(Expression expression, int level, BuildInfo buildInfo) | |
| 353 { | |
| 354 if (expression == null) | |
| 355 return this; | |
| 356 | |
| 357 return base.GetContext(expression, level, buildInfo); | |
| 358 } | |
| 359 | |
| 360 Expression _counterExpression; | |
| 361 SqlInfo[] _counterInfo; | |
| 362 | |
| 363 public override SqlInfo[] ConvertToIndex(Expression expression, int level, ConvertFlags flags) | |
| 364 { | |
| 365 if (expression != null && expression == _counterExpression) | |
| 366 return _counterInfo ?? (_counterInfo = new[] | |
| 367 { | |
| 368 new SqlInfo | |
| 369 { | |
| 370 Query = CounterSql.ParentSql, | |
| 371 Index = CounterSql.ParentSql.Select.Add(CounterSql), | |
| 372 Sql = CounterSql | |
| 373 } | |
| 374 }); | |
| 375 | |
| 376 return base.ConvertToIndex(expression, level, flags); | |
| 377 } | |
| 378 | |
| 379 public override IsExpressionResult IsExpression(Expression expression, int level, RequestFor testFlag) | |
| 380 { | |
| 381 if (testFlag == RequestFor.GroupJoin && expression == null) | |
| 382 return IsExpressionResult.True; | |
| 383 | |
| 384 return base.IsExpression(expression, level, testFlag); | |
| 385 } | |
| 386 | |
| 387 public SqlQuery GetCounter(Expression expr) | |
| 388 { | |
| 389 Join.IsWeak = true; | |
| 390 | |
| 391 _counterExpression = expr; | |
| 392 | |
| 393 return CounterSql; | |
| 394 } | |
| 395 } | |
| 396 } | |
| 397 } |
