上篇提到 Model Validation,在 EF 裡面,也是可以吃的到 ValidationAttribute,當調用 SaveChanegs 就會進行 Model 的檢查,當需要把檢查機制寫在 EF 的時候,就可以利用此招
情境一:
DataTime的最小值為西元1年1月1日,這對 SQL Server 的 DateTime型別是非法數值,但對 .NET 來講合法,在開發的過程常常會碰到這種錯誤訊息,這是因為沒有處理到日期欄位,EF 也沒幫我們檢查出來,丟給 SQL Server 後就爆掉了
System.Data.SqlClient.SqlException: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.
為此我想要檢查 Entity Model 的每一個日期欄位都有塞值,沒有塞到的就回報我是哪一個欄位有問題
情境二:
新增資料時確保 Log資料有新增
實作
Entity Model 如下:
[Table("Member")] internal class Member { public Member() { this.Logs = new HashSet<MemberLog>(); } [Key] public Guid Id { get; set; } [StringLength(100)] public string Name { get; set; } public DateTime CreateAt { get; set; } public virtual ICollection<MemberLog> Logs { get; set; } }
internal class MemberLog { [Key] public Guid Id { get; set; } [ForeignKey("Member")] public Guid Memebr_Id { get; set; } [StringLength(100)] public string Name { get; set; } public virtual Member Member { get; set; } }
複寫 DbContext.ValidateEntity
internal class ValidationDbContext : DbContext { protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var result = base.ValidateEntity(entityEntry, items); //TODO:Custom Validation return result; } }
驗證最小日期
把資料的驗證放在這個方法裡面,這裡要做的事很簡單,就是檢查所有的日期欄位
private void ValidateMinDateTime(DbEntityValidationResult result) { var entityEntry = result.Entry; var entityType = entityEntry.Entity.GetType(); var datePropertyInfos = GetDatePropertyInfos(entityType); result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>()); foreach (var datePropertyInfo in datePropertyInfos) { var name = datePropertyInfo.Name; var value = (DateTime) datePropertyInfo.GetValue(entityEntry.Entity, null); if (value == DateTime.MinValue) { result.ValidationErrors.Add(new DbValidationError(name, $"Not support {value} data")); } } } private static IEnumerable<PropertyInfo> GetDatePropertyInfos(Type entityType) { List<PropertyInfo> results = null; if (s_datePropertyInfo.ContainsKey(entityType)) { results = s_datePropertyInfo[entityType].ToList(); } else { results = new List<PropertyInfo>(); var propertyInfos = entityType.GetProperties(); foreach (var propertyInfo in propertyInfos) { if (IsDateTime(propertyInfo.PropertyType)) { results.Add(propertyInfo); } } s_datePropertyInfo.TryAdd(entityType, results.ToArray()); } return results; } private static bool IsDateTime(Type sourceType) { var result = false; result = sourceType == typeof(DateTime) || sourceType == typeof(DateTime?); return result;
驗證的執行順序
先執行自訂驗證再執行基本驗證
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>()); this.ValidateMinDateTime(result); if (!result.IsValid) { return result; } return base.ValidateEntity(entityEntry, items); }
先執行基本驗證再執行自訂驗證
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var result = base.ValidateEntity(entityEntry, items); if (result.IsValid) { this.ValidateMinDateTime(result); } return result; }
或是兩者一起執行
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>()); this.ValidateMinDateTime(result); base.ValidateEntity(entityEntry, items); return result; }
以目前情境,個人比較偏好先執行基本驗證。
驗證是否有Log
再來看個例子,異動資料時,若 Logs 欄位沒有資料,則驗證失敗
private void ValidateHasLogIfChangeMode(DbEntityValidationResult result) { var propertyName = "Logs"; var entityEntry = result.Entry; var entityType = entityEntry.Entity.GetType(); var logPropertyInfo = entityType.GetProperty(propertyName); if (logPropertyInfo == null) { return; } var logs = (IEnumerable<object>) logPropertyInfo.GetValue(entityEntry.Entity, null); var state = entityEntry.State; if ((state == EntityState.Added || state == EntityState.Modified || state == EntityState.Deleted) & logs.Count() == 0) { result.ValidationErrors.Add(new DbValidationError(propertyName, $"New {entityType.Name} must have a log.")); } }
完成後的 ValidationEntiry 方法如下
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var result = base.ValidateEntity(entityEntry, items); if (result.IsValid) { this.ValidateHasLogIfChangeMode(result); } if (result.IsValid) { this.ValidateMinDateTime(result); } return result; }
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET