Summary

Class:Xrm.Oss.UnitOfWork.UpdateContext`1
Assembly:Xrm.Oss.UnitOfWork
File(s):C:\Users\Florian\Entwicklung\Xrm-Update-Context\src\lib\Xrm.Oss.UnitOfWork\UpdateContext.cs
Covered lines:117
Uncovered lines:18
Coverable lines:135
Total lines:253
Line coverage:86.6%
Branch coverage:80%

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
CloneAttribute(...)1051284.6284.21
CloneEntity(...)4285.7166.67
.ctor(...)22100100
GetUpdateObject()113293.1090.91
Update(...)22100100
GetUpdateRequest()2285.7166.67
Dispose()10100100
Dispose(...)348060

File(s)

C:\Users\Florian\Entwicklung\Xrm-Update-Context\src\lib\Xrm.Oss.UnitOfWork\UpdateContext.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3using System.Collections.Generic;
 4using System.Linq;
 5using Microsoft.Xrm.Sdk;
 6using Microsoft.Xrm.Sdk.Messages;
 7
 8namespace 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)
 2422        {
 2423            if (value == null)
 124            {
 125                return null;
 26            }
 27
 28            // Reference Type
 2329            var stringValue = value as string;
 2330            if (stringValue != null)
 1031            {
 1032                return new string(stringValue.ToCharArray());
 33            }
 34
 35            // Reference Type
 1336            var optionSetValue = value as OptionSetValue;
 1337            if (optionSetValue != null)
 338            {
 339                return new OptionSetValue(optionSetValue.Value);
 40            }
 41
 42            // Reference Type
 1043            var entityReferenceValue = value as EntityReference;
 1044            if (entityReferenceValue != null)
 045            {
 046                return new EntityReference
 047                {
 048                    LogicalName = CloneAttribute(entityReferenceValue.LogicalName) as string,
 049                    Id = entityReferenceValue.Id,
 050                    Name = CloneAttribute(entityReferenceValue.Name) as string
 051                };
 52            }
 53
 54            // Reference Type
 1055            var booleanManagedValue = value as BooleanManagedProperty;
 1056            if (booleanManagedValue != null)
 057            {
 058                return new BooleanManagedProperty(booleanManagedValue.Value);
 59            }
 60
 61            // Reference Type
 1062            var moneyValue = value as Money;
 1063            if (moneyValue != null)
 564            {
 565                return new Money(moneyValue.Value);
 66            }
 67
 68            // Reference Type
 569            var aliasedValue = value as AliasedValue;
 570            if (aliasedValue != null)
 171            {
 172                return new AliasedValue(CloneAttribute(aliasedValue.EntityLogicalName) as string,
 173                    CloneAttribute(aliasedValue.AttributeLogicalName) as string,
 174                    CloneAttribute(aliasedValue.Value));
 75            }
 76
 77            // Clone all contained records
 478            var entityCollectionValue = value as EntityCollection;
 479            if (entityCollectionValue != null)
 080            {
 081                return new EntityCollection(entityCollectionValue.Entities.Select(CloneEntity).ToList());
 82            }
 83
 484            var valueTypes = new List<Type>
 485            {
 486                typeof(long), typeof(bool), typeof(DateTime),
 487                typeof(decimal), typeof(double), typeof(int),
 488                typeof(Guid), typeof(float), typeof(byte), typeof(Enum)
 489            };
 90
 491            var type = value.GetType();
 92
 493            if (valueTypes.Contains(type))
 394            {
 395                return value;
 96            }
 97
 198            throw new InvalidDataException("Attribute of type '" + type.Name + "' is not supported yet. Please file an i
 2399        }
 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)
 25107        {
 25108            if (entity == null)
 0109            {
 0110                return null;
 111            }
 112
 25113            var clone = new Entity
 25114            {
 25115                Id = entity.Id,
 25116                LogicalName = entity.LogicalName
 25117            };
 118
 116119            foreach (var attribute in entity.Attributes)
 21120            {
 21121                clone[attribute.Key] = CloneAttribute(attribute.Value);
 20122            }
 123
 24124            return clone;
 24125        }
 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>
 32131        public UpdateContext(T entity) {
 16132            if (entity == null)
 1133            {
 1134                throw new InvalidOperationException("You must initialize the context using a valid entity, but was null"
 135            }
 136
 15137            _workingState = entity;
 15138            _initialState = CloneEntity(entity).ToEntity<T>();
 14139        }
 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()
 15146        {
 15147            var update = new Entity
 15148            {
 15149                Id = _initialState.Id,
 15150                LogicalName = _initialState.LogicalName
 15151            };
 152
 77153            foreach (var property in _workingState.Attributes)
 16154            {
 16155                var key = property.Key;
 16156                var value = property.Value;
 157
 158                // We can't update aliased values, so skip them
 16159                if (value is AliasedValue)
 2160                {
 2161                    continue;
 162                }
 163
 164                // Handle newly added attributes
 14165                if (!_initialState.Attributes.ContainsKey(key))
 5166                {
 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
 5170                    if (value == null || (value is string && string.IsNullOrEmpty(value as string)))
 0171                    {
 0172                        continue;
 173                    }
 174
 5175                    update[key] = value;
 5176                }
 177                // Handle changed attributes
 9178                else if (!object.Equals(_initialState[key], _workingState[key]))
 7179                {
 7180                    update[key] = value;
 7181                }
 14182            }
 183
 15184            if (update.Attributes.Count == 0)
 5185            {
 5186                return null;
 187            }
 188
 189            // Return "snapshot" of update object to not include updates that occured on working state after update was 
 10190            return CloneEntity(update).ToEntity<T>();
 15191        }
 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)
 2199        {
 2200            var update = GetUpdateObject();
 201
 2202            if (update != null)
 1203            {
 1204                service.Update(update);
 1205                return true;
 206            }
 207
 1208            return false;
 2209        }
 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()
 1216        {
 1217            var update = GetUpdateObject();
 218
 1219            if (update != null)
 1220            {
 1221                return new UpdateRequest
 1222                {
 1223                    Target = update
 1224                };
 225            }
 226
 0227            return null;
 1228        }
 229
 230        public void Dispose()
 14231        {
 14232            Dispose(true);
 14233            GC.SuppressFinalize(this);
 14234        }
 235
 236        protected virtual void Dispose(bool disposing)
 14237        {
 14238            if (_disposed)
 0239            {
 0240                return;
 241            }
 242
 14243            if (disposing)
 14244            {
 245                // Free any managed objects here.
 14246                _initialState = null;
 14247            }
 248
 249            // Free any unmanaged objects here.
 14250            _disposed = true;
 14251        }
 252    }
 253}