5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
\r
8 // of this software and associated documentation files (the "Software"), to deal
\r
9 // in the Software without restriction, including without limitation the rights
\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
11 // copies of the Software, and to permit persons to whom the Software is
\r
12 // furnished to do so, subject to the following conditions:
\r
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
\r
27 using System.Collections.Generic;
\r
28 using System.ComponentModel;
\r
29 using System.Data.Linq.Mapping;
\r
30 using System.Reflection;
\r
33 namespace DbLinq.Data.Linq.Implementation
\r
36 /// ModificationHandler class handles entities in two ways:
\r
37 /// 1. if entity implements IModifed, uses the interface and its IsModifed flag property
\r
38 /// 2. otherwise, the handler keeps a dictionary of raw data per entity
\r
40 internal class MemberModificationHandler : IMemberModificationHandler
\r
42 private readonly IDictionary<object, IDictionary<string, object>> rawDataEntities = new Dictionary<object, IDictionary<string, object>>(new ReferenceEqualityComparer<object>());
\r
43 private readonly IDictionary<object, IDictionary<string, MemberInfo>> modifiedProperties = new Dictionary<object, IDictionary<string, MemberInfo>>(new ReferenceEqualityComparer<object>());
\r
45 private static readonly IDictionary<string, MemberInfo> propertyChangingSentinal = new Dictionary<string, MemberInfo>();
\r
48 /// Gets the column members.
\r
50 /// <param name="entityType">Type of the entity.</param>
\r
51 /// <param name="metaModel">The meta model.</param>
\r
52 /// <returns></returns>
\r
53 protected virtual IEnumerable<MemberInfo> GetColumnMembers(Type entityType, MetaModel metaModel)
\r
55 foreach (var dataMember in metaModel.GetTable(entityType).RowType.PersistentDataMembers)
\r
57 yield return dataMember.Member;
\r
62 /// Determines whether the specified type is primitive type.
\r
64 /// <param name="type">The type.</param>
\r
66 /// <c>true</c> if the specified type is primitive type; otherwise, <c>false</c>.
\r
68 protected static bool IsPrimitiveType(Type type)
\r
70 if (type.IsValueType)
\r
72 if (type == typeof(string))
\r
78 /// Adds simple (value) properties of an object to a given dictionary
\r
79 /// and recurses if a property contains complex data
\r
81 /// <param name="entity"></param>
\r
82 /// <param name="rawData"></param>
\r
83 /// <param name="prefix"></param>
\r
84 /// <param name="metaModel"></param>
\r
85 protected void AddRawData(object entity, IDictionary<string, object> rawData, string prefix, MetaModel metaModel)
\r
89 foreach (var memberInfo in GetColumnMembers(entity.GetType(), metaModel))
\r
91 var propertyValue = memberInfo.GetMemberValue(entity);
\r
92 // if it is a value, it can be stored directly
\r
93 var memberType = memberInfo.GetMemberType();
\r
94 if (IsPrimitiveType(memberType))
\r
96 rawData[prefix + memberInfo.Name] = propertyValue;
\r
98 else if (memberType.IsArray)
\r
100 if (propertyValue != null)
\r
102 var arrayValue = (Array) propertyValue;
\r
103 for (int arrayIndex = 0; arrayIndex < arrayValue.Length; arrayIndex++)
\r
105 rawData[string.Format("{0}[{1}]", memberInfo.Name, arrayIndex)] =
\r
106 arrayValue.GetValue(arrayIndex);
\r
110 else // otherwise, we recurse, and prefix the current property name to sub properties to avoid conflicts
\r
112 AddRawData(propertyValue, rawData, memberInfo.Name + ".", metaModel);
\r
118 /// Creates a "flat view" from a composite object
\r
120 /// <param name="entity"></param>
\r
121 /// <param name="metaModel"></param>
\r
122 /// <returns>a pair of {property name, property value}</returns>
\r
123 protected IDictionary<string, object> GetEntityRawData(object entity, MetaModel metaModel)
\r
125 var rawData = new Dictionary<string, object>();
\r
126 AddRawData(entity, rawData, string.Empty, metaModel);
\r
131 /// Tells if the object notifies a change
\r
133 /// <param name="entity"></param>
\r
134 /// <returns></returns>
\r
135 private static bool IsNotifying(object entity)
\r
137 return entity is INotifyPropertyChanged
\r
138 || entity is INotifyPropertyChanging;
\r
142 /// Start to watch an entity. From here, changes will make IsModified() return true
\r
144 /// <param name="entity"></param>
\r
145 /// <param name="metaModel"></param>
\r
146 public void Register(object entity, MetaModel metaModel)
\r
148 Register(entity, entity, metaModel);
\r
152 /// Start to watch an entity. From here, changes will make IsModified() return true if the entity has changed
\r
153 /// If the entity is already registered, there's no error, but the entity is reset to its original state
\r
155 /// <param name="entity"></param>
\r
156 /// <param name="entityOriginalState"></param>
\r
157 /// <param name="metaModel"></param>
\r
158 public void Register(object entity, object entityOriginalState, MetaModel metaModel)
\r
160 // notifying, we need to wait for changes
\r
161 if (IsNotifying(entity))
\r
163 RegisterNotification(entity, entityOriginalState, metaModel);
\r
165 // raw data, we keep a snapshot of the current state
\r
168 if (!rawDataEntities.ContainsKey(entity) && entityOriginalState != null)
\r
169 rawDataEntities[entity] = GetEntityRawData(entityOriginalState, metaModel);
\r
173 private void RegisterNotification(object entity, object entityOriginalState, MetaModel metaModel)
\r
175 if (modifiedProperties.ContainsKey(entity))
\r
177 modifiedProperties[entity] = null;
\r
179 var entityChanged = entity as INotifyPropertyChanged;
\r
180 if (entityChanged != null)
\r
182 entityChanged.PropertyChanged += OnPropertyChangedEvent;
\r
185 var entityChanging = entity as INotifyPropertyChanging;
\r
186 if (entityChanging != null)
\r
188 entityChanging.PropertyChanging += OnPropertyChangingEvent;
\r
191 // then check all properties, and note them as changed if they already did
\r
192 if (!ReferenceEquals(entity, entityOriginalState)) // only if we specified another original entity
\r
194 foreach (var dataMember in metaModel.GetTable(entity.GetType()).RowType.PersistentDataMembers)
\r
196 var memberInfo = dataMember.Member;
\r
197 if (entityOriginalState == null ||
\r
198 IsPropertyModified(memberInfo.GetMemberValue(entity),
\r
199 memberInfo.GetMemberValue(entityOriginalState)))
\r
201 SetPropertyChanged(entity, memberInfo.Name);
\r
208 /// Occurs on INotifyPropertyChanged.PropertyChanged
\r
210 /// <param name="sender"></param>
\r
211 /// <param name="e"></param>
\r
212 private void OnPropertyChangedEvent(object sender, PropertyChangedEventArgs e)
\r
214 SetPropertyChanged(sender, e.PropertyName);
\r
217 private void OnPropertyChangingEvent(object entity, PropertyChangingEventArgs e)
\r
219 if (modifiedProperties[entity] == null)
\r
220 modifiedProperties[entity] = propertyChangingSentinal;
\r
224 /// Unregisters an entity.
\r
225 /// This is useful when it is switched from update to delete list
\r
227 /// <param name="entity"></param>
\r
228 public void Unregister(object entity)
\r
230 if (IsNotifying(entity))
\r
231 UnregisterNotification(entity);
\r
234 if (rawDataEntities.ContainsKey(entity))
\r
235 rawDataEntities.Remove(entity);
\r
239 private void UnregisterNotification(object entity)
\r
241 if (!modifiedProperties.ContainsKey(entity))
\r
243 modifiedProperties.Remove(entity);
\r
244 INotifyPropertyChanged npc = entity as INotifyPropertyChanged;
\r
247 npc.PropertyChanged -= OnPropertyChangedEvent;
\r
249 var changing = entity as INotifyPropertyChanging;
\r
250 if (changing != null)
\r
252 changing.PropertyChanging -= OnPropertyChangingEvent;
\r
257 /// This method is called when a notifying object sends an event because of a property change
\r
258 /// We may keep track of the precise change in the future
\r
260 /// <param name="entity"></param>
\r
261 /// <param name="propertyName"></param>
\r
262 private void SetPropertyChanged(object entity, string propertyName)
\r
264 PropertyInfo pi = GetProperty(entity, propertyName);
\r
266 throw new ArgumentException("Incorrect property changed");
\r
268 if (modifiedProperties[entity] == null ||
\r
269 ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]))
\r
271 modifiedProperties[entity] = new Dictionary<string, MemberInfo>();
\r
273 modifiedProperties[entity][propertyName] = pi;
\r
277 /// Returns if the entity was modified since it has been Register()ed for the first time
\r
279 /// <param name="entity"></param>
\r
280 /// <param name="metaModel"></param>
\r
281 /// <returns></returns>
\r
282 public bool IsModified(object entity, MetaModel metaModel)
\r
284 // 1. event notifying case (INotify*)
\r
285 if (IsNotifying(entity))
\r
286 return IsNotifyingModified(entity);
\r
289 return IsRawModified(entity, metaModel);
\r
293 /// Determines whether the specified notifiying entity is modified.
\r
295 /// <param name="entity">The entity.</param>
\r
297 /// <c>true</c> if the specified notifiying entity is modified; otherwise, <c>false</c>.
\r
299 private bool IsNotifyingModified(object entity)
\r
301 if (!modifiedProperties.ContainsKey(entity) || modifiedProperties[entity] == null)
\r
303 return ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]) ||
\r
304 modifiedProperties[entity].Count > 0;
\r
308 /// Determines whether the specified property has changed, by comparing its current and previous value.
\r
310 /// <param name="p1">The p1.</param>
\r
311 /// <param name="p2">The p2.</param>
\r
313 /// <c>true</c> if the specified property has changed; otherwise, <c>false</c>.
\r
315 private static bool IsPropertyModified(object p1, object p2)
\r
317 return !Equals(p1, p2);
\r
321 /// Determines whether the specified raw entity has changed.
\r
323 /// <param name="entity">The entity.</param>
\r
324 /// <param name="metaModel">The meta model.</param>
\r
326 /// <c>true</c> if the specified raw entity has changed; otherwise, <c>false</c>.
\r
328 private bool IsRawModified(object entity, MetaModel metaModel)
\r
330 // if not present, maybe it was inserted (or set to dirty)
\r
331 // TODO: this will be useless when we will support the differential properties
\r
332 if (!rawDataEntities.ContainsKey(entity))
\r
335 IDictionary<string, object> originalData = rawDataEntities[entity];
\r
336 IDictionary<string, object> currentData = GetEntityRawData(entity, metaModel);
\r
338 foreach (string key in originalData.Keys)
\r
340 object originalValue = originalData[key];
\r
341 object currentValue = currentData[key];
\r
342 if (IsPropertyModified(originalValue, currentValue))
\r
349 /// Returns a list of all modified properties since last Register/ClearModified
\r
351 /// <param name="entity"></param>
\r
352 /// <param name="metaModel"></param>
\r
353 /// <returns></returns>
\r
354 public IList<MemberInfo> GetModifiedProperties(object entity, MetaModel metaModel)
\r
356 if (IsNotifying(entity))
\r
357 return GetNotifyingModifiedProperties(entity, metaModel);
\r
359 return GetRawModifiedProperties(entity, metaModel);
\r
363 /// Gets all column properties.
\r
365 /// <param name="entity">The entity.</param>
\r
366 /// <param name="metaModel">The meta model.</param>
\r
367 /// <returns></returns>
\r
368 protected IList<MemberInfo> GetAllColumnProperties(object entity, MetaModel metaModel)
\r
370 if (entity == null)
\r
371 throw new ArgumentNullException("entity");
\r
372 var properties = new List<MemberInfo>(GetColumnMembers(entity.GetType(), metaModel));
\r
377 /// Gets the self declaring entity modified properties.
\r
379 /// <param name="entity">The entity.</param>
\r
380 /// <param name="metaModel">The meta model.</param>
\r
381 /// <returns></returns>
\r
382 protected IList<MemberInfo> GetSelfDeclaringModifiedProperties(object entity, MetaModel metaModel)
\r
384 return GetAllColumnProperties(entity, metaModel);
\r
388 /// Gets the notifying entity modified properties.
\r
390 /// <param name="entity">The entity.</param>
\r
391 /// <param name="metaModel">The meta model.</param>
\r
392 /// <returns></returns>
\r
393 protected IList<MemberInfo> GetNotifyingModifiedProperties(object entity, MetaModel metaModel)
\r
395 IDictionary<string, MemberInfo> properties;
\r
396 // if we don't have it, it is fully dirty
\r
397 if (!modifiedProperties.TryGetValue(entity, out properties) ||
\r
398 ReferenceEquals(propertyChangingSentinal, modifiedProperties[entity]))
\r
399 return GetAllColumnProperties(entity, metaModel);
\r
400 return new List<MemberInfo>(properties.Values);
\r
404 /// Gets modified properties for entity, by using raw compare method.
\r
406 /// <param name="entity">The entity.</param>
\r
407 /// <param name="metaModel">The meta model.</param>
\r
408 /// <returns></returns>
\r
409 protected IList<MemberInfo> GetRawModifiedProperties(object entity, MetaModel metaModel)
\r
411 var properties = new List<MemberInfo>();
\r
413 IDictionary<string, object> originalData;
\r
414 // if we don't have this entity we consider all its properties as having been modified
\r
415 if (!rawDataEntities.TryGetValue(entity, out originalData))
\r
416 return GetAllColumnProperties(entity, metaModel);
\r
417 var currentData = GetEntityRawData(entity, metaModel);
\r
419 // otherwise, we iterate and find what's changed
\r
420 foreach (string key in currentData.Keys)
\r
422 var currentValue = currentData[key];
\r
423 var originalValue = originalData[key];
\r
424 if (IsPropertyModified(originalValue, currentValue))
\r
425 properties.Add(GetProperty(entity, key));
\r
432 /// Marks the entity as not dirty.
\r
434 /// <param name="entity"></param>
\r
435 /// <param name="metaModel"></param>
\r
436 public void ClearModified(object entity, MetaModel metaModel)
\r
438 if (IsNotifying(entity))
\r
439 ClearNotifyingModified(entity);
\r
441 ClearRawModified(entity, metaModel);
\r
445 /// Sets the notifying entity as unmodified.
\r
447 /// <param name="entity">The entity.</param>
\r
448 private void ClearNotifyingModified(object entity)
\r
450 modifiedProperties[entity] = null;
\r
454 /// Sets the raw entity as unmodified.
\r
456 /// <param name="entity">The entity.</param>
\r
457 /// <param name="metaModel">The meta model.</param>
\r
458 private void ClearRawModified(object entity, MetaModel metaModel)
\r
460 rawDataEntities[entity] = GetEntityRawData(entity, metaModel);
\r
464 /// Gets the property, given a property name.
\r
466 /// <param name="entity">The entity.</param>
\r
467 /// <param name="propertyName">Name of the property.</param>
\r
468 /// <returns></returns>
\r
469 private static PropertyInfo GetProperty(object entity, string propertyName)
\r
471 return entity.GetType().GetProperty(propertyName);
\r