Skip to content

Commit

Permalink
Added ability to ignore null value for specified properties.
Browse files Browse the repository at this point in the history
Fix exception when null assigned to a nullable value type.
  • Loading branch information
omaxel committed Oct 13, 2017
1 parent 8396a5c commit 4515c99
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 21 deletions.
3 changes: 3 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
1.2.3 - Oct 13, 2017
* Added ability to ignore null value for specified properties.

1.2.2 - Oct 12, 2017
* Added ability to ignore lettere case for properties names.

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,23 @@ DeltaConfig.Init((cfg) =>
{
cfg.IgnoreLetterCase();
});
```

#### Ignore null value for specified properties
You can ignore null value for specified properties of an entity.

This is particularly useful in two cases:

- when your property is a value type (like `int` and `DateTime`) and your client still send a null value for that property. Ignoring null value will avoid exception.
- when your property is a reference type (which allows null) but you don't want that `null` overwrites your previous stored data.

**Global.asax** or **Startup.cs**
```
DeltaConfig.Init(cfg =>
{
cfg.IgnoreNullValue<MyClass>(x => x.Date);
// Multiple properties
// cfg.IgnoreNullValue<MyClass>(x => x.Date1, x => x.Date2);
});
```
41 changes: 25 additions & 16 deletions src/SimplePatch/Delta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Delta() : base()
{
typeFullName = typeof(TEntity).FullName;

DeltaCache.entityProperties.TryAdd(typeFullName, typeof(TEntity).GetTypeInfo().DeclaredProperties.Where(x => x.GetMethod.IsPublic && x.SetMethod.IsPublic && x.CanRead && x.CanWrite));
DeltaCache.entityProperties.TryAdd(typeFullName, TypeHelper.GetEntityProperties<TEntity>());
}

/// <summary>
Expand Down Expand Up @@ -187,22 +187,41 @@ private TEntity SetPropertiesValue(TEntity entity)
{
foreach (var prop in properties)
{
if (ContainsKey(prop.Name) && !IsExcludedProperty(typeFullName, prop.Name))
var propertyInfo = prop.PropertyInfo;
if (ContainsKey(propertyInfo.Name) && !IsExcludedProperty(typeFullName, propertyInfo.Name))
{
var propertyType = GetTrueType(prop.PropertyType);
var newPropertyValue = this[prop.Name];
var propertyType = TypeHelper.GetTrueType(propertyInfo.PropertyType);
var newPropertyValue = this[propertyInfo.Name];


//Check for null value before getting type of new value
if (newPropertyValue == null)
{
if (prop.IgnoreNullValue) continue;

//Check if destination property allows null value
if (TypeHelper.IsNullable(propertyType))
{
propertyInfo.SetValue(entity, null, null);
continue;
}
else
{
throw new Exception($"Null value not allowed for '{propertyInfo.Name}' property of '{typeFullName}'");
}
}

var newPropertyValueType = newPropertyValue.GetType();

//Guid from string
if (propertyType == typeof(Guid) && newPropertyValueType == typeof(string))
{
newPropertyValue = new Guid((string)newPropertyValue);
prop.SetValue(entity, newPropertyValue, null);
propertyInfo.SetValue(entity, newPropertyValue, null);
}
else
{
prop.SetValue(entity, Convert.ChangeType(newPropertyValue, propertyType), null);
propertyInfo.SetValue(entity, Convert.ChangeType(newPropertyValue, propertyType), null);
}
}
}
Expand All @@ -226,16 +245,6 @@ private bool IsExcludedProperty(string typeFullName, string propertyName)
return false;
}

/// <summary>
/// Returns the type specified by the parameter or type below if <paramref name="type" /> is <see cref="Nullable" />.
/// </summary>
/// <param name="type">The type to be verified.</param>
/// <returns>The type specified by the parameter or type below if <paramref name="type" /> is <see cref="Nullable" />.</returns>
private Type GetTrueType(Type type)
{
return Nullable.GetUnderlyingType(type) ?? type;
}

#endregion

#region Implementing the IDictionary Interface
Expand Down
2 changes: 1 addition & 1 deletion src/SimplePatch/DeltaCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace SimplePatch
{
internal static class DeltaCache
{
public static ConcurrentDictionary<string, IEnumerable<PropertyInfo>> entityProperties = new ConcurrentDictionary<string, IEnumerable<PropertyInfo>>();
public static ConcurrentDictionary<string, IEnumerable<DeltaInfo>> entityProperties = new ConcurrentDictionary<string, IEnumerable<DeltaInfo>>();
public static ConcurrentDictionary<string, string[]> excludedProperties = new ConcurrentDictionary<string, string[]>();
}
}
26 changes: 24 additions & 2 deletions src/SimplePatch/DeltaConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;

namespace SimplePatch
{
Expand Down Expand Up @@ -33,9 +34,30 @@ public Config ExcludeProperties<T>(params Expression<Func<T, object>>[] properti
propList.Add(propertyInfo.Name);
}

string typeFullname = typeof(T).FullName;
DeltaCache.excludedProperties.TryAdd(type.FullName, propList.ToArray());

DeltaCache.excludedProperties.TryAdd(typeFullname, propList.ToArray());
return this;
}

/// <summary>
/// Specifies properties for whose null value will be ignored.
/// </summary>
/// <typeparam name="T">Class in which the property is contained.</typeparam>
/// <param name="properties">Properties for whose null value will be ignored.</param>
/// <returns></returns>
public Config IgnoreNullValue<T>(params Expression<Func<T, object>>[] properties)
{
var type = typeof(T);

var propList = TypeHelper.GetEntityProperties<T>().ToList();
foreach (var prop in properties)
{
var propertyInfo = GetMemberExpression(prop).Member as PropertyInfo;

propList.First(x => x.Name == propertyInfo.Name).IgnoreNullValue = true;
}

DeltaCache.entityProperties.TryAdd(type.FullName, propList);

return this;
}
Expand Down
17 changes: 17 additions & 0 deletions src/SimplePatch/DeltaInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Reflection;

namespace SimplePatch
{
internal class DeltaInfo
{
public bool IgnoreNullValue { get; set; } = false;
public PropertyInfo PropertyInfo { get; set; }

public string Name { get => PropertyInfo.Name; }

public DeltaInfo(PropertyInfo propertyInfo)
{
PropertyInfo = propertyInfo;
}
}
}
6 changes: 4 additions & 2 deletions src/SimplePatch/SimplePatch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@

<PropertyGroup>
<TargetFrameworks>net451;netstandard1.4</TargetFrameworks>
<Version>1.2.2</Version>
<Version>1.2.3</Version>
<Authors>Omar Muscatello</Authors>
<Company>Omar Muscatello</Company>
<Description>A simple library for partial entity changes in ASP.NET and ASP.NET Core.</Description>
<Copyright>Copyright (c) Omar Muscatello 2017</Copyright>
<PackageLicenseUrl>https://github.com/OmarMuscatello/SimplePatch/blob/master/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>Added ability to ignore lettere case for properties names.</PackageReleaseNotes>
<PackageReleaseNotes>Added ability to ignore null value for specified properties</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/OmarMuscatello/SimplePatch</PackageProjectUrl>
<RepositoryUrl>https://github.com/OmarMuscatello/SimplePatch</RepositoryUrl>
<PackageIconUrl>http://raw.github.com/OmarMuscatello/SimplePatch/master/simplepatch-icon.png</PackageIconUrl>
<RepositoryType></RepositoryType>
<PackageTags>web-api patch entity-framework update asp-net asp-net-core</PackageTags>
<ApplicationIcon></ApplicationIcon>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>1.2.3.0</AssemblyVersion>
<FileVersion>1.2.3.0</FileVersion>
</PropertyGroup>

</Project>
37 changes: 37 additions & 0 deletions src/SimplePatch/TypeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace SimplePatch
{
internal class TypeHelper
{
public static IEnumerable<DeltaInfo> GetEntityProperties<TEntity>()
{
return typeof(TEntity).GetTypeInfo().DeclaredProperties.Where(x => x.GetMethod.IsPublic && x.SetMethod.IsPublic && x.CanRead && x.CanWrite).Select(x => new DeltaInfo(x));
}

/// <summary>
/// Returns the type specified by the parameter or type below if <paramref name="type" /> is <see cref="Nullable" />.
/// </summary>
/// <param name="type">The type to be verified.</param>
/// <returns>The type specified by the parameter or type below if <paramref name="type" /> is <see cref="Nullable" />.</returns>
public static Type GetTrueType(Type type)
{
return Nullable.GetUnderlyingType(type) ?? type;
}

/// <summary>
/// Indicates whether the specified type accepts null values.
/// </summary>
/// <param name="type">Type to check.</param>
/// <returns>True if the specified type accepts null values, otherwise false.</returns>
public static bool IsNullable(Type type)
{
if (!type.GetTypeInfo().IsValueType) return true; // ref-type
if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
return false;
}
}
}

0 comments on commit 4515c99

Please sign in to comment.