Quantcast
Channel: ProgrammerXDB Blog - C#
Viewing all articles
Browse latest Browse all 73

Entity Framework Validation API - 1

$
0
0

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N170518301
出刊日期: 2017/5/3

我們所設計的應用程式時常常需要搜集使用者輸入的資料,也需要檢查使用者輸入的資料是否正確符合需求。Entity Framework預設支援使用.NET Framework 4提供的ValidationAttribute、IValidatableObject來驗證實體模型的資料是否如預期,若預設的功能不符合需求,您也可以自行設計驗證機制。DbContext類別也新增了新的Validation API進一步整合並擴充驗證的功能。本篇文章將介紹如何在Entity Framework應用程式之中使用ValidationAttribute、IValidatableObject以及Validation API來設計驗證、自訂驗證,以及利用try..catch語法攔截驗證例外錯誤。

  • 預設執行以下動作,會促使DbContext進行驗證:
  • 呼叫DbContext.SaveChanges()方法,將驗證所有標識為Added與Modified的物件。
  • 呼叫DbEntityEntry.GetValidationResult()方法,將驗證特定物件。
  • 叫用DbContext.GetValidationErrors()方法,將驗證所有標識為Added與Modified的物件。

Store實體類別(Entity Class)

本文延續使用《Change Tracking API - 1》一文建立的ADO.NET實體資料模型來說明Entity Framework提供的驗證API。參考以下範例程式碼,目前Store實體類別(Entity Class)的定義如下,stor_id、stor_name、stor_address、city、state、zip屬性上方都套用了StringLength Attribute限定資料的有效長度。ValidationAttribute是.NET Framework 4 的功能,不是Entity Framework的一部分,但Entity Framework的Validation API已經整合了ValidationAttribute,將會根據套用的這些Attribute進行資料驗證檢查:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "99999";
                aStore.stor_name = "9999 store";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OROR";
                aStore.zip = "890766";

                if (context.Entry(aStore).GetValidationResult().IsValid)
                {
                    Console.WriteLine("Validation success!");
                }
                else
                {
                    Console.WriteLine("Validation failed!");
                }

            }
        }
    }
}

 

使用GetValidationResult()方法進行驗證

DbEntityEntry<T>類別的GetValidationResult()方法可以針對一個實體(Entity)的屬性資料進行驗證,它會根據實體設定的驗證Attribute進行資料有效性檢查,然後回傳一個DbEntityValidationResult物件代表驗證的結果,只要有任何一個屬性值違反驗證Attribute的規定,就會自動將DbEntityValidationResult物件IsValid屬性的值設定為「false」,代表驗證失敗;所有實體屬性驗證都成功的話,才會將IsValid屬性的值設定為「true」。

參考以下範例程式碼,建立一個store物件,故意設定stor_id屬性值設定為「99999」,其資料長度超過「4」;將state屬性的值設定為「9999」,資料長度超過「2」,zip屬性值設定為「890766」,資料長度超過「5」,然後判斷IsValid屬性來顯示驗證結果:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "9999";
                aStore.stor_name = "9999 store";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OR";
                aStore.zip = "89076";

                if (context.Entry(aStore).GetValidationResult().IsValid)
                {
                    Console.WriteLine("Validation success!");
                }
                else
                {
                    Console.WriteLine("Validation failed!");
                }

            }
        }
    }
}

 

此範例執行結果如下所示,將印出驗證失敗的訊息,請參考下圖所示:

clip_image002

圖 1:使用GetValidationResult()方法驗證一個實體(Entity)的屬性。

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "9999";
                aStore.stor_name = "9999 store";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OR";
                aStore.zip = "89076";

                if (context.Entry(aStore).GetValidationResult().IsValid)
                {
                    Console.WriteLine("Validation success!");
                }
                else
                {
                    Console.WriteLine("Validation failed!");
                }

            }
        }
    }
}

 

修改目前程式碼進一步測試,填入有效的store屬性資料,讓資料不要超過預期的長度:

此範例執行結果如下所示:

clip_image004

圖 2:使用GetValidationResult()方法驗證一個實體(Entity)的屬性。

 

使用ValidationErrors屬性檢視詳細錯誤資訊

DbEntityEntry<T>類別的GetValidationResult()方法會回傳DbEntityValidationResult物件,此物件包含一個ValidationErrors屬性,可以進一步得知驗證錯誤的詳細資訊。ValidationErrors屬性是一個由DbValidationError物件所成的集合,DbValidationError物件包含兩個屬性:PropertyName,驗證錯誤的屬性名稱;ErrorMessage:驗證錯誤的錯誤訊息。

參考以下範例程式碼,叫用DbEntityEntry<T>類別的GetValidationResult()方法驗證新建立的store物件,故意設定stor_id屬性值設定為「99999」,其資料長度超過「4」;將state屬性的值設定為「9999」,資料長度超過「2」,zip屬性值設定為「890766」,資料長度超過「5」,然後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "99999";
                aStore.stor_name = "9999 store";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OROR";
                aStore.zip = "890766";

                DbEntityValidationResult result = context.Entry(aStore).GetValidationResult();
                if (!result.IsValid)
                {
                    foreach (DbValidationError item in result.ValidationErrors)
                    {
                        Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
                    }
                }

            }
        }
    }
}

 

此範例執行結果如下:

stor_id - The field stor_id must be a string with a maximum length of 4.

state - The field state must be a string with a maximum length of 2.

