顯示包含「Entity Framework」標籤的文章。顯示所有文章
顯示包含「Entity Framework」標籤的文章。顯示所有文章

2013年11月22日星期五

Bug in Entity Framework 6 w/ SQL Server 2012

若你的程式有使用EF6 + SQL Server 2012,很大機會會遭遇兩個錯誤:
Sys.WebForms.PageRequestManagerServerErrorException: The provider did not return a ProviderManifest instance.
Could not determine storage version; a valid storage connection or a version hint is required.

以我的情況來說,是ASP.NET 4.5 Webforms Project,有部份頁面的GridView或WebService是有使用SQLDataSource和EntityDataSource,
在升級到EF6後,就會出現上述的錯誤。



上Google搜尋,並沒有統一的答案,而Microsoft Connect上的Bug Report還是未解決狀態。

一輪苦戰之後,最後找到一個解決方法,就是把Data Model (*.edmx) 中的ProviderManifestToken的值,由"2012"設回"2008",再Rebuild Solution就OK。

2013年3月14日星期四

EntityFramework的DbUpdateConcurrencyException與OptimisticConcurrencyException的解析和解決辦法

這幾天,工作上需要製作一個Data Grid型式的Edit Form做CRUD
技術上使用ASP.NET MVC4 / EntityFramework 5.0 / Scaffolding,因為一個小小低級錯誤,導致出現DbUpdateConcurrencyException,了解這個錯誤後,在此講解一下,順道亦介紹一下相關的OptimisticConcurrencyException

DbUpdateConcurrencyException
這是EF 5.0新增的Exception,如圖所示,這通常會出現在SaveChanges()的時候。

Exception Message :
中文 :
存放區更新、插入或刪除陳述式影響到非預期數目的資料列 (0)。這些實體載入之後可能被修改或刪除了。請重新整理 ObjectStateManager 實體。
英文 :
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

原因 :
就如Exception Message所說,這個錯誤會出現在INSERT / UPDATE / DELETE,原因是出現非預期的Affected Row數目。
那我們就要想一想什麼原因會導致Affected Row是 "0"。

1. 遺失Primary Key
以我工作上的例子,MVC4 Scaffolding建立的View都是含@model keyword的strongly-typed view
理論上你按Submit ,Post form後,ActionResult應該都會做Model-Binding

但有時候,因為一些人為錯誤,就會導致接收的Model數值不完整。
例如我這個情況,把DropDownList設定disabled,Post form後,那就沒有CompanyID了。出現"0"是因為CompanyID是int Type預設值而已。
那EF 跟本找不到符合的紀錄,固然亦UPDATE不能,所以就回報"0"Rows了。



這情況亦會出現在UPDATE和INSERT的場景上,所以要多加注意。
但有時我們真的需要把DropDownList / TextBox等等設定為Disabled,那怎麼辦?
那跟ASP.NET Webforms時一樣,加上一個HiddenField吧。
Razor 方法:
@Html.HiddenFor(model => model.CompanyID)

2. 同時Parallel作業
這就是所謂的並行處理(Concurrency)。
舉一個簡單的例子,Ken開啟了某客戶的Edit Form,想把電話修改一下,開啟後,Ken離開了崗位,但Edit Form依然開著。
幾分鐘後,Cammy把這位客戶的資料刪除。
當Ken 回到崗位後,修改了客戶的電話,按儲存。
就會出現DbUpdateConcurrencyException了。
原因很明顯就是找不到紀錄。

OptimisticConcurrencyException
跟上面的同時Parallel作業很相似,但OptimisticConcurrencyException只會發生在UPDATE的時候。
兩位使用者同時修改同一項目,Ken更新電話,Cammy更新地址,只要數值正確,雙方都可以進行儲存。
只是如果Ken和Cammy都是更新電話的話,就要Handle OptimisticConcurrencyException這個Exception進行Resolve Conflicts。
可以看看下列兩條連結的做法,是不簡單的。

不過由此可以見到EF發展到5.0版本已經是一個功能強大,安全的Framework。

Handling Concurrency with the Entity Framework in an ASP.NET MVC Application
Handling Concurrency with the Entity Framework 4.0 in an ASP.NET Web Application

2011年3月3日星期四

當EF LINQ Expression遇上Methods

若你跟我一樣之前有用LINQ to SQL的話,總會有機會把Extension Methods與LINQ Query結合,就算沒有,toString()這個Object Method或者Convert.ToString(),你都沒可能未用過。

在LINQ to SQL或者LINQ to Object的時候都是相安無事的。

但在Entity Framework上,情況就有不同。

