白話Entity Framework Core數據驗證
原創【51CTO.com原創稿件】數據驗證是每個項目必須存在的,可以防止不符合系統規范的數據進入系統進而導致系統不穩定甚至崩潰。我們可以自己編寫代碼(包括前臺和后臺代碼)進行驗證,但是這樣一方面代碼量較大,另一方面有可能驗證代碼覆蓋不完全。但是在 Entity Framework Core (以下簡稱 EF Core )中這些問題全可以解決。在 EF Core 中有兩種驗證模式,分別是內置模型驗證和第三方擴展模型驗證。下面我分別對這兩種模式進行講解,在講解前我們先來創建必須的模型。
- public class User
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public int Age { get; set; }
- }
一、內置模型驗證
在 EF Core 中并沒有 Fluent API 模式對數據進行驗證,因此我們只能通過 Data Annotations (數據注解)方式來進行數據驗證,也就是添加特性的方法來驗證數據。例如我們要驗證 User 模型中的 Name 的長度,Name 長度不能大于 5 ,我們只需在 Name 屬性上增加 StringLength 數據注解即可, StringLength 位于命名空間 System.ComponentModel.DataAnnotations 中,修改 User 模型代碼如下:
- public class User
- {
- public int Id { get; set; }
- [StringLength(5)]
- public string Name { get; set; }
- public int Age { get; set; }
- }
上述代碼通過 StringLength(5) 數據注解將 Name 屬性的數據長度限定在 5 ,并且在數據提交時按照這個約定進行數據驗證。下面我們就通過數據注解中的驗證器來驗證剛才添加的特性。首先我們要創建一個上下文的擴展方法:
- public static List<ValidationResult> ExecuteValidation(DbContext context)
- {
- List<ValidationResult> result = new List<ValidationResult>();
- var models = context.ChangeTracker.Entries()
- .Where(p =>
- (p.State == EntityState.Added) || (p.State == EntityState.Modified));
- foreach (var model in models)
- {
- var entity = model.Entity;
- var valProvider = new ValidationDbContextServiceProvider(context);
- var valContext = new ValidationContext(entity, valProvider, null);
- List<ValidationResult> error = new List<ValidationResult>();
- if(!Validator.TryValidateObject(entity,valContext,error,true))
- {
- result.AddRange(error);
- }
- return result.ToList();
- }
- }
在上述代碼中我們通過 ChangeTracker 方法找出被追蹤的實體,然后過濾出需要添加和更新的實體,對這些實體進行數據驗證。最后我們通過 Validator 中的 TryValidateObject 方法驗證實體數據并返回校驗錯誤信息。在業務代碼中我們調用前面定義的 ExecuteValidation 方法進行驗證,如果驗證通過就調用 EF Core 的 SaveChange() 方法,如果未通過就調用相應的處理代碼,代碼片段如下:
- if(context.ExecuteValidation().Any())
- {
- foreach(var error in context.ExecuteValidation())
- {
- //處理代碼
- }
- }
- else
- {
- context.SaveChange();
- }
講到這里估計會有很多小伙伴說每個業務代碼中都要這么寫太麻煩了,而且也產生了大量的重復代碼。那么重復代碼這個問題該怎么解決呢?這時一定有部分小伙伴想到了通過重寫 SaveChanges 方法,將驗證代碼加入到這個方法中,這樣就可以解決剛才的那個問題,達到一勞永逸的效果。具體代碼如下:
- public override int SaveChanges(bool acceptAllChangesOnSucces)
- {
- var provider = ((IInfrastructure<IServiceProvider>)this).Instance;
- var items = new Dictionary<object, object>();
- var models = this.ChangeTracker.Entries()
- .Where(
- p => (p.State == EntityState.Added)||(p.State==EntityState.Modified));
- foreach (var model in models)
- {
- var entity = model.Entity;
- var context = new ValidationContext(entity, provider, items);
- List<ValidationResult> results = new List<ValidationResult>();
- if(!Validator.TryValidateObject(entity,context,results,true))
- {
- foreach (var result in results)
- {
- if(result!=ValidationResult.Success)
- {
- throw new ValidationException(result.ErrorMessage);
- }
- }
- }
- }
- return base.SaveChanges();
- }
通過上述代碼就可以一處編寫驗證,多處使用了。具體的思路和前面所講的一樣,這里就不再進行講解了。
二、第三方擴展模型驗證
前面所講的是通過數據注解的方式來進行數據驗證的,但是如果是使用 Fluent API 的方式就沒辦法解決文章開頭所說的問題,因為Fluent API 模式并沒有提供對數據模型的驗證。這時我們可以使用第三方擴展,在 EF Core 中常用的模型數據驗證第三方擴展是 FluentValidation.AspNetCore 。在使用前我們需要在 NuGet 中下載此擴展。 FluentValidation.AspNetCore 安裝完成后我們需要為模型創建驗證器,驗證器是一個繼承自 AbstractValidator<T> 的類,驗證規則使用 RuleFor 方法定義在驗證器構造函數中。代碼如下:
- public class ModelValidator:AbstractValidator<User>
- {
- public ModelValidator()
- {
- RuleFor(p => p.Name).NotEmpty().WithMessage("姓名不能為空");
- RuleFor(p => p.Name).MaximumLength(5).WithMessage("姓名長度在5字節");
- }
- }
上述代碼進行了兩個驗證,一個是驗證 Name 字段是否為空,另一個是驗證 Name 字段的長度,其中我們通過 MaximumLength 規定了 Name 字段的最長長度為 5 字節。之后我們通過 WithMessage 方法返回我們自定義的錯誤信息。 我們定義完驗證規則后下一步就是將我們定義的驗證規則與應用程序連接起來,這里我們需要用到 AddFluentValidation 來注入,例如在 Asp.Net Core 程序中我們將注入程序寫入 Startup 的 ConfigureServices 方法里。我們調用 AddFluentValidation 方法會將 FluentValidation 服務添加到 Asp.Net Core 中,然后使用 RegisterValidatorsFromAssembly 方法將自定義的驗證代碼注入到容器中,代碼段如下:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc()
- .AddFluentValidation(p=>
- p.RegisterValidatorsFromAssemblyContaining<Startup>());
- }
在需要驗證數據的地方我們通過 ModelState 獲取驗證狀態,驗證通過就執行后續代碼,不通過就執行處理代碼。示例代碼如下:
- if(ModelState.IsValid)
- {
- //后續代碼
- }
- else
- {
- //驗證不通過處理代碼
- }
這里有一點需要注意,當傳遞的實體為 null 時,將返回錯誤信息,這是因為 AbstractValidator 中存在 EnsureInstanceNotNull 方法,這個方法在實例為 null 時會拋出異常,即使重寫該方法也無法返回自定義的錯誤信息。如果需要驗證實體集合就需要使用 RuleForEach 方法即可,對于自定義驗證規則則可使用 SetValidator 方法。
三、總結
本篇文章講解了 EF Core 數據驗證的方法,雖然講的是 EF Core 的方法,但是同樣也適用于 EF6 ,這些內容是常用的,上述部分代碼可以在大部分項目中通用。
作者簡介:
朱鋼,筆名喵叔,國內某技術博客認證專家,.NET高級開發工程師,7年一線開發經驗,參與過電子政務系統和AI客服系統的開發,以及互聯網招聘網站的架構設計,目前就職于一家初創公司,從事企業級安全監控系統的開發。
【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】