zip - The field zip must be a string with a maximum length of 5.

 

 

自訂錯誤訊息

ValidationAttribute包含一個ErrorMessage屬性,可以自定驗證錯誤訊息,參考以下範例程式碼,讓我們修改Store類別,為每一個StringLength Attribute加上自訂錯誤訊息,訊息中的{0}代表參數,代表屬性的名稱;{1}參數則是StringLength中設定的長度:

 

namespace PubsDemo
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;

    public partial class store
    {
        public store()
        {
        }

        [Key]
        [StringLength(4, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string stor_id { get; set; }

        [StringLength(40, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string stor_name { get; set; }

        [StringLength(40, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string stor_address { get; set; }

        [StringLength(20, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string city { get; set; }

        [StringLength(2, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string state { get; set; }

        [StringLength(5, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string zip { get; set; }

        public virtual ICollection<sale> sales { get; set; }

        public virtual ICollection<discount> discounts { get; set; }
    }
}

 
重新執行測試程式碼,範例執行結果如下所示:

stor_id - stor_id 長度不可超過 4

state - state 長度不可超過 2

zip - zip 長度不可超過 5

 

使用CustomValidationAttribute自訂驗證

如果預設的驗證Attribute不能滿足您複雜的商業邏輯需求,Entity Framework可以允許你使用CustomValidationAttribute在指定的屬性套用自訂驗證的邏輯。參考以下範例程式碼,包含一個MyCustomValidations靜態類別,裏頭定義一個ContentValidationRule靜態方法,此方法檢查指定的屬性值是否包含不合法的字串「admin」與「test」,若包含這兩個字串,則回傳一個ValidationResult物件,並設定錯誤訊息為「名稱不可包含 admin 或 test 字串」;若不包含這兩個字串,則回傳ValidationResult.Success:

 

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    public static class MyCustomValidations
    {
        public static ValidationResult ContentValidationRule(string value)
        {
            string errMsg = "";
            if (value != null)
            {
                if (value.Contains("admin") || value.Contains("test"))
                {
                    errMsg = "名稱不可包含 admin 或 test 字串";
                    return new ValidationResult(errMsg);
                }
            }
            return ValidationResult.Success;
        }
    }
}


接著修改store 類別程式碼,使用CustomValidation Attribute套用自訂驗證程式碼到store類別欲驗證的stor_name屬性上方,CustomValidation需要傳入兩個參數,第一個參數指定驗證程式碼所在的類別之型別,本例為「typeof(MyCustomValidations)」;第二個參數則是要叫用的方法名稱,本例為「ContentValidationRule」方法:

 

namespace PubsDemo
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;

    public partial class store
    {
        public store()
        {
        }

        [Key]
        [StringLength(4, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string stor_id { get; set; }

        [StringLength(40)]
        [CustomValidation(typeof(MyCustomValidations), "ContentValidationRule")]
        public virtual string stor_name { get; set; }

        [StringLength(40)]
        public virtual string stor_address { get; set; }

        [StringLength(20)]
        public virtual string city { get; set; }

        [StringLength(2, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string state { get; set; }

        [StringLength(5, ErrorMessage = "{0} 長度不可超過 {1}")]
        public virtual string zip { get; set; }

        public virtual ICollection<sale> sales { get; set; }

        public virtual ICollection<discount> discounts { get; set; }
    }
}

 

修改一下主程式碼來進行測試程式,將含有無效的admin字串填入stor_name屬性,然後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "99999";
                aStore.stor_name = "9999 admin store";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OROR";
                aStore.zip = "890766";

                DbEntityValidationResult result = context.Entry(aStore).GetValidationResult();
                if (!result.IsValid)
                {
                    foreach (DbValidationError item in result.ValidationErrors)
                    {
                        Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
                    }
                }

            }
        }
    }
}

 

執行測試程式碼,此範例執行結果如下所示,除了顯示AttributeValidation的驗證錯誤資訊之外,也顯示了自訂驗證錯誤訊息:

stor_id - stor_id 長度不可超過 4

stor_name - 名稱不可包含admin或test字串

state - state 長度不可超過 2

zip - zip 長度不可超過 5

驗證特定屬性

DbPropertyEntry類別包含GetValidationErrors()方法,可以針對特定的屬性來進行資料驗證。GetValidationErrors()方法會回傳一個ICollection<DbValidationError>集合,包含多個驗證錯誤資訊。

參考以下程式碼範例,延續前文範例,建立一個store物件,並且故意填入長度超過40的字串到stor_name屬性中,且包含無效的admin字串,然後使用GetValidationErrors()方法驗證stor_name屬性,最後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PubsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new PubsContext())
            {
                var aStore = new store();
                aStore.stor_id = "99999";
                aStore.stor_name = "9999 admin store 9999 admin store 9999 admin store 9999 admin store ";
                aStore.stor_address = "679 Carson St.";
                aStore.city = "Portland";
                aStore.state = "OR";
                aStore.zip = "89076";

                ICollection<DbValidationError> result =
                    context.Entry(aStore).Property(n => n.stor_name).GetValidationErrors();

                foreach (var item in result)
                {
                    Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
                }
            }
        }
    }
}

 

根據我們設定的驗證規則,此範例執行結果如下所示:

stor_name - The field stor_name must be a string with a maximum length of 40.

stor_name - 名稱不可包含admin或test字串


Viewing all articles
Browse latest Browse all 73

Latest Images

Trending Articles