先看Extension methods :
    public static class ExtensionMethods
    {
        public static int ToInt(this String str)
        {
            return Convert.ToInt32(str);
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        using (AdventureWorksEntities awe = new AdventureWorksEntities())
        {
            string s = "1";
            var select = from p in awe.ProductModel
                         where p.ProductModelID == s.ToInt()
                         select p;
            Response.Write(select.Count());
        }
    }
很無奈,你會得到錯誤訊息:
中文:
LINQ to Entities 無法辨識方法 'Int32 ToInt(System.String)' 方法,而且這個方法無法轉譯成存放區運算式。

英文:
System.NotSupportedException: LINQ to Entities does not recognize the method 'Int32 ToInt(System.String)' method, and this method cannot be translated into a store expression.

可能你會說,不用Extension Methods就可以了,但原來ToString()和Convert.ToString()都會出現類似錯誤。
再看看:
    protected void Page_Load(object sender, EventArgs e)
    {
        using (AdventureWorksEntities awe = new AdventureWorksEntities())
        {
            object obj = "abcd";
            var select = from p in awe.ProductModel
                         where p.CatalogDescription.Contains(obj.ToString())
                         select p;
            Response.Write(select.Count());
            //錯誤訊息:LINQ to Entities 無法辨識方法 'System.String ToString()' 方法,
            //而且這個方法無法轉譯成存放區運算式。
        }

    }

    protected void Page_Load(object sender, EventArgs e)
    {
        using (AdventureWorksEntities awe = new AdventureWorksEntities())
        {
            object obj = "abcd";
            var select = from p in awe.ProductModel
                         where p.CatalogDescription.Contains(Convert.ToString(obj))
                         select p;
            Response.Write(select.Count());
            //錯誤訊息:LINQ to Entities 無法辨識方法 'System.String ToString(System.Object)' 方法,
            //而且這個方法無法轉譯成存放區運算式。
        }
    }

那原因是什麼呢?
根據這裡這裡這裡所講。

L2S和EF其中一個最大分別就是,EF把LINQ expressions區分為Client的CLR Method和Server的Canonical Function
因為EF轉換expressions成SQL時,就必須參照兩者進行轉換。
可以參考MSDN上的CLR Method to Canonical Function Mapping

而大家所見列表中並沒有ToString()和Convert.ToString(),更莫說自定義的Extension Methods,所以才會出現錯誤 : System.NotSupportedException: LINQ to Entities does not recognize the method.

2010年8月6日星期五

Entity Framework 4.0 : No support for Auto-increment primary keys

EF4是一個不錯的ORM,配搭SQL Server是無敵的組合,但如果用EF4配合SQLCE的話,你可能要重新考慮一下。


無論LINQ2SQL或EF4,一個Table的Primary Key/Unique Key都很重要,因為做Insert/Update/Delete時,在SubmitChange()之前,.NET Framework就會用identifier把需要修改的Entity記起。
一般情況下,Create Table很自然會把id設定做INT / PrimaryKey / IDENTITY(1,1),但如果是SQLCE的話,會怎麼樣呢?

當你使用VS內建的Generator建立EntityModel *.edmx都沒有問題的,但只要你一做修改,就會出現Error
NotSupportedException Message=Server-generated keys and server-generated values are not supported by SQL Server Compact.

原因在Technet已經有解釋:
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework. When using the Entity Framework, an entity’s keys may be marked as server generated. This enables the database to generate a value for the key on insertion or entity creation. Additionally, zero or more properties of an entity may be marked as server-generated values. For more information, see the Store Generated Pattern topic in the Entity Framework documentation. SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework, although the Entity Framework allows you to define entity types with server-generated keys or values. Data manipulation operation on an entity that has server-generated values throws a "Not supported" exception.

唯一解決方法,就是把INT轉做uniqueidentifier類型,而Default value設定做NEWID(),那每次新增一個紀錄就會自行輸入新的Row ID。


在程式碼方面,無論LINQ2SQL或EF4.0都會把uniqueidentifier的資料類型視為GUID (順便一提,GUID全名是Microsoft's Globally Unique Identifiers,和GUI (graphical user interface)無關),所以如果要做Where的程序時,就必須把Value轉成GUID,例如 :
private void cbAccount_SelectionChanged(object sender, SelectionChangedEventArgs e) {
dataEntities de = new dataEntities(EntityConn);
Guid guid = new Guid(cbAccount.SelectedValue.ToString());
var select = from val in de.account_value where val.account_id.Equals(guid) select val;
foreach (account_value v in select) { Console.WriteLine(v.value); }
}

或者有人擔心重覆的問題,大家可以看看維基百科,都幾有趣的。
與被隕石擊中的機率比較的話,已知一個人每年被隕石擊中的機率估計為170億分之1,等同於在一年內建立數十兆筆UUID並發生一次重複。換句話說,每秒產生10億筆UUID,100年後只產生一次重複的機率是50%。如果地球上每個人都各有6億筆UUID,發生一次重複的機率是50%。

其實我個人都有點不太認同把EF不支援INT類型的PrimaryKey,因為實際使用上,uniqueidentifier的確不方便,在我網上Research的同時,很多人都寧願使用回傳統SQL就算。