2

我有一個解決方案,使用webforms前端& mvc管理控制檯。工作單元範圍

這兩個UI都通過Ninject消耗一個服務層,並且我在解決一個微妙但相當重要的問題時遇到了麻煩。

假設我有一個CourseService根據字符串搜索詞返回一個課程列表 - 服務返回搜索結果,但我還需要記錄搜索結果,以及匹配該詞的課程數量,以便管理信息的目的。

我開始時的想法是,工作單元將在請求結束時由UI在頁面方法中提交,例如按鈕單擊事件。這同樣適用於控制器。

這裏的問題是,我依靠UI開發人員調用工作單元上的Commit()以便搜索記錄。 UI開發人員可以在不調用commit的情況下繼續進行,結果將被返回 - 但搜索不會被記錄。這使我決定讓服務層控制工作單元的範圍。 Ninject會自動將工作單元傳遞給服務層和存儲庫實現,並且這將與我已經告訴ninject根據請求範圍創建它的實例相同。

下面是如何我的層寫了一個例子...

public class CourseService 
{ 
    private readonly ICourseRepository _repo; 

    public CourseService(ICourseRepository repo) 
    { 
     _repo = repo; 
    } 

    public IEnumerable<Course> FindCoursesBy(string searchTerm) 
    { 
     var courses = _repo.FindBy(searchTerm); 
     var log = string.format("search for '{1}' returned {0} courses",courses.Count(),searchTerm); 
     _repo.LogCourseSearch(log); 
     //IMO the service layer should be calling Commit() on IUnitOfWork here... 
     return courses; 
    } 
} 

public class EFCourseRepository : ICourseRepository 
{ 
    private readonly ObjectContext _context; 

    public EFCourseRepository(IUnitOfWork unitOfWork) 
    { 
     _context = (ObjectContext)unitOfWork; 
    } 

    public IEnumerable<Course> FindBy(string text) 
    { 
     var qry = from c in _context.CreateObjectSet<tblCourse>() 
      where c.CourseName.Contains(text) 
      select new Course() 
      { 
       Id = c.CourseId, 
       Name = c.CourseName 
      }; 
     return qry.AsEnumerable(); 
    } 

    public Course Register(string courseName) 
    { 
     var c = new tblCourse() 
     { 
      CourseName = courseName; 
     }; 
     _context.AddObject(c); 
     //the repository needs to call SaveChanges to get the primary key of the newly created entry in tblCourse... 
     var createdCourse = new Course() 
     { 
      Id = c.CourseId, 
      Name = c.CourseName; 
     }; 
     return createdCourse; 
    } 
} 

public class EFUnitOfWork : ObjectContext, IUnitOfWork 
{ 
    public EFUnitOfWork(string connectionString) : base(connectionString) 
    {} 

    public void Commit() 
    { 
     SaveChanges(); 
    } 

    public object Context 
    { 
     get { return this; } 
    } 
} 

在你上面的評論可以看到,我覺得我「應該」犯我的變化,但我覺得我可能通過允許服務層和存儲庫實現來控制事務的範圍,從而忽略了更大的問題。

此外 - 當我的存儲庫需要保存一個新的對象,並返回新的給定的主鍵完好無損時,如果我在對象返回後從UI調用Commit,則不會發生這種情況。因此,存儲庫有時需要管理工作單元。

你能看到我的方法有任何直接的問題嗎?

回答

2

這就是你的工作單元的「邊界」。你的邏輯運算的邊界是什麼?是UI代碼/控制器還是服務層?我的意思是界定誰是工作單位? UI開發人員是否有責任將多個服務調用編排爲單個工作單元,還是由服務開發人員負責公開每個包裝單個工作單元的服務操作?這些問題應該在應該調用工作單元的Commit時立即給出答案。

如果你的邏輯操作的邊界是由UI開發人員定義的,你不能這樣做 - 永遠不會。 UI開發人員可以在調用您的方法之前做出一些未提交的更改,但是一旦登錄搜索,您將默默地提交這些更改!在這種情況下,您的日誌操作必須使用自己的上下文/工作單元(此外,它應該在當前事務之外運行),這需要單獨的ninject配置爲每個調用創建新的UoW實例。如果邏輯操作的邊界位於服務中,則不應將工作單元公開給UI開發人員 - 他不應該與活動的UoW實例進行交互。

我不喜歡你的實現是Register在工作單元上調用Commit。邊界又在哪裏?存儲庫操作是否包含工作單元?在這種情況下,你爲什麼有一個服務層?如果您想在單一工作單元中註冊多個咒語,或者您希望課程註冊成爲更大的工作單元的一部分,會發生什麼情況?服務層負責致電Commit。這整個可能來自這樣的想法,即您的存儲庫會將實體投影到自定義類型/ DTO中 - 它看起來對存儲庫負有太多責任並且太複雜。特別是如果你可以使用POCOs(EFv4.x)。

最後要提的是 - 一旦您將服務操作作爲工作單元的邊界,您可以找到每個請求實例不足的情況。您可以擁有內部執行多個工作單元的Web請求。

最後。您擔心UI開發人員的責任 - 同時UI開發人員可能會擔心您的實現 - 如果UI開發人員決定並行運行多個服務操作(EF上下文不是線程安全的,但您只有一個對於整個請求處理)?所以,這與你和UI開發人員之間的溝通(或者所有關於非常好的文檔)有關。

+0

感謝這樣一個發人深省的答覆。我的邏輯操作的邊界不應該由UI開發人員定義,這促使我質疑當前的方法。大部分UI工作將由初級開發人員完成,對業務領域知之甚少,所以我們希望儘可能少地提出問題。由於我們可能需要構建多個實現,因爲我們需要存儲庫層非常「愚蠢」,因爲某些客戶可能非常希望使用基於其他平臺的存儲解決方案,因此服務層是必需的。 – Baldy 2011-06-04 16:21:46

相關問題