2014年12月11日 星期四

[Entity Framework 6] Issue (2)

.NET   Entity Framework    ORM

背景

採用Entity Framework在專案中發現的一些小技巧。
PS.  可能視版本不同而需做調整或無法使用之情況。

環境

l   Windows 7 pro
l   MS Sql Server 2012
l   Visual Studio 2013 updat4 (C#)
l   EntityFramework 6.1.1

實作


DbContext Constructor

在加入實體資料模型時,可在DbContext內容,手動加入注入DbConnection Constructor 方便從外層注入連線,取代直接由Config設定。

public partial class TmsEntities : DbContext
{
public TmsEntities() : base("name=TmsEntities")
   { }

public TmsEntities(
       System.Data.Common.DbConnection existingConnection,
       bool contextOwnsConnection): base(existingConnection, contextOwnsConnection)
   { }
}


l   existingConnection
要用於新內容的現有連接。

l   contextOwnsConnection
型別:System.Boolean
如果設定為 true,則處置內容時也會處置此連接,否則呼叫端必須處置此連接。


Use Partial class to update DbContext/Models

加入/更新實體資料模型,會讓物件層程式碼重新建立,開發者在上面做的修改都會消失。 好消息是這些類別在建立的時候都是partial class 所以只要在相同的namespace下再建立另一個partial class來放我們的程式碼即可。

l   Model in tt
namespace Tms.Infra.DAL.Database
{
    public partial class TmsCCN
    {
        public int SN { get; set; }
        public string CsvName { get; set; }
        public string CsvDate { get; set; }
        //...
}
}


l   Create update codes for the Model

如下,除了加入Attribute定義,也實作了一些介面。 未來即使修改實體模型,也可直接套用這些程式碼。

namespace Tms.Infra.DAL.Database
{
    [Serializable]
    public partial class TmsCCN : ITmsCsv,ICloneable
    {
        public object Clone()
        {
            using (Stream objStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objStream, this);
                objStream.Seek(0, SeekOrigin.Begin);
                var cloneObj = formatter.Deserialize(objStream) as TmsCCN;
                return cloneObj;
            }

        }
    }
}


DAL層定義一個處理DbContext屬性的父類別Repository

建立父類別後,可讓各子類別DALRepository專心於處理CRUD的邏輯。
並可依據不同需求設定DbContext 例如

/// <summary>
/// DAL父層
/// </summary>
public class BaseDAL
{
    /// <summary>
    /// Entity framework model
    /// </summary>
    protected TmsEntities _tmsEntities = null;

private readonly int? CMD_TIMEOUT = 600; //Default timeout : 600sec=10min
       
public BaseDAL(int? timeOut = null)
    {
        // 建立DbContext
        this._tmsEntities = new TmsEntities();
if (timeOut == null){
             this._tmsEntities.Database.CommandTimeout = this.CMD_TIMEOUT;
        }
        else{
             this._tmsEntities.Database.CommandTimeout = timeOut;
        }
    }
}

l   使用Default timeout
public class EntEdiDetailDAL : BaseDAL, IDisposable
{
        public EntEdiDetailDAL():base()
        {}
}

l   或者自訂 timeout
public class EntEdiDetailDAL : InfraDAL, IDisposable
{
     public EntEdiDetailDAL():base(timeOut:1200)
     {}
}



Multi-threading

l   Entity framework在處理多條執行緒時,無法共用同一個DbContext
錯誤訊息:
此連接已經在交易中,無法參與其他交易。EntityClient 不支援平行交易。

所以都必須在每一條Thread都必須使用一個專屬的DbContext



錯誤訊息:”The object was not found in the ObjectStateManager”

l   此錯誤訊息表示此物件尚未繫結到目前的DbContext
在此範例中的發生原因是 在非同步程式碼中,將另一個DbContext挑出來的Entity,用另一個DbContext做刪除。

public async Task<int> BatchDelete(List<T> entityList)
{
    var cloneContext = this._context.Clone() as TmsEntities;
    cloneContext.Configuration.AutoDetectChangesEnabled = false;

    using (var dbContextTransaction = cloneContext.Database.BeginTransaction())
    {
        try{
                                                                
cloneContext.TmsCCN.RemoveRange(entityList.AsEnumerable());
cloneContext.SaveChanges();
dbContextTransaction.Commit();
}
        catch (Exception ex){
          dbContextTransaction.Rollback();
          throw;
}
}
    cloneContext.Dispose();
    cloneContext = null;
return entityList.Count;
}



l   解決方式

在執行Remove之前,必須將entity與目前的DbContext繫結,在上面程式碼的灰色地方加入:

foreach (var entity in entityList)
{
    if (cloneContext.Entry(entity).State == EntityState.Detached)
    {
cloneContext.TmsCCN.Attach(entity);
    }
}





Reference




沒有留言:

張貼留言