2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq.Expressions;
6 using System.Reflection;
8 using System.Security.Permissions;
11 namespace System.Data.Linq {
12 using System.Data.Linq.Mapping;
13 using System.Data.Linq.Provider;
14 using System.Diagnostics.CodeAnalysis;
17 /// Controls how inserts, updates and deletes are performed.
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);
24 internal abstract int Update(TrackedObject item);
25 internal abstract int DynamicUpdate(TrackedObject item);
26 internal abstract void AppendUpdateText(TrackedObject item, StringBuilder appendTo);
28 internal abstract int Delete(TrackedObject item);
29 internal abstract int DynamicDelete(TrackedObject item);
30 internal abstract void AppendDeleteText(TrackedObject item, StringBuilder appendTo);
32 internal abstract void RollbackAutoSync();
33 internal abstract void ClearAutoSyncRollback();
35 internal static ChangeDirector CreateChangeDirector(DataContext context) {
36 return new StandardChangeDirector(context);
40 /// Implementation of ChangeDirector which calls user code if possible
41 /// and othewise falls back to creating SQL for 'INSERT', 'UPDATE' and 'DELETE'.
43 internal class StandardChangeDirector : ChangeDirector {
44 private enum UpdateType { Insert, Update, Delete };
45 private enum AutoSyncBehavior { ApplyNewAutoSync, RollbackSavedValues }
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;
51 internal StandardChangeDirector(DataContext context) {
52 this.context = context;
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 {
58 if (syncRollbackItems == null) {
59 syncRollbackItems = new List<KeyValuePair<TrackedObject, object[]>>();
61 return syncRollbackItems;
65 internal override int Insert(TrackedObject item) {
66 if (item.Type.Table.InsertMethod != null) {
68 item.Type.Table.InsertMethod.Invoke(this.context, new object[] { item.Current });
70 catch (TargetInvocationException tie) {
71 if (tie.InnerException != null) {
72 throw tie.InnerException;
79 return DynamicInsert(item);
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;
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);
97 throw Error.InsertAutoSyncFailure();
102 internal override void AppendInsertText(TrackedObject item, StringBuilder appendTo) {
103 if (item.Type.Table.InsertMethod != null) {
104 appendTo.Append(Strings.InsertCallbackComment);
107 Expression cmd = this.GetInsertCommand(item);
108 appendTo.Append(this.context.Provider.GetQueryText(cmd));
109 appendTo.AppendLine();
114 /// Update the item, returning 0 if the update fails, 1 if it succeeds.
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
121 item.Type.Table.UpdateMethod.Invoke(this.context, new object[] { item.Current });
123 catch (TargetInvocationException tie) {
124 if (tie.InnerException != null) {
125 throw tie.InnerException;
132 return DynamicUpdate(item);
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;
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);
155 internal override void AppendUpdateText(TrackedObject item, StringBuilder appendTo) {
156 if (item.Type.Table.UpdateMethod != null) {
157 appendTo.Append(Strings.UpdateCallbackComment);
160 Expression cmd = this.GetUpdateCommand(item);
161 appendTo.Append(this.context.Provider.GetQueryText(cmd));
162 appendTo.AppendLine();
166 internal override int Delete(TrackedObject item) {
167 if (item.Type.Table.DeleteMethod != null) {
169 item.Type.Table.DeleteMethod.Invoke(this.context, new object[] { item.Current });
171 catch (TargetInvocationException tie) {
172 if (tie.InnerException != null) {
173 throw tie.InnerException;
180 return DynamicDelete(item);
184 internal override int DynamicDelete(TrackedObject item) {
185 Expression cmd = this.GetDeleteCommand(item);
186 int ret = (int)this.context.Provider.Execute(cmd).ReturnValue;
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;
196 internal override void AppendDeleteText(TrackedObject item, StringBuilder appendTo) {
197 if (item.Type.Table.DeleteMethod != null) {
198 appendTo.Append(Strings.DeleteCallbackComment);
201 Expression cmd = this.GetDeleteCommand(item);
202 appendTo.Append(this.context.Provider.GetQueryText(cmd));
203 appendTo.AppendLine();
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;
219 rollbackItem.IsNew ? UpdateType.Insert : UpdateType.Update,
220 AutoSyncBehavior.RollbackSavedValues);
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;
230 private Expression GetInsertCommand(TrackedObject item) {
231 MetaType mt = item.Type;
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);
242 return Expression.Call(typeof(DataManipulation), "Insert", new Type[] { item.Type.InheritanceRoot.Type }, Expression.Constant(item.Current));
247 /// For the meta members specified, create an array initializer for each and bind to
250 private Expression CreateAutoSync(List<MetaDataMember> membersToSync, Expression source) {
251 System.Diagnostics.Debug.Assert(membersToSync.Count > 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));
257 return Expression.NewArrayInit(typeof(object), initializers);
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);
270 return membersToSync;
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.
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.
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) {
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];
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)
300 : mm.StorageAccessor;
302 if (syncRollbackValues != null) {
303 syncRollbackValues[idx] = accessor.GetBoxedValue(current);
305 accessor.SetBoxedValue(ref current, DBConvert.ChangeType(value, mm.Type));
309 if (syncRollbackValues != null) {
310 this.SyncRollbackItems.Add(new KeyValuePair<TrackedObject, object[]>(item, syncRollbackValues));
314 private Expression GetUpdateCommand(TrackedObject tracked) {
315 object database = tracked.Original;
316 MetaType rowType = tracked.Type.GetInheritanceType(database.GetType());
317 MetaType rowTypeRoot = rowType.InheritanceRoot;
319 ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p");
321 if (rowType != rowTypeRoot) {
322 pv = Expression.Convert(p, rowType.Type);
325 Expression check = this.GetUpdateCheck(pv, tracked);
327 check = Expression.Lambda(check, p);
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);
336 return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), check, resultSelector);
339 return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), resultSelector);
342 else if (check != null) {
343 return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current), check);
346 return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current));
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)
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);
368 this.GetMemberExpression(serverItem, mm.Member),
369 Expression.Constant(memberValue, mm.Type)
371 expr = (expr != null) ? Expression.And(expr, eq) : eq;
379 private Expression GetDeleteCommand(TrackedObject tracked) {
380 MetaType rowType = tracked.Type;
381 MetaType rowTypeRoot = rowType.InheritanceRoot;
382 ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p");
384 if (rowType != rowTypeRoot) {
385 pv = Expression.Convert(p, rowType.Type);
387 object original = tracked.CreateDataCopy(tracked.Original);
388 Expression check = this.GetUpdateCheck(pv, tracked);
390 check = Expression.Lambda(check, p);
391 return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original), check);
394 return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original));
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;
410 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
411 private Expression GetMemberExpression(Expression exp, MemberInfo mi) {
412 FieldInfo fi = mi as FieldInfo;
414 return Expression.Field(exp, fi);
415 PropertyInfo pi = (PropertyInfo)mi;
416 return Expression.Property(exp, pi);