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
);