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