| 0 | 1 using System; | 
|  | 2 using System.Collections.Generic; | 
|  | 3 using System.Linq; | 
|  | 4 using System.Linq.Expressions; | 
|  | 5 using System.Reflection; | 
|  | 6 | 
|  | 7 namespace BLToolkit.Data.Linq.Builder | 
|  | 8 { | 
|  | 9 	using BLToolkit.Linq; | 
|  | 10 	using Data.Sql; | 
|  | 11 	using Reflection; | 
|  | 12 | 
|  | 13 	class UpdateBuilder : MethodCallBuilder | 
|  | 14 	{ | 
|  | 15 		#region Update | 
|  | 16 | 
|  | 17 		protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | 
|  | 18 		{ | 
|  | 19 			return methodCall.IsQueryable("Update"); | 
|  | 20 		} | 
|  | 21 | 
|  | 22 		protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | 
|  | 23 		{ | 
|  | 24 			var sequence = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[0])); | 
|  | 25 | 
|  | 26 			switch (methodCall.Arguments.Count) | 
|  | 27 			{ | 
|  | 28 				case 1 : // int Update<T>(this IUpdateable<T> source) | 
|  | 29 					CheckAssociation(sequence); | 
|  | 30 					break; | 
|  | 31 | 
|  | 32 				case 2 : // int Update<T>(this IQueryable<T> source, Expression<Func<T,T>> setter) | 
|  | 33 					{ | 
|  | 34 						CheckAssociation(sequence); | 
|  | 35 | 
|  | 36 						BuildSetter( | 
|  | 37 							builder, | 
|  | 38 							buildInfo, | 
|  | 39 							(LambdaExpression)methodCall.Arguments[1].Unwrap(), | 
|  | 40 							sequence, | 
|  | 41 							sequence.SqlQuery.Update.Items, | 
|  | 42 							sequence); | 
|  | 43 						break; | 
|  | 44 					} | 
|  | 45 | 
|  | 46 				case 3 : | 
|  | 47 					{ | 
|  | 48 						var expr = methodCall.Arguments[1].Unwrap(); | 
|  | 49 | 
|  | 50 						if (expr is LambdaExpression) | 
|  | 51 						{ | 
|  | 52 							// int Update<T>(this IQueryable<T> source, Expression<Func<T,bool>> predicate, Expression<Func<T,T>> setter) | 
|  | 53 							// | 
|  | 54 							sequence = builder.BuildWhere(buildInfo.Parent, sequence, (LambdaExpression)methodCall.Arguments[1].Unwrap(), false); | 
|  | 55 | 
|  | 56 							CheckAssociation(sequence); | 
|  | 57 | 
|  | 58 							BuildSetter( | 
|  | 59 								builder, | 
|  | 60 								buildInfo, | 
|  | 61 								(LambdaExpression)methodCall.Arguments[2].Unwrap(), | 
|  | 62 								sequence, | 
|  | 63 								sequence.SqlQuery.Update.Items, | 
|  | 64 								sequence); | 
|  | 65 						} | 
|  | 66 						else | 
|  | 67 						{ | 
|  | 68 							// static int Update<TSource,TTarget>(this IQueryable<TSource> source, Table<TTarget> target, Expression<Func<TSource,TTarget>> setter) | 
|  | 69 							// | 
|  | 70 							var into = builder.BuildSequence(new BuildInfo(buildInfo, expr, new SqlQuery())); | 
|  | 71 | 
|  | 72 							sequence.ConvertToIndex(null, 0, ConvertFlags.All); | 
|  | 73 							sequence.SqlQuery.ResolveWeakJoins(new List<ISqlTableSource>()); | 
|  | 74 							sequence.SqlQuery.Select.Columns.Clear(); | 
|  | 75 | 
|  | 76 							BuildSetter( | 
|  | 77 								builder, | 
|  | 78 								buildInfo, | 
|  | 79 								(LambdaExpression)methodCall.Arguments[2].Unwrap(), | 
|  | 80 								into, | 
|  | 81 								sequence.SqlQuery.Update.Items, | 
|  | 82 								sequence); | 
|  | 83 | 
|  | 84 							var sql = sequence.SqlQuery; | 
|  | 85 | 
|  | 86 							sql.Select.Columns.Clear(); | 
|  | 87 | 
|  | 88 							foreach (var item in sql.Update.Items) | 
|  | 89 								sql.Select.Columns.Add(new SqlQuery.Column(sql, item.Expression)); | 
|  | 90 | 
|  | 91 							sql.Update.Table = ((TableBuilder.TableContext)into).SqlTable; | 
|  | 92 						} | 
|  | 93 | 
|  | 94 						break; | 
|  | 95 					} | 
|  | 96 			} | 
|  | 97 | 
|  | 98 			sequence.SqlQuery.QueryType = QueryType.Update; | 
|  | 99 | 
|  | 100 			return new UpdateContext(buildInfo.Parent, sequence); | 
|  | 101 		} | 
|  | 102 | 
|  | 103 		static void CheckAssociation(IBuildContext sequence) | 
|  | 104 		{ | 
|  | 105 			var ctx = sequence as SelectContext; | 
|  | 106 | 
|  | 107 			if (ctx != null && ctx.IsScalar) | 
|  | 108 			{ | 
|  | 109 				var res = ctx.IsExpression(null, 0, RequestFor.Association); | 
|  | 110 | 
|  | 111 				if (res.Result && res.Context is TableBuilder.AssociatedTableContext) | 
|  | 112 				{ | 
|  | 113 					var atc = (TableBuilder.AssociatedTableContext)res.Context; | 
|  | 114 					sequence.SqlQuery.Update.Table = atc.SqlTable; | 
|  | 115 				} | 
|  | 116 				else | 
|  | 117 				{ | 
|  | 118 					res = ctx.IsExpression(null, 0, RequestFor.Table); | 
|  | 119 | 
|  | 120 					if (res.Result && res.Context is TableBuilder.TableContext) | 
|  | 121 					{ | 
|  | 122 						var tc = (TableBuilder.TableContext)res.Context; | 
|  | 123 | 
|  | 124 						if (sequence.SqlQuery.From.Tables.Count == 0 || sequence.SqlQuery.From.Tables[0].Source != tc.SqlQuery) | 
|  | 125 							sequence.SqlQuery.Update.Table = tc.SqlTable; | 
|  | 126 					} | 
|  | 127 				} | 
|  | 128 			} | 
|  | 129 		} | 
|  | 130 | 
|  | 131 		protected override SequenceConvertInfo Convert( | 
|  | 132 			ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo, ParameterExpression param) | 
|  | 133 		{ | 
|  | 134 			return null; | 
|  | 135 		} | 
|  | 136 | 
|  | 137 		#endregion | 
|  | 138 | 
|  | 139 		#region Helpers | 
|  | 140 | 
|  | 141 		internal static void BuildSetter( | 
|  | 142 			ExpressionBuilder            builder, | 
|  | 143 			BuildInfo                    buildInfo, | 
|  | 144 			LambdaExpression             setter, | 
|  | 145 			IBuildContext                into, | 
|  | 146 			List<SqlQuery.SetExpression> items, | 
|  | 147 			IBuildContext                sequence) | 
|  | 148 		{ | 
|  | 149 			var path = Expression.Parameter(setter.Body.Type, "p"); | 
|  | 150 			var ctx  = new ExpressionContext(buildInfo.Parent, sequence, setter); | 
|  | 151 | 
|  | 152 			if (setter.Body.NodeType == ExpressionType.MemberInit) | 
|  | 153 			{ | 
|  | 154 				var ex  = (MemberInitExpression)setter.Body; | 
|  | 155 				var p   = sequence.Parent; | 
|  | 156 | 
|  | 157 				BuildSetter(builder, into, items, ctx, ex, path); | 
|  | 158 | 
|  | 159 				builder.ReplaceParent(ctx, p); | 
|  | 160 			} | 
|  | 161 			else | 
|  | 162 			{ | 
|  | 163 				var sqlInfo = ctx.ConvertToSql(setter.Body, 0, ConvertFlags.All); | 
|  | 164 | 
|  | 165 				foreach (var info in sqlInfo) | 
|  | 166 				{ | 
|  | 167 					if (info.Members.Count == 0) | 
|  | 168 						throw new LinqException("Object initializer expected for insert statement."); | 
|  | 169 | 
|  | 170 					if (info.Members.Count != 1) | 
|  | 171 						throw new InvalidOperationException(); | 
|  | 172 | 
|  | 173 					var member = info.Members[0]; | 
|  | 174 					var pe     = Expression.MakeMemberAccess(path, member); | 
|  | 175 					var column = into.ConvertToSql(pe, 1, ConvertFlags.Field); | 
|  | 176 					var expr   = info.Sql; | 
|  | 177 | 
|  | 178 					if (expr is SqlParameter) | 
|  | 179 					{ | 
|  | 180 						var type = member.MemberType == MemberTypes.Field ? | 
|  | 181 							((FieldInfo)   member).FieldType : | 
|  | 182 							((PropertyInfo)member).PropertyType; | 
|  | 183 | 
|  | 184 						if (TypeHelper.IsEnumOrNullableEnum(type)) | 
|  | 185 						{ | 
|  | 186 							var memberAccessor = TypeAccessor.GetAccessor(member.DeclaringType)[member.Name]; | 
|  | 187 							((SqlParameter)expr).SetEnumConverter(memberAccessor, builder.MappingSchema); | 
|  | 188 						} | 
|  | 189 					} | 
|  | 190 | 
|  | 191 					items.Add(new SqlQuery.SetExpression(column[0].Sql, expr)); | 
|  | 192 				} | 
|  | 193 			} | 
|  | 194 		} | 
|  | 195 | 
|  | 196 		static void BuildSetter( | 
|  | 197 			ExpressionBuilder            builder, | 
|  | 198 			IBuildContext                into, | 
|  | 199 			List<SqlQuery.SetExpression> items, | 
|  | 200 			IBuildContext                ctx, | 
|  | 201 			MemberInitExpression         expression, | 
|  | 202 			Expression                   path) | 
|  | 203 		{ | 
|  | 204 			foreach (var binding in expression.Bindings) | 
|  | 205 			{ | 
|  | 206 				var member  = binding.Member; | 
|  | 207 | 
|  | 208 				if (member is MethodInfo) | 
|  | 209 					member = TypeHelper.GetPropertyByMethod((MethodInfo)member); | 
|  | 210 | 
|  | 211 				if (binding is MemberAssignment) | 
|  | 212 				{ | 
|  | 213 					var ma = binding as MemberAssignment; | 
|  | 214 					var pe = Expression.MakeMemberAccess(path, member); | 
|  | 215 | 
|  | 216 					if (ma.Expression is MemberInitExpression && !into.IsExpression(pe, 1, RequestFor.Field).Result) | 
|  | 217 					{ | 
|  | 218 						BuildSetter( | 
|  | 219 							builder, | 
|  | 220 							into, | 
|  | 221 							items, | 
|  | 222 							ctx, | 
|  | 223 							(MemberInitExpression)ma.Expression, Expression.MakeMemberAccess(path, member)); | 
|  | 224 					} | 
|  | 225 					else | 
|  | 226 					{ | 
|  | 227 						var column = into.ConvertToSql(pe, 1, ConvertFlags.Field); | 
|  | 228 						var expr   = builder.ConvertToSqlExpression(ctx, ma.Expression, false); | 
|  | 229 | 
|  | 230 						if (expr is SqlValueBase && TypeHelper.IsEnumOrNullableEnum(ma.Expression.Type)) | 
|  | 231 						{ | 
|  | 232 							var memberAccessor = TypeAccessor.GetAccessor(ma.Member.DeclaringType)[ma.Member.Name]; | 
|  | 233 							((SqlValueBase)expr).SetEnumConverter(memberAccessor, builder.MappingSchema); | 
|  | 234 						} | 
|  | 235 | 
|  | 236 						items.Add(new SqlQuery.SetExpression(column[0].Sql, expr)); | 
|  | 237 					} | 
|  | 238 				} | 
|  | 239 				else | 
|  | 240 					throw new InvalidOperationException(); | 
|  | 241 			} | 
|  | 242 		} | 
|  | 243 | 
|  | 244 		internal static void ParseSet( | 
|  | 245 			ExpressionBuilder            builder, | 
|  | 246 			BuildInfo                    buildInfo, | 
|  | 247 			LambdaExpression             extract, | 
|  | 248 			LambdaExpression             update, | 
|  | 249 			IBuildContext                select, | 
|  | 250 			SqlTable                     table, | 
|  | 251 			List<SqlQuery.SetExpression> items) | 
|  | 252 		{ | 
|  | 253 			var ext = extract.Body; | 
|  | 254 | 
|  | 255 			while (ext.NodeType == ExpressionType.Convert || ext.NodeType == ExpressionType.ConvertChecked) | 
|  | 256 				ext = ((UnaryExpression)ext).Operand; | 
|  | 257 | 
|  | 258 			if (ext.NodeType != ExpressionType.MemberAccess || ext.GetRootObject() != extract.Parameters[0]) | 
|  | 259 				throw new LinqException("Member expression expected for the 'Set' statement."); | 
|  | 260 | 
|  | 261 			var body   = (MemberExpression)ext; | 
|  | 262 			var member = body.Member; | 
|  | 263 | 
|  | 264 			if (member is MethodInfo) | 
|  | 265 				member = TypeHelper.GetPropertyByMethod((MethodInfo)member); | 
|  | 266 | 
|  | 267 			var members = body.GetMembers(); | 
|  | 268 			var name    = members | 
|  | 269 				.Skip(1) | 
|  | 270 				.Select(ex => | 
|  | 271 				{ | 
|  | 272 					var me = ex as MemberExpression; | 
|  | 273 | 
|  | 274 					if (me == null) | 
|  | 275 						return null; | 
|  | 276 | 
|  | 277 					var m = me.Member; | 
|  | 278 | 
|  | 279 					if (m is MethodInfo) | 
|  | 280 						m = TypeHelper.GetPropertyByMethod((MethodInfo)m); | 
|  | 281 | 
|  | 282 					return m; | 
|  | 283 				}) | 
|  | 284 				.Where(m => m != null && !TypeHelper.IsNullableValueMember(m)) | 
|  | 285 				.Select(m => m.Name) | 
|  | 286 				.Aggregate((s1,s2) => s1 + "." + s2); | 
|  | 287 | 
|  | 288 			if (table != null && !table.Fields.ContainsKey(name)) | 
|  | 289 				throw new LinqException("Member '{0}.{1}' is not a table column.", member.DeclaringType.Name, name); | 
|  | 290 | 
|  | 291 			var column = table != null ? | 
|  | 292 				table.Fields[name] : | 
|  | 293 				select.ConvertToSql( | 
|  | 294 					body, 1, ConvertFlags.Field)[0].Sql; | 
|  | 295 					//Expression.MakeMemberAccess(Expression.Parameter(member.DeclaringType, "p"), member), 1, ConvertFlags.Field)[0].Sql; | 
|  | 296 			var sp     = select.Parent; | 
|  | 297 			var ctx    = new ExpressionContext(buildInfo.Parent, select, update); | 
|  | 298 			var expr   = builder.ConvertToSqlExpression(ctx, update.Body, false); | 
|  | 299 | 
|  | 300 			builder.ReplaceParent(ctx, sp); | 
|  | 301 | 
|  | 302 			if (expr is SqlValueBase && TypeHelper.IsEnumOrNullableEnum(update.Body.Type)) | 
|  | 303 			{ | 
|  | 304 				var memberAccessor = TypeAccessor.GetAccessor(body.Member.DeclaringType)[body.Member.Name]; | 
|  | 305 				((SqlValueBase)expr).SetEnumConverter(memberAccessor, builder.MappingSchema); | 
|  | 306 			} | 
|  | 307 | 
|  | 308 			items.Add(new SqlQuery.SetExpression(column, expr)); | 
|  | 309 		} | 
|  | 310 | 
|  | 311 		internal static void ParseSet( | 
|  | 312 			ExpressionBuilder            builder, | 
|  | 313 			BuildInfo                    buildInfo, | 
|  | 314 			LambdaExpression             extract, | 
|  | 315 			Expression                   update, | 
|  | 316 			IBuildContext                select, | 
|  | 317 			List<SqlQuery.SetExpression> items) | 
|  | 318 		{ | 
|  | 319 			var ext = extract.Body; | 
|  | 320 | 
|  | 321 			if (!ExpressionHelper.IsConstant(update.Type) && !builder.AsParameters.Contains(update)) | 
|  | 322 				builder.AsParameters.Add(update); | 
|  | 323 | 
|  | 324 			while (ext.NodeType == ExpressionType.Convert || ext.NodeType == ExpressionType.ConvertChecked) | 
|  | 325 				ext = ((UnaryExpression)ext).Operand; | 
|  | 326 | 
|  | 327 			if (ext.NodeType != ExpressionType.MemberAccess || ext.GetRootObject() != extract.Parameters[0]) | 
|  | 328 				throw new LinqException("Member expression expected for the 'Set' statement."); | 
|  | 329 | 
|  | 330 			var body   = (MemberExpression)ext; | 
|  | 331 			var member = body.Member; | 
|  | 332 | 
|  | 333 			if (member is MethodInfo) | 
|  | 334 				member = TypeHelper.GetPropertyByMethod((MethodInfo)member); | 
|  | 335 | 
|  | 336 			var column = select.ConvertToSql( | 
|  | 337 				body, 1, ConvertFlags.Field); | 
|  | 338 				//Expression.MakeMemberAccess(Expression.Parameter(member.DeclaringType, "p"), member), 1, ConvertFlags.Field); | 
|  | 339 | 
|  | 340 			if (column.Length == 0) | 
|  | 341 				throw new LinqException("Member '{0}.{1}' is not a table column.", member.DeclaringType.Name, member.Name); | 
|  | 342 | 
|  | 343 			var expr   = builder.ConvertToSql(select, update, false, false); | 
|  | 344 | 
|  | 345 			if (expr is SqlValueBase && TypeHelper.IsEnumOrNullableEnum(update.Type)) | 
|  | 346 			{ | 
|  | 347 				var memberAccessor = TypeAccessor.GetAccessor(body.Member.DeclaringType)[body.Member.Name]; | 
|  | 348 				((SqlValueBase)expr).SetEnumConverter(memberAccessor, builder.MappingSchema); | 
|  | 349 			} | 
|  | 350 | 
|  | 351 			items.Add(new SqlQuery.SetExpression(column[0].Sql, expr)); | 
|  | 352 		} | 
|  | 353 | 
|  | 354 		#endregion | 
|  | 355 | 
|  | 356 		#region UpdateContext | 
|  | 357 | 
|  | 358 		class UpdateContext : SequenceContextBase | 
|  | 359 		{ | 
|  | 360 			public UpdateContext(IBuildContext parent, IBuildContext sequence) | 
|  | 361 				: base(parent, sequence, null) | 
|  | 362 			{ | 
|  | 363 			} | 
|  | 364 | 
|  | 365 			public override void BuildQuery<T>(Query<T> query, ParameterExpression queryParameter) | 
|  | 366 			{ | 
|  | 367 				query.SetNonQueryQuery(); | 
|  | 368 			} | 
|  | 369 | 
|  | 370 			public override Expression BuildExpression(Expression expression, int level) | 
|  | 371 			{ | 
|  | 372 				throw new InvalidOperationException(); | 
|  | 373 			} | 
|  | 374 | 
|  | 375 			public override SqlInfo[] ConvertToSql(Expression expression, int level, ConvertFlags flags) | 
|  | 376 			{ | 
|  | 377 				throw new InvalidOperationException(); | 
|  | 378 			} | 
|  | 379 | 
|  | 380 			public override SqlInfo[] ConvertToIndex(Expression expression, int level, ConvertFlags flags) | 
|  | 381 			{ | 
|  | 382 				throw new InvalidOperationException(); | 
|  | 383 			} | 
|  | 384 | 
|  | 385 			public override IsExpressionResult IsExpression(Expression expression, int level, RequestFor requestFlag) | 
|  | 386 			{ | 
|  | 387 				throw new InvalidOperationException(); | 
|  | 388 			} | 
|  | 389 | 
|  | 390 			public override IBuildContext GetContext(Expression expression, int level, BuildInfo buildInfo) | 
|  | 391 			{ | 
|  | 392 				throw new InvalidOperationException(); | 
|  | 393 			} | 
|  | 394 		} | 
|  | 395 | 
|  | 396 		#endregion | 
|  | 397 | 
|  | 398 		#region Set | 
|  | 399 | 
|  | 400 		internal class Set : MethodCallBuilder | 
|  | 401 		{ | 
|  | 402 			protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | 
|  | 403 			{ | 
|  | 404 				return methodCall.IsQueryable("Set"); | 
|  | 405 			} | 
|  | 406 | 
|  | 407 			protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | 
|  | 408 			{ | 
|  | 409 				var sequence = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[0])); | 
|  | 410 				var extract  = (LambdaExpression)methodCall.Arguments[1].Unwrap(); | 
|  | 411 				var update   =                   methodCall.Arguments[2].Unwrap(); | 
|  | 412 | 
|  | 413 				if (update.NodeType == ExpressionType.Lambda) | 
|  | 414 					ParseSet( | 
|  | 415 						builder, | 
|  | 416 						buildInfo, | 
|  | 417 						extract, | 
|  | 418 						(LambdaExpression)update, | 
|  | 419 						sequence, | 
|  | 420 						sequence.SqlQuery.Update.Table, | 
|  | 421 						sequence.SqlQuery.Update.Items); | 
|  | 422 				else | 
|  | 423 					ParseSet( | 
|  | 424 						builder, | 
|  | 425 						buildInfo, | 
|  | 426 						extract, | 
|  | 427 						update, | 
|  | 428 						sequence, | 
|  | 429 						sequence.SqlQuery.Update.Items); | 
|  | 430 | 
|  | 431 				return sequence; | 
|  | 432 			} | 
|  | 433 | 
|  | 434 			protected override SequenceConvertInfo Convert( | 
|  | 435 				ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo, ParameterExpression param) | 
|  | 436 			{ | 
|  | 437 				return null; | 
|  | 438 			} | 
|  | 439 		} | 
|  | 440 | 
|  | 441 		#endregion | 
|  | 442 	} | 
|  | 443 } |