Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Linq / ChangeDirector.cs
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq.Expressions;
5 using System.Text;
6 using System.Reflection;
7 using System.Linq;
8 using System.Security.Permissions;
9 using System.Security;
10
11 namespace System.Data.Linq {
12     using System.Data.Linq.Mapping;
13     using System.Data.Linq.Provider;
14     using System.Diagnostics.CodeAnalysis;
15
16     /// <summary>
17     /// Controls how inserts, updates and deletes are performed.
18     /// </summary>
19     internal abstract class ChangeDirector {
20         internal abstract int Insert(TrackedObject item);
21         internal abstract int DynamicInsert(TrackedObject item);
22         internal abstract void AppendInsertText(TrackedObject item, StringBuilder appendTo);
23
24         internal abstract int Update(TrackedObject item);
25         internal abstract int DynamicUpdate(TrackedObject item);
26         internal abstract void AppendUpdateText(TrackedObject item, StringBuilder appendTo);
27
28         internal abstract int Delete(TrackedObject item);
29         internal abstract int DynamicDelete(TrackedObject item);
30         internal abstract void AppendDeleteText(TrackedObject item, StringBuilder appendTo);
31
32         internal abstract void RollbackAutoSync();
33         internal abstract void ClearAutoSyncRollback();
34
35         internal static ChangeDirector CreateChangeDirector(DataContext context) {
36             return new StandardChangeDirector(context);
37         }
38
39         /// <summary>
40         /// Implementation of ChangeDirector which calls user code if possible 
41         /// and othewise falls back to creating SQL for 'INSERT', 'UPDATE' and 'DELETE'.
42         /// </summary>
43         internal class StandardChangeDirector : ChangeDirector {
44             private enum UpdateType { Insert, Update, Delete };
45             private enum AutoSyncBehavior { ApplyNewAutoSync, RollbackSavedValues }            
46
47             DataContext context;
48             [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="Microsoft: FxCop bug Dev10:423110 -- List<KeyValuePair<object, object>> is not supposed to be flagged as a violation.")]
49             List<KeyValuePair<TrackedObject, object[]>> syncRollbackItems;
50             
51             internal StandardChangeDirector(DataContext context) {
52                 this.context = context;
53             }
54
55             [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="Microsoft: FxCop bug Dev10:423110 -- List<KeyValuePair<object, object>> is not supposed to be flagged as a violation.")]
56             private List<KeyValuePair<TrackedObject, object[]>> SyncRollbackItems {
57                 get {
58                     if (syncRollbackItems == null) {
59                         syncRollbackItems = new List<KeyValuePair<TrackedObject, object[]>>();
60                     }
61                     return syncRollbackItems;
62                 }
63             }
64
65             internal override int Insert(TrackedObject item) {
66                 if (item.Type.Table.InsertMethod != null) {
67                     try {
68                         item.Type.Table.InsertMethod.Invoke(this.context, new object[] { item.Current });
69                     }
70                     catch (TargetInvocationException tie) {
71                         if (tie.InnerException != null) {
72                             throw tie.InnerException;
73                         }
74                         throw;
75                     }
76                     return 1;
77                 }
78                 else {
79                     return DynamicInsert(item);
80                 }
81             }
82
83             internal override int DynamicInsert(TrackedObject item) {
84                 Expression cmd = this.GetInsertCommand(item);
85                 if (cmd.Type == typeof(int)) {
86                     return (int)this.context.Provider.Execute(cmd).ReturnValue;
87                 }
88                 else {
89                     IEnumerable<object> facts = (IEnumerable<object>)this.context.Provider.Execute(cmd).ReturnValue;
90                     object[] syncResults = (object[])facts.FirstOrDefault();
91                     if (syncResults != null) {
92                         // sync any auto gen or computed members
93                         AutoSyncMembers(syncResults, item, UpdateType.Insert, AutoSyncBehavior.ApplyNewAutoSync);
94                         return 1;
95                     }
96                     else {
97                         throw Error.InsertAutoSyncFailure();
98                     }
99                 }
100             }
101
102             internal override void AppendInsertText(TrackedObject item, StringBuilder appendTo) {
103                 if (item.Type.Table.InsertMethod != null) {
104                     appendTo.Append(Strings.InsertCallbackComment);
105                 }
106                 else {
107                     Expression cmd = this.GetInsertCommand(item);
108                     appendTo.Append(this.context.Provider.GetQueryText(cmd));
109                     appendTo.AppendLine();
110                 }
111             }
112
113             /// <summary>
114             /// Update the item, returning 0 if the update fails, 1 if it succeeds.
115             /// </summary>        
116             internal override int Update(TrackedObject item) {
117                 if (item.Type.Table.UpdateMethod != null) {
118                     // create a copy - don't allow the override to modify our
119                     // internal original values
120                     try {
121                         item.Type.Table.UpdateMethod.Invoke(this.context, new object[] { item.Current });
122                     }
123                     catch (TargetInvocationException tie) {
124                         if (tie.InnerException != null) {
125                             throw tie.InnerException;
126                         }
127                         throw;
128                     }
129                     return 1;
130                 }
131                 else {
132                     return DynamicUpdate(item);
133                 }
134             }
135
136             internal override int DynamicUpdate(TrackedObject item) {
137                 Expression cmd = this.GetUpdateCommand(item);
138                 if (cmd.Type == typeof(int)) {
139                     return (int)this.context.Provider.Execute(cmd).ReturnValue;
140                 }
141                 else {
142                     IEnumerable<object> facts = (IEnumerable<object>)this.context.Provider.Execute(cmd).ReturnValue;
143                     object[] syncResults = (object[])facts.FirstOrDefault();
144                     if (syncResults != null) {
145                         // sync any auto gen or computed members
146                         AutoSyncMembers(syncResults, item, UpdateType.Update, AutoSyncBehavior.ApplyNewAutoSync);
147                         return 1;
148                     }
149                     else {
150                         return 0;
151                     }
152                 }
153             }
154
155             internal override void AppendUpdateText(TrackedObject item, StringBuilder appendTo) {
156                 if (item.Type.Table.UpdateMethod != null) {
157                     appendTo.Append(Strings.UpdateCallbackComment);
158                 }
159                 else {
160                     Expression cmd = this.GetUpdateCommand(item);
161                     appendTo.Append(this.context.Provider.GetQueryText(cmd));
162                     appendTo.AppendLine();
163                 }
164             }
165
166             internal override int Delete(TrackedObject item) {
167                 if (item.Type.Table.DeleteMethod != null) {
168                     try {
169                         item.Type.Table.DeleteMethod.Invoke(this.context, new object[] { item.Current });
170                     }
171                     catch (TargetInvocationException tie) {
172                         if (tie.InnerException != null) {
173                             throw tie.InnerException;
174                         }
175                         throw;
176                     }
177                     return 1;
178                 }
179                 else {
180                     return DynamicDelete(item);
181                 }
182             }
183
184             internal override int DynamicDelete(TrackedObject item) {
185                 Expression cmd = this.GetDeleteCommand(item);
186                 int ret = (int)this.context.Provider.Execute(cmd).ReturnValue;
187                 if (ret == 0) {
188                     // we don't yet know if the delete failed because the check constaint did not match
189                     // or item was already deleted.  Verify the item exists
190                     cmd = this.GetDeleteVerificationCommand(item);
191                     ret = ((int?)this.context.Provider.Execute(cmd).ReturnValue) ?? -1;
192                 }
193                 return ret;
194             }
195
196             internal override void AppendDeleteText(TrackedObject item, StringBuilder appendTo) {
197                 if (item.Type.Table.DeleteMethod != null) {
198                     appendTo.Append(Strings.DeleteCallbackComment);
199                 }
200                 else {
201                     Expression cmd = this.GetDeleteCommand(item);
202                     appendTo.Append(this.context.Provider.GetQueryText(cmd));
203                     appendTo.AppendLine();
204                 }
205             }
206
207             [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="Microsoft: FxCop bug Dev10:423110 -- List<KeyValuePair<object, object>> is not supposed to be flagged as a violation.")]
208             internal override void  RollbackAutoSync() {
209                 // Rolls back any AutoSync values that may have been set already
210                 // Those values are no longer valid since the transaction will be rolled back on the server
211                 if (this.syncRollbackItems != null) {
212                     foreach (KeyValuePair<TrackedObject, object[]> rollbackItemPair in this.SyncRollbackItems) {
213                         TrackedObject rollbackItem = rollbackItemPair.Key;
214                         object[] rollbackValues = rollbackItemPair.Value;
215
216                         AutoSyncMembers(
217                             rollbackValues,
218                             rollbackItem,
219                             rollbackItem.IsNew ? UpdateType.Insert : UpdateType.Update,
220                             AutoSyncBehavior.RollbackSavedValues);
221                     }
222                 }
223             }
224
225             [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="Microsoft: FxCop bug Dev10:423110 -- List<KeyValuePair<object, object>> is not supposed to be flagged as a violation.")]
226             internal override void  ClearAutoSyncRollback() {
227                 this.syncRollbackItems = null;
228             }
229
230             private Expression GetInsertCommand(TrackedObject item) {
231                 MetaType mt = item.Type;
232
233                 // bind to InsertFacts if there are any members to syncronize
234                 List<MetaDataMember> membersToSync = GetAutoSyncMembers(mt, UpdateType.Insert);
235                 ParameterExpression p = Expression.Parameter(item.Type.Table.RowType.Type, "p");
236                 if (membersToSync.Count > 0) {
237                     Expression autoSync = this.CreateAutoSync(membersToSync, p);
238                     LambdaExpression resultSelector = Expression.Lambda(autoSync, p);
239                     return Expression.Call(typeof(DataManipulation), "Insert", new Type[] { item.Type.InheritanceRoot.Type, resultSelector.Body.Type }, Expression.Constant(item.Current), resultSelector);
240                 }
241                 else {
242                     return Expression.Call(typeof(DataManipulation), "Insert", new Type[] { item.Type.InheritanceRoot.Type }, Expression.Constant(item.Current));
243                 }
244             }
245
246             /// <summary>
247             /// For the meta members specified, create an array initializer for each and bind to
248             /// an output array.
249             /// </summary>
250             private Expression CreateAutoSync(List<MetaDataMember> membersToSync, Expression source) {
251                 System.Diagnostics.Debug.Assert(membersToSync.Count > 0);
252                 int i = 0;
253                 Expression[] initializers = new Expression[membersToSync.Count];
254                 foreach (MetaDataMember mm in membersToSync) {
255                     initializers[i++] = Expression.Convert(this.GetMemberExpression(source, mm.Member), typeof(object));
256                 }
257                 return Expression.NewArrayInit(typeof(object), initializers);
258             }
259
260             private static List<MetaDataMember> GetAutoSyncMembers(MetaType metaType, UpdateType updateType) {
261                 List<MetaDataMember> membersToSync = new List<MetaDataMember>();
262                 foreach (MetaDataMember metaMember in metaType.PersistentDataMembers.OrderBy(m => m.Ordinal)) {
263                     // add all auto generated members for the specified update type to the auto-sync list
264                     if ((updateType == UpdateType.Insert && metaMember.AutoSync == AutoSync.OnInsert) ||
265                         (updateType == UpdateType.Update && metaMember.AutoSync == AutoSync.OnUpdate) ||
266                          metaMember.AutoSync == AutoSync.Always) {
267                         membersToSync.Add(metaMember);
268                     }
269                 }
270                 return membersToSync;
271             }
272
273             /// <summary>
274             /// Synchronize the specified item by copying in data from the specified results.
275             /// Used to sync members after successful insert or update, but also used to rollback to previous values if a failure
276             /// occurs on other entities in the same SubmitChanges batch.
277             /// </summary>
278             /// <param name="autoSyncBehavior">
279             /// If AutoSyncBehavior.ApplyNewAutoSync, the current value of the property is saved before the sync occurs. This is used for normal synchronization after a successful update/insert.
280             /// Otherwise, the current value is not saved. This is used for rollback operations when something in the SubmitChanges batch failed, rendering the previously-sync'd values invalid.
281             /// </param>
282             [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="Microsoft: FxCop bug Dev10:423110 -- List<KeyValuePair<object, object>> is not supposed to be flagged as a violation.")]
283             private void AutoSyncMembers(object[] syncResults, TrackedObject item, UpdateType updateType, AutoSyncBehavior autoSyncBehavior) {
284                 System.Diagnostics.Debug.Assert(item != null);
285                 System.Diagnostics.Debug.Assert(item.IsNew || item.IsPossiblyModified, "AutoSyncMembers should only be called for new and modified objects.");
286                 object[] syncRollbackValues = null;
287                 if (syncResults != null) {
288                     int idx = 0;
289                     List<MetaDataMember> membersToSync = GetAutoSyncMembers(item.Type, updateType);
290                     System.Diagnostics.Debug.Assert(syncResults.Length == membersToSync.Count);                    
291                     if (autoSyncBehavior == AutoSyncBehavior.ApplyNewAutoSync) {
292                         syncRollbackValues = new object[syncResults.Length];
293                     }
294                     foreach (MetaDataMember mm in membersToSync) {
295                         object value = syncResults[idx];
296                         object current = item.Current;
297                         MetaAccessor accessor =
298                             (mm.Member is PropertyInfo && ((PropertyInfo)mm.Member).CanWrite)
299                                 ? mm.MemberAccessor
300                                 : mm.StorageAccessor;
301
302                         if (syncRollbackValues != null) {
303                             syncRollbackValues[idx] = accessor.GetBoxedValue(current);
304                         }
305                         accessor.SetBoxedValue(ref current, DBConvert.ChangeType(value, mm.Type));
306                         idx++;
307                     }                    
308                 }
309                 if (syncRollbackValues != null) {
310                     this.SyncRollbackItems.Add(new KeyValuePair<TrackedObject, object[]>(item, syncRollbackValues));
311                 }
312             }
313
314             private Expression GetUpdateCommand(TrackedObject tracked) {
315                 object database = tracked.Original;
316                 MetaType rowType = tracked.Type.GetInheritanceType(database.GetType());
317                 MetaType rowTypeRoot = rowType.InheritanceRoot;
318
319                 ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p");
320                 Expression pv = p;
321                 if (rowType != rowTypeRoot) {
322                     pv = Expression.Convert(p, rowType.Type);
323                 }
324
325                 Expression check = this.GetUpdateCheck(pv, tracked);
326                 if (check != null) {
327                     check = Expression.Lambda(check, p);
328                 }
329
330                 // bind to out array if there are any members to synchronize
331                 List<MetaDataMember> membersToSync = GetAutoSyncMembers(rowType, UpdateType.Update);
332                 if (membersToSync.Count > 0) {
333                     Expression autoSync = this.CreateAutoSync(membersToSync, pv);
334                     LambdaExpression resultSelector = Expression.Lambda(autoSync, p);
335                     if (check != null) {
336                         return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), check, resultSelector);
337                     }
338                     else {
339                         return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), resultSelector);
340                     }
341                 }
342                 else if (check != null) {
343                     return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current), check);
344                 }
345                 else {
346                     return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current));
347                 }
348             }
349
350             private Expression GetUpdateCheck(Expression serverItem, TrackedObject tracked) {
351                 MetaType mt = tracked.Type;
352                 if (mt.VersionMember != null) {
353                     return Expression.Equal(
354                         this.GetMemberExpression(serverItem, mt.VersionMember.Member),
355                         this.GetMemberExpression(Expression.Constant(tracked.Current), mt.VersionMember.Member)
356                         );
357                 }
358                 else {
359                     Expression expr = null;
360                     foreach (MetaDataMember mm in mt.PersistentDataMembers) {
361                         if (!mm.IsPrimaryKey) {
362                             UpdateCheck check = mm.UpdateCheck;
363                             if (check == UpdateCheck.Always ||
364                                 (check == UpdateCheck.WhenChanged && tracked.HasChangedValue(mm))) {
365                                 object memberValue = mm.MemberAccessor.GetBoxedValue(tracked.Original);
366                                 Expression eq =
367                                     Expression.Equal(
368                                         this.GetMemberExpression(serverItem, mm.Member),
369                                         Expression.Constant(memberValue, mm.Type)
370                                         );
371                                 expr = (expr != null) ? Expression.And(expr, eq) : eq;
372                             }
373                         }
374                     }
375                     return expr;
376                 }
377             }
378
379             private Expression GetDeleteCommand(TrackedObject tracked) {
380                 MetaType rowType = tracked.Type;
381                 MetaType rowTypeRoot = rowType.InheritanceRoot;
382                 ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p");
383                 Expression pv = p;
384                 if (rowType != rowTypeRoot) {
385                     pv = Expression.Convert(p, rowType.Type);
386                 }
387                 object original = tracked.CreateDataCopy(tracked.Original);
388                 Expression check = this.GetUpdateCheck(pv, tracked);
389                 if (check != null) {
390                     check = Expression.Lambda(check, p);
391                     return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original), check);
392                 }
393                 else {
394                     return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original));
395                 }
396             }
397
398             private Expression GetDeleteVerificationCommand(TrackedObject tracked) {
399                 ITable table = this.context.GetTable(tracked.Type.InheritanceRoot.Type);
400                 System.Diagnostics.Debug.Assert(table != null);
401                 ParameterExpression p = Expression.Parameter(table.ElementType, "p");
402                 Expression pred = Expression.Lambda(Expression.Equal(p, Expression.Constant(tracked.Current)), p);
403                 Expression where = Expression.Call(typeof(Queryable), "Where", new Type[] { table.ElementType }, table.Expression, pred);
404                 Expression selector = Expression.Lambda(Expression.Constant(0, typeof(int?)), p);
405                 Expression select = Expression.Call(typeof(Queryable), "Select", new Type[] { table.ElementType, typeof(int?) }, where, selector);
406                 Expression singleOrDefault = Expression.Call(typeof(Queryable), "SingleOrDefault", new Type[] { typeof(int?) }, select);
407                 return singleOrDefault;
408             }
409
410             [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
411             private Expression GetMemberExpression(Expression exp, MemberInfo mi) {
412                 FieldInfo fi = mi as FieldInfo;
413                 if (fi != null)
414                     return Expression.Field(exp, fi);
415                 PropertyInfo pi = (PropertyInfo)mi;
416                 return Expression.Property(exp, pi);
417             }
418         }
419     }
420 }