|  |  | 1 |  | using System; | 
|  |  | 2 |  | using System.IO; | 
|  |  | 3 |  | using System.Collections.Generic; | 
|  |  | 4 |  | using System.Linq; | 
|  |  | 5 |  | using Microsoft.Xrm.Sdk; | 
|  |  | 6 |  | using Microsoft.Xrm.Sdk.Messages; | 
|  |  | 7 |  |  | 
|  |  | 8 |  | namespace Xrm.Oss.UnitOfWork | 
|  |  | 9 |  | { | 
|  |  | 10 |  |     public partial class UpdateContext<T> : IDisposable where T : Entity | 
|  |  | 11 |  |     { | 
|  |  | 12 |  |         private T _initialState; | 
|  |  | 13 |  |         private T _workingState; | 
|  |  | 14 |  |         private bool _disposed; | 
|  |  | 15 |  |  | 
|  |  | 16 |  |         /// <summary> | 
|  |  | 17 |  |         /// Clone all attribute types defined in https://msdn.microsoft.com/de-de/library/gg328507(v=crm.6).aspx | 
|  |  | 18 |  |         /// </summary> | 
|  |  | 19 |  |         /// <param name="value"></param> | 
|  |  | 20 |  |         /// <returns></returns> | 
|  |  | 21 |  |         private object CloneAttribute(object value) | 
|  | 24 | 22 |  |         { | 
|  | 24 | 23 |  |             if (value == null) | 
|  | 1 | 24 |  |             { | 
|  | 1 | 25 |  |                 return null; | 
|  |  | 26 |  |             } | 
|  |  | 27 |  |  | 
|  |  | 28 |  |             // Reference Type | 
|  | 23 | 29 |  |             var stringValue = value as string; | 
|  | 23 | 30 |  |             if (stringValue != null) | 
|  | 10 | 31 |  |             { | 
|  | 10 | 32 |  |                 return new string(stringValue.ToCharArray()); | 
|  |  | 33 |  |             } | 
|  |  | 34 |  |  | 
|  |  | 35 |  |             // Reference Type | 
|  | 13 | 36 |  |             var optionSetValue = value as OptionSetValue; | 
|  | 13 | 37 |  |             if (optionSetValue != null) | 
|  | 3 | 38 |  |             { | 
|  | 3 | 39 |  |                 return new OptionSetValue(optionSetValue.Value); | 
|  |  | 40 |  |             } | 
|  |  | 41 |  |  | 
|  |  | 42 |  |             // Reference Type | 
|  | 10 | 43 |  |             var entityReferenceValue = value as EntityReference; | 
|  | 10 | 44 |  |             if (entityReferenceValue != null) | 
|  | 0 | 45 |  |             { | 
|  | 0 | 46 |  |                 return new EntityReference | 
|  | 0 | 47 |  |                 { | 
|  | 0 | 48 |  |                     LogicalName = CloneAttribute(entityReferenceValue.LogicalName) as string, | 
|  | 0 | 49 |  |                     Id = entityReferenceValue.Id, | 
|  | 0 | 50 |  |                     Name = CloneAttribute(entityReferenceValue.Name) as string | 
|  | 0 | 51 |  |                 }; | 
|  |  | 52 |  |             } | 
|  |  | 53 |  |  | 
|  |  | 54 |  |             // Reference Type | 
|  | 10 | 55 |  |             var booleanManagedValue = value as BooleanManagedProperty; | 
|  | 10 | 56 |  |             if (booleanManagedValue != null) | 
|  | 0 | 57 |  |             { | 
|  | 0 | 58 |  |                 return new BooleanManagedProperty(booleanManagedValue.Value); | 
|  |  | 59 |  |             } | 
|  |  | 60 |  |  | 
|  |  | 61 |  |             // Reference Type | 
|  | 10 | 62 |  |             var moneyValue = value as Money; | 
|  | 10 | 63 |  |             if (moneyValue != null) | 
|  | 5 | 64 |  |             { | 
|  | 5 | 65 |  |                 return new Money(moneyValue.Value); | 
|  |  | 66 |  |             } | 
|  |  | 67 |  |  | 
|  |  | 68 |  |             // Reference Type | 
|  | 5 | 69 |  |             var aliasedValue = value as AliasedValue; | 
|  | 5 | 70 |  |             if (aliasedValue != null) | 
|  | 1 | 71 |  |             { | 
|  | 1 | 72 |  |                 return new AliasedValue(CloneAttribute(aliasedValue.EntityLogicalName) as string, | 
|  | 1 | 73 |  |                     CloneAttribute(aliasedValue.AttributeLogicalName) as string, | 
|  | 1 | 74 |  |                     CloneAttribute(aliasedValue.Value)); | 
|  |  | 75 |  |             } | 
|  |  | 76 |  |  | 
|  |  | 77 |  |             // Clone all contained records | 
|  | 4 | 78 |  |             var entityCollectionValue = value as EntityCollection; | 
|  | 4 | 79 |  |             if (entityCollectionValue != null) | 
|  | 0 | 80 |  |             { | 
|  | 0 | 81 |  |                 return new EntityCollection(entityCollectionValue.Entities.Select(CloneEntity).ToList()); | 
|  |  | 82 |  |             } | 
|  |  | 83 |  |  | 
|  | 4 | 84 |  |             var valueTypes = new List<Type> | 
|  | 4 | 85 |  |             { | 
|  | 4 | 86 |  |                 typeof(long), typeof(bool), typeof(DateTime), | 
|  | 4 | 87 |  |                 typeof(decimal), typeof(double), typeof(int), | 
|  | 4 | 88 |  |                 typeof(Guid), typeof(float), typeof(byte), typeof(Enum) | 
|  | 4 | 89 |  |             }; | 
|  |  | 90 |  |  | 
|  | 4 | 91 |  |             var type = value.GetType(); | 
|  |  | 92 |  |  | 
|  | 4 | 93 |  |             if (valueTypes.Contains(type)) | 
|  | 3 | 94 |  |             { | 
|  | 3 | 95 |  |                 return value; | 
|  |  | 96 |  |             } | 
|  |  | 97 |  |  | 
|  | 1 | 98 |  |             throw new InvalidDataException("Attribute of type '" + type.Name + "' is not supported yet. Please file an i | 
|  | 23 | 99 |  |         } | 
|  |  | 100 |  |  | 
|  |  | 101 |  |         /// <summary> | 
|  |  | 102 |  |         /// Clones entity that is passed in by serializing and deserializing using JSON | 
|  |  | 103 |  |         /// </summary> | 
|  |  | 104 |  |         /// <param name="entity">Entity to clone</param> | 
|  |  | 105 |  |         /// <returns>Deep Copied clone of passed in entity</returns> | 
|  |  | 106 |  |         private Entity CloneEntity(Entity entity) | 
|  | 25 | 107 |  |         { | 
|  | 25 | 108 |  |             if (entity == null) | 
|  | 0 | 109 |  |             { | 
|  | 0 | 110 |  |                 return null; | 
|  |  | 111 |  |             } | 
|  |  | 112 |  |  | 
|  | 25 | 113 |  |             var clone = new Entity | 
|  | 25 | 114 |  |             { | 
|  | 25 | 115 |  |                 Id = entity.Id, | 
|  | 25 | 116 |  |                 LogicalName = entity.LogicalName | 
|  | 25 | 117 |  |             }; | 
|  |  | 118 |  |  | 
|  | 116 | 119 |  |             foreach (var attribute in entity.Attributes) | 
|  | 21 | 120 |  |             { | 
|  | 21 | 121 |  |                 clone[attribute.Key] = CloneAttribute(attribute.Value); | 
|  | 20 | 122 |  |             } | 
|  |  | 123 |  |  | 
|  | 24 | 124 |  |             return clone; | 
|  | 24 | 125 |  |         } | 
|  |  | 126 |  |  | 
|  |  | 127 |  |         /// <summary> | 
|  |  | 128 |  |         /// Creates a new context that tracks changes to the passed in entity | 
|  |  | 129 |  |         /// </summary> | 
|  |  | 130 |  |         /// <param name="entity">Entity to track changes for</param> | 
|  | 32 | 131 |  |         public UpdateContext(T entity) { | 
|  | 16 | 132 |  |             if (entity == null) | 
|  | 1 | 133 |  |             { | 
|  | 1 | 134 |  |                 throw new InvalidOperationException("You must initialize the context using a valid entity, but was null" | 
|  |  | 135 |  |             } | 
|  |  | 136 |  |  | 
|  | 15 | 137 |  |             _workingState = entity; | 
|  | 15 | 138 |  |             _initialState = CloneEntity(entity).ToEntity<T>(); | 
|  | 14 | 139 |  |         } | 
|  |  | 140 |  |  | 
|  |  | 141 |  |         /// <summary> | 
|  |  | 142 |  |         /// Returns an update object if tracked entity properties were changed, null otherwise | 
|  |  | 143 |  |         /// </summary> | 
|  |  | 144 |  |         /// <returns>Update object of same type and with same ID, but only containing attributes that changed since cont | 
|  |  | 145 |  |         public T GetUpdateObject() | 
|  | 15 | 146 |  |         { | 
|  | 15 | 147 |  |             var update = new Entity | 
|  | 15 | 148 |  |             { | 
|  | 15 | 149 |  |                 Id = _initialState.Id, | 
|  | 15 | 150 |  |                 LogicalName = _initialState.LogicalName | 
|  | 15 | 151 |  |             }; | 
|  |  | 152 |  |  | 
|  | 77 | 153 |  |             foreach (var property in _workingState.Attributes) | 
|  | 16 | 154 |  |             { | 
|  | 16 | 155 |  |                 var key = property.Key; | 
|  | 16 | 156 |  |                 var value = property.Value; | 
|  |  | 157 |  |  | 
|  |  | 158 |  |                 // We can't update aliased values, so skip them | 
|  | 16 | 159 |  |                 if (value is AliasedValue) | 
|  | 2 | 160 |  |                 { | 
|  | 2 | 161 |  |                     continue; | 
|  |  | 162 |  |                 } | 
|  |  | 163 |  |  | 
|  |  | 164 |  |                 // Handle newly added attributes | 
|  | 14 | 165 |  |                 if (!_initialState.Attributes.ContainsKey(key)) | 
|  | 5 | 166 |  |                 { | 
|  |  | 167 |  |                     // Null valued attributes will not be set in records retrieved using the IOrganizationService | 
|  |  | 168 |  |                     // Therefore if attributes are not in the initial state and set null, this should be treated as noop | 
|  |  | 169 |  |                     // When a string is cleared on a form, the plugin target will not contain a null value for the attri | 
|  | 5 | 170 |  |                     if (value == null || (value is string && string.IsNullOrEmpty(value as string))) | 
|  | 0 | 171 |  |                     { | 
|  | 0 | 172 |  |                         continue; | 
|  |  | 173 |  |                     } | 
|  |  | 174 |  |  | 
|  | 5 | 175 |  |                     update[key] = value; | 
|  | 5 | 176 |  |                 } | 
|  |  | 177 |  |                 // Handle changed attributes | 
|  | 9 | 178 |  |                 else if (!object.Equals(_initialState[key], _workingState[key])) | 
|  | 7 | 179 |  |                 { | 
|  | 7 | 180 |  |                     update[key] = value; | 
|  | 7 | 181 |  |                 } | 
|  | 14 | 182 |  |             } | 
|  |  | 183 |  |  | 
|  | 15 | 184 |  |             if (update.Attributes.Count == 0) | 
|  | 5 | 185 |  |             { | 
|  | 5 | 186 |  |                 return null; | 
|  |  | 187 |  |             } | 
|  |  | 188 |  |  | 
|  |  | 189 |  |             // Return "snapshot" of update object to not include updates that occured on working state after update was  | 
|  | 10 | 190 |  |             return CloneEntity(update).ToEntity<T>(); | 
|  | 15 | 191 |  |         } | 
|  |  | 192 |  |  | 
|  |  | 193 |  |         /// <summary> | 
|  |  | 194 |  |         /// Sends update with changed attributes only using supplied OrganizationService | 
|  |  | 195 |  |         /// </summary> | 
|  |  | 196 |  |         /// <param name="service">Organization Service instance to use for sending update</param> | 
|  |  | 197 |  |         /// <returns>True if changes found and update send, false otherwise</returns> | 
|  |  | 198 |  |         public bool Update(IOrganizationService service) | 
|  | 2 | 199 |  |         { | 
|  | 2 | 200 |  |             var update = GetUpdateObject(); | 
|  |  | 201 |  |  | 
|  | 2 | 202 |  |             if (update != null) | 
|  | 1 | 203 |  |             { | 
|  | 1 | 204 |  |                 service.Update(update); | 
|  | 1 | 205 |  |                 return true; | 
|  |  | 206 |  |             } | 
|  |  | 207 |  |  | 
|  | 1 | 208 |  |             return false; | 
|  | 2 | 209 |  |         } | 
|  |  | 210 |  |  | 
|  |  | 211 |  |         /// <summary> | 
|  |  | 212 |  |         /// Returns an update request for use in transactions or otherwhere | 
|  |  | 213 |  |         /// </summary> | 
|  |  | 214 |  |         /// <returns>Update request with target set to update object, or null if nothing changed</returns> | 
|  |  | 215 |  |         public UpdateRequest GetUpdateRequest() | 
|  | 1 | 216 |  |         { | 
|  | 1 | 217 |  |             var update = GetUpdateObject(); | 
|  |  | 218 |  |  | 
|  | 1 | 219 |  |             if (update != null) | 
|  | 1 | 220 |  |             { | 
|  | 1 | 221 |  |                 return new UpdateRequest | 
|  | 1 | 222 |  |                 { | 
|  | 1 | 223 |  |                     Target = update | 
|  | 1 | 224 |  |                 }; | 
|  |  | 225 |  |             } | 
|  |  | 226 |  |  | 
|  | 0 | 227 |  |             return null; | 
|  | 1 | 228 |  |         } | 
|  |  | 229 |  |  | 
|  |  | 230 |  |         public void Dispose() | 
|  | 14 | 231 |  |         { | 
|  | 14 | 232 |  |             Dispose(true); | 
|  | 14 | 233 |  |             GC.SuppressFinalize(this); | 
|  | 14 | 234 |  |         } | 
|  |  | 235 |  |  | 
|  |  | 236 |  |         protected virtual void Dispose(bool disposing) | 
|  | 14 | 237 |  |         { | 
|  | 14 | 238 |  |             if (_disposed) | 
|  | 0 | 239 |  |             { | 
|  | 0 | 240 |  |                 return; | 
|  |  | 241 |  |             } | 
|  |  | 242 |  |  | 
|  | 14 | 243 |  |             if (disposing) | 
|  | 14 | 244 |  |             { | 
|  |  | 245 |  |                 // Free any managed objects here. | 
|  | 14 | 246 |  |                 _initialState = null; | 
|  | 14 | 247 |  |             } | 
|  |  | 248 |  |  | 
|  |  | 249 |  |             // Free any unmanaged objects here. | 
|  | 14 | 250 |  |             _disposed = true; | 
|  | 14 | 251 |  |         } | 
|  |  | 252 |  |     } | 
|  |  | 253 |  | } |