Skip to content

Commit af83454

Browse files
(GH-678) Add entity validation for the in-memory context
1 parent 68dae67 commit af83454

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

src/DotNetToolkit.Repository.InMemory/Internal/InMemoryRepositoryContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using Configuration;
44
using Configuration.Conventions;
5+
using Configuration.Conventions.Internal;
56
using Extensions;
67
using InMemory.Properties;
78
using Query.Strategies;
@@ -84,6 +85,8 @@ public override void Add<TEntity>(TEntity entity)
8485
{
8586
Guard.NotNull(entity, nameof(entity));
8687

88+
ModelConventionHelper.Validate<TEntity>(entity);
89+
8790
var entityType = typeof(TEntity);
8891
var keyValues = Conventions.GetPrimaryKeyValues(entity);
8992

@@ -110,6 +113,8 @@ public override void Update<TEntity>(TEntity entity)
110113
{
111114
Guard.NotNull(entity, nameof(entity));
112115

116+
ModelConventionHelper.Validate<TEntity>(entity);
117+
113118
var keyValues = Conventions.GetPrimaryKeyValues(entity);
114119

115120
if (!_db.TryFind<TEntity>(keyValues, out object _))
@@ -124,6 +129,8 @@ public override void Remove<TEntity>(TEntity entity)
124129
{
125130
Guard.NotNull(entity, nameof(entity));
126131

132+
ModelConventionHelper.Validate<TEntity>(entity);
133+
127134
var keyValues = Conventions.GetPrimaryKeyValues(entity);
128135

129136
if (!_db.TryFind<TEntity>(keyValues, out object _))

src/DotNetToolkit.Repository/Configuration/Conventions/Internal/ModelConventionHelper.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using JetBrains.Annotations;
66
using System;
77
using System.Collections.Concurrent;
8+
using System.Collections.Generic;
9+
using System.ComponentModel.DataAnnotations;
810
using System.ComponentModel.DataAnnotations.Schema;
911
using System.Linq;
1012
using System.Reflection;
@@ -158,5 +160,36 @@ private static bool IsColumnIdentityCore(IRepositoryConventions conventions, Pro
158160

159161
return databaseGeneratedAttribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity;
160162
}
163+
164+
public static bool TryValidate<T>(T entity, out string[] errors)
165+
{
166+
var entityType = typeof(T);
167+
var validationContext = new ValidationContext(entity, null, null);
168+
var validationResults = new List<ValidationResult>();
169+
170+
if (!Validator.TryValidateObject(entity, validationContext, validationResults, true))
171+
{
172+
// Remove error associated to complex properties
173+
validationResults.RemoveAll(x =>
174+
{
175+
var member = x.MemberNames.First();
176+
var pi = entityType.GetProperty(member);
177+
178+
return pi.IsComplex();
179+
});
180+
}
181+
182+
errors = validationResults.Select(x => x.ErrorMessage).ToArray();
183+
184+
return errors.Length <= 0;
185+
}
186+
187+
public static void Validate<T>(T entity)
188+
{
189+
if (!TryValidate<T>(entity, out var errors))
190+
{
191+
throw new InvalidOperationException(errors[0]);
192+
}
193+
}
161194
}
162195
}

test/DotNetToolkit.Repository.Integration.Test/Data/TestEntity.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,15 @@ public class CustomerAddressWithMultipleAddresses
156156
public CustomerWithMultipleAddresses Customer { get; set; }
157157
}
158158

159+
public class CustomerWithRequiredName
160+
{
161+
[Key]
162+
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
163+
public int Id { get; set; }
164+
[Required]
165+
public string Name { get; set; }
166+
}
167+
159168
public interface IHaveTimeStamp
160169
{
161170
DateTime? CreateTime { get; set; }

test/DotNetToolkit.Repository.Integration.Test/Tests/ContextProvider/RepositoryContextTests.InMemory.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,29 @@ public void CanScoped()
5353
Assert.Equal(1, entity3.Id);
5454
}
5555

56+
[Fact]
57+
public void CanValidateOnCrudOperations()
58+
{
59+
const string requiredNameError = "The Name field is required.";
60+
61+
var options = new RepositoryOptionsBuilder()
62+
.UseInMemoryDatabase(Guid.NewGuid().ToString())
63+
.UseLoggerProvider(TestXUnitLoggerProvider)
64+
.Options;
65+
66+
var repo = new Repository<CustomerWithRequiredName>(options);
67+
var entity = new CustomerWithRequiredName();
68+
69+
var ex = Assert.Throws<InvalidOperationException>(() => repo.Add(entity));
70+
Assert.Equal(requiredNameError, ex.Message);
71+
72+
ex = Assert.Throws<InvalidOperationException>(() => repo.Update(entity));
73+
Assert.Equal(requiredNameError, ex.Message);
74+
75+
ex = Assert.Throws<InvalidOperationException>(() => repo.Delete(entity));
76+
Assert.Equal(requiredNameError, ex.Message);
77+
}
78+
5679
[Fact]
5780
public void CanBeginNullTransactionWhenWarningIgnored()
5881
{

0 commit comments

Comments
 (0)