Mercurial > pub > bltoolkit
comparison Source/Data/Linq/Builder/UpdateBuilder.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 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 } |