2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
6 using System.Reflection;
8 using System.Diagnostics;
10 namespace System.Data.Linq {
11 using System.Data.Linq.Mapping;
12 using System.Data.Linq.Provider;
13 using System.Diagnostics.CodeAnalysis;
15 public sealed class ChangeConflictCollection : ICollection<ObjectChangeConflict>, ICollection, IEnumerable<ObjectChangeConflict>, IEnumerable {
16 private List<ObjectChangeConflict> conflicts;
18 internal ChangeConflictCollection() {
19 this.conflicts = new List<ObjectChangeConflict>();
23 /// The number of conflicts in the collection
26 get { return this.conflicts.Count; }
29 public ObjectChangeConflict this[int index] {
30 get { return this.conflicts[index]; }
33 bool ICollection<ObjectChangeConflict>.IsReadOnly {
37 void ICollection<ObjectChangeConflict>.Add(ObjectChangeConflict item) {
38 throw Error.CannotAddChangeConflicts();
42 /// Removes the specified conflict from the collection.
44 /// <param name="item">The conflict to remove</param>
45 /// <returns></returns>
46 public bool Remove(ObjectChangeConflict item) {
47 return this.conflicts.Remove(item);
51 /// Removes all conflicts from the collection
54 this.conflicts.Clear();
58 /// Returns true if the specified conflict is a member of the collection.
60 /// <param name="item"></param>
61 /// <returns></returns>
62 public bool Contains(ObjectChangeConflict item) {
63 return this.conflicts.Contains(item);
66 public void CopyTo(ObjectChangeConflict[] array, int arrayIndex) {
67 this.conflicts.CopyTo(array, arrayIndex);
71 /// Returns the enumerator for the collection.
73 /// <returns></returns>
74 public IEnumerator<ObjectChangeConflict> GetEnumerator() {
75 return this.conflicts.GetEnumerator();
78 IEnumerator IEnumerable.GetEnumerator() {
79 return this.conflicts.GetEnumerator();
82 bool ICollection.IsSynchronized {
86 object ICollection.SyncRoot {
90 void ICollection.CopyTo(Array array, int index) {
91 ((ICollection)this.conflicts).CopyTo(array, index);
95 /// Resolves all conflicts in the collection using the specified strategy.
97 /// <param name="mode">The strategy to use to resolve the conflicts.</param>
98 public void ResolveAll(RefreshMode mode) {
99 this.ResolveAll(mode, true);
103 /// Resolves all conflicts in the collection using the specified strategy.
105 /// <param name="mode">The strategy to use to resolve the conflicts.</param>
106 /// <param name="autoResolveDeletes">If true conflicts resulting from the modified
107 /// object no longer existing in the database will be automatically resolved.</param>
108 public void ResolveAll(RefreshMode mode, bool autoResolveDeletes) {
109 foreach (ObjectChangeConflict c in this.conflicts) {
111 c.Resolve(mode, autoResolveDeletes);
116 internal void Fill(List<ObjectChangeConflict> conflictList) {
117 this.conflicts = conflictList;
121 internal sealed class ChangeConflictSession {
122 private DataContext context;
123 private DataContext refreshContext;
125 internal ChangeConflictSession(DataContext context) {
126 this.context = context;
129 internal DataContext Context {
130 get { return this.context; }
133 internal DataContext RefreshContext {
135 if (this.refreshContext == null) {
136 this.refreshContext = this.context.CreateRefreshContext();
138 return this.refreshContext;
144 /// Represents an update with one or more optimistic concurrency conflicts.
146 public sealed class ObjectChangeConflict {
147 private ChangeConflictSession session;
148 private TrackedObject trackedObject;
149 private bool isResolved;
150 private ReadOnlyCollection<MemberChangeConflict> memberConflicts;
151 private object database;
152 private object original;
153 private bool? isDeleted;
158 /// <param name="session">The session in which the conflicts occurred.</param>
159 /// <param name="trackedObject">The tracked item in conflict.</param>
160 internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject) {
161 this.session = session;
162 this.trackedObject = trackedObject;
163 this.original = trackedObject.CreateDataCopy(trackedObject.Original);
169 /// <param name="session">The session in which the conflicts occurred.</param>
170 /// <param name="trackedObject">The tracked item in conflict.</param>
171 /// <param name="isDeleted">True if the item in conflict no longer exists in the database.</param>
172 internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject, bool isDeleted)
173 : this(session, trackedObject) {
174 this.isDeleted = isDeleted;
177 internal ChangeConflictSession Session {
178 get { return this.session; }
181 internal TrackedObject TrackedObject {
182 get { return this.trackedObject; }
186 /// The object in conflict.
188 public object Object {
189 get { return this.trackedObject.Current; }
193 /// An instance containing the baseline original values used to perform the concurrency check.
195 internal object Original {
196 get { return this.original; }
200 /// True if the conflicts for this object have already been resovled.
202 public bool IsResolved {
203 get { return this.isResolved; }
207 /// True if the object in conflict has been deleted from the database.
209 public bool IsDeleted {
211 if (this.isDeleted.HasValue) {
212 return this.isDeleted.Value;
214 return (this.Database == null);
219 /// An instance containing the most recent values from the database
221 internal object Database {
223 if (this.database == null) {
224 // use the 'refresh' context to retrieve the current database state
225 DataContext ctxt = this.session.RefreshContext;
226 object[] keyValues = CommonDataServices.GetKeyValues(this.trackedObject.Type, this.original);
227 this.database = ctxt.Services.GetObjectByKey(this.trackedObject.Type, keyValues);
229 return this.database;
234 /// Resolve member conflicts keeping current values and resetting the baseline 'Original' values
235 /// to match the more recent 'Database' values.
237 public void Resolve() {
238 this.Resolve(RefreshMode.KeepCurrentValues, true);
242 /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values
243 /// to match the more recent 'Database' values.
245 /// <param name="refreshMode">The mode that determines how the current values are
246 /// changed in order to resolve the conflict</param>
247 public void Resolve(RefreshMode refreshMode) {
248 this.Resolve(refreshMode, false);
252 /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values
253 /// to match the more recent 'Database' values.
255 /// <param name="refreshMode">The mode that determines how the current values are
256 /// changed in order to resolve the conflict</param>
257 /// <param name="autoResolveDeletes">If true conflicts resulting from the modified
258 /// object no longer existing in the database will be automatically resolved.</param>
259 public void Resolve(RefreshMode refreshMode, bool autoResolveDeletes) {
260 if (autoResolveDeletes && this.IsDeleted) {
261 this.ResolveDelete();
264 // We make these calls explicity rather than simply calling
265 // DataContext.Refresh (which does virtually the same thing)
266 // since we want to cache the database value read.
267 if (this.Database == null) {
268 throw Error.RefreshOfDeletedObject();
270 trackedObject.Refresh(refreshMode, this.Database);
271 this.isResolved = true;
276 /// Resolve a conflict where we have updated an entity that no longer exists
279 private void ResolveDelete() {
280 Debug.Assert(this.IsDeleted);
281 // If the user is attempting to update an entity that no longer exists
282 // in the database, we first need to [....] the delete into the local cache.
283 if (!trackedObject.IsDeleted) {
284 trackedObject.ConvertToDeleted();
287 // As the object have been deleted, it needs to leave the cache
288 this.Session.Context.Services.RemoveCachedObjectLike(trackedObject.Type, trackedObject.Original);
290 // Now that our cache is in [....], we accept the changes
291 this.trackedObject.AcceptChanges();
292 this.isResolved = true;
296 /// Returns a collection of all member conflicts that caused the update to fail.
298 public ReadOnlyCollection<MemberChangeConflict> MemberConflicts {
300 if (this.memberConflicts == null) {
301 var list = new List<MemberChangeConflict>();
302 if (this.Database != null) {
303 // determine which members are in conflict
304 foreach (MetaDataMember metaMember in trackedObject.Type.PersistentDataMembers) {
305 if (!metaMember.IsAssociation && this.HasMemberConflict(metaMember)) {
306 list.Add(new MemberChangeConflict(this, metaMember));
310 this.memberConflicts = list.AsReadOnly();
312 return this.memberConflicts;
316 private bool HasMemberConflict(MetaDataMember member) {
317 object oValue = member.StorageAccessor.GetBoxedValue(this.original);
318 if (!member.DeclaringType.Type.IsAssignableFrom(this.database.GetType())) {
321 object dValue = member.StorageAccessor.GetBoxedValue(this.database);
322 return !this.AreEqual(member, oValue, dValue);
325 private bool AreEqual(MetaDataMember member, object v1, object v2) {
326 if (v1 == null && v2 == null)
328 if (v1 == null || v2 == null)
330 if (member.Type == typeof(char[])) {
331 return this.AreEqual((char[])v1, (char[])v2);
333 else if (member.Type == typeof(byte[])) {
334 return this.AreEqual((byte[])v1, (byte[])v2);
337 return object.Equals(v1, v2);
341 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
342 private bool AreEqual(char[] a1, char[] a2) {
343 if (a1.Length != a2.Length)
345 for (int i = 0, n = a1.Length; i < n; i++) {
352 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
353 private bool AreEqual(byte[] a1, byte[] a2) {
354 if (a1.Length != a2.Length)
356 for (int i = 0, n = a1.Length; i < n; i++) {
363 internal void OnMemberResolved() {
364 if (!this.IsResolved) {
365 int nResolved = this.memberConflicts.AsEnumerable().Count(m => m.IsResolved);
366 if (nResolved == this.memberConflicts.Count) {
367 this.Resolve(RefreshMode.KeepCurrentValues, false);
374 /// Represents a single optimistic concurrency member conflict.
376 public sealed class MemberChangeConflict {
377 private ObjectChangeConflict conflict;
378 private MetaDataMember metaMember;
379 private object originalValue;
380 private object databaseValue;
381 private object currentValue;
384 internal MemberChangeConflict(ObjectChangeConflict conflict, MetaDataMember metaMember) {
385 this.conflict = conflict;
386 this.metaMember = metaMember;
387 this.originalValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Original);
388 this.databaseValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Database);
389 this.currentValue = metaMember.StorageAccessor.GetBoxedValue(conflict.TrackedObject.Current);
393 /// The previous client value.
395 public object OriginalValue {
396 get { return this.originalValue; }
400 /// The current database value.
402 public object DatabaseValue {
403 get { return this.databaseValue; }
407 /// The current client value.
409 public object CurrentValue {
410 get { return this.currentValue; }
414 /// MemberInfo for the member in conflict.
416 public MemberInfo Member {
417 get { return this.metaMember.Member; }
421 /// Updates the current value to the specified value.
423 public void Resolve(object value) {
424 this.conflict.TrackedObject.RefreshMember(this.metaMember, RefreshMode.OverwriteCurrentValues, value);
425 this.isResolved = true;
426 this.conflict.OnMemberResolved();
430 /// Updates the current value using the specified strategy.
432 public void Resolve(RefreshMode refreshMode) {
433 this.conflict.TrackedObject.RefreshMember(this.metaMember, refreshMode, this.databaseValue);
434 this.isResolved = true;
435 this.conflict.OnMemberResolved();
439 /// True if the value was modified by the client.
441 public bool IsModified {
442 get { return this.conflict.TrackedObject.HasChangedValue(this.metaMember); }
446 /// True if the member conflict has been resolved.
448 public bool IsResolved {
449 get { return this.isResolved; }