2011-03-10 28 views
2

我一直在研究大量使用關係數據庫的大型項目。該項目使用C#,不使用ORM。由於它在應用程序代碼中訪問數據庫的方式,我發現應用程序很難處理,但我沒有足夠的大型項目經驗來說明它可能會更好(不是我認爲這是一個好主意改變大量的遺留代碼,但我想知道如何更好地爲下一個項目做)。我不在乎你的答案是否與C#有關或者是否使用ORM,我只是想閱讀爲解決這個問題而採取的各種方法。應用程序代碼中的DB訪問體系結構

下面是該項目的運作方式概要:

  • 有相當多的表,視圖和存儲過程極少數。
  • 沒有在應用程序代碼,處理到數據庫的原始訪問(這一層是像會是什麼樣GetUserById(id)GetUserByLastName(lastName)AddUser(firstname, lastName)GetCommentsByDateAndPostId(date, postId)GetCommentsByDateAndPostIdSortedByDate(date, postId),等等一堆功能)數據訪問層。所有這些函數調用手寫SQL查詢並基本返回表的內存表示(即,results[0].lastName是行0的列lastName)。在C#中,這是一個DataTable。
  • 數據訪問層上方有一層用於業務處理規則。它是每個數據訪問函數的包裝器,但在調用相應(即相同名稱)數據訪問函數之前,可能會進行一些業務邏輯檢查。它在所有情況下都會返回與數據訪問層相同的內容。應用程序代碼只能通過該層訪問數據庫,而不能直接訪問數據訪問層。
  • 在野外沒有一次性的查詢。數據訪問層中的查詢和功能(以及業務邏輯層)之間存在一對一的對應關係。由於數據庫已標準化,因此大多數查詢都有一個視圖,因爲連接是必需的。
  • 這裏還有就是很少使用存儲過程,並有

所以如果我今天要訪問數據庫中的一種新的方式,我必須修改數據庫,然後創建一個調用數據訪問層功能一個自定義的寫入SQL查詢到數據庫,然後創建一個調用數據訪問層功能的業務邏輯函數。然後可能修改一大堆現有的函數來包含這個變化。我甚至不知道在這樣一個動盪的環境中從哪裏開始自動化測試。

這就是如果我想修改或添加一個數據庫的單個列。如果我想添加一個新表格,可以添加一些新的函數來添加它可以選擇的所有方式(WHERE子句的組合),或插入,更新,刪除或排序等。

+0

這應該被標記爲社區維基。 – 2011-03-10 18:44:36

回答

2

你所描述的本身不是問題。這實際上是應用程序設計和模式使用的一個很好的例子。它缺乏的東西使得它看起來有問題,因爲它沒有利用幫助維護性的新技術/新技術。

例如,從您的描述中可以明顯看出,架構明確地將職能職責劃分爲層。您有一個與域(BLL)進行通信的演示文稿(UI),該域又使用存儲庫模式與其基礎架構(DAL)進行通信。你的BLL似乎已經實施了諸如驗證和安全等交叉問題。

你可以做些什麼來改進這個設計,那就是通過加入一個模型來包含一個更強的域。刪除舊的ADO.NET DataTable技術並設計反映數據庫的強類型模型。結合ORM可以極大地幫助它,因爲它有能力從數據庫生成模型並輕鬆維護更改。

我不會深入瞭解ORM的優勢。你的DAL應該返回POCO和Enumerables。讓BLL返回響應對象(我喜歡稱它們爲服務響應對象或演示文稿傳輸對象),其中可能包含如下內容:POCO數據,錯誤處理結果,驗證結果。

另一種可能的解決方案是將您的Repository模式的實現更改爲Generic Repository,儘管這會將您的基礎架構邏輯放到BLL中。例如,而不是:

public class UserRepository 
{ 
    public User GetUserById(Int32 userId){...} 
} 

您可以創建(使用泛型)實現IQueryable的存儲庫。看看nCommon,這是一個很好的方法。這將允許你做這樣的事情:

var userRepository = new EF4Repository<User>(OrmContextFactory.CreateContext(...)); 
User u = userRepository.Where(user => user.Id == 1).SingleOrDefault(); 

這是專業人士只需要創建域的業務邏輯。如果您需要修改數據庫表,則只需更改一次業務邏輯即可。但是,該查詢現在存在於業務邏輯中,並且簡單地使用「存儲庫」作爲與您的數據庫進行通信的媒介,而這些數據庫有些人認爲是不恰當的。


UPDATE

您可以使用泛型創建一個簡單的響應對象。例如:

[DataContract(Name = "ServiceResponseOf{0}")] 
public class ServiceResponse<TDto> : ResponseTransferObjectBase<TDto> where TDto : IDto 
{ 
    #region Constructors 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="error">The error.</param> 
    /// <remarks></remarks> 
    public ServiceResponse(ServiceErrorBase error) 
     : this(ResponseStatus.Failure, null, new List<ServiceErrorBase> {error}, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="errors">The errors.</param> 
    /// <remarks></remarks> 
    public ServiceResponse(IEnumerable<ServiceErrorBase> errors) 
     : this(ResponseStatus.Failure, null, errors, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Failure"/>. 
    /// </summary> 
    /// <param name="validationResults">The validation results.</param> 
    public ServiceResponse(MSValidation.ValidationResults validationResults) 
     : this(ResponseStatus.Failure, null, null, validationResults) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Success"/>. 
    /// </summary> 
    /// <param name="data">The response data.</param> 
    public ServiceResponse(TDto data) 
     : this(ResponseStatus.Success, new List<TDto> { data }, null, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Success"/>. 
    /// </summary> 
    /// <param name="data">The response data.</param> 
    public ServiceResponse(IEnumerable<TDto> data) 
     : this(ResponseStatus.Success, data, null, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="responseStatus">The response status.</param> 
    /// <param name="data">The data.</param> 
    /// <param name="errors">The errors.</param> 
    /// <param name="validationResults">The validation results.</param> 
    /// <remarks></remarks> 
    private ServiceResponse(ResponseStatus responseStatus, IEnumerable<TDto> data, IEnumerable<ServiceErrorBase> errors, MSValidation.ValidationResults validationResults) 
    { 
     Status = responseStatus; 
     Data = (data != null) ? new List<TDto>(data) : new List<TDto>(); 

     Errors = Mapper.Map<IEnumerable<ServiceErrorBase>, List<ServiceError>>(errors) ?? 
       new List<ServiceError>(); 

     ValidationResults = 
      Mapper.Map<MSValidation.ValidationResults, List<IValidationResult>>(validationResults) ?? 
      new List<IValidationResult>(); 
    } 

    #endregion 

    #region Properties 

    /// <summary> 
    /// Gets the <see cref="IDto"/> data. 
    /// </summary> 
    [DataMember(Order = 0)] 
    public List<TDto> Data { get; private set; } 

    [DataMember(Order = 1)] 
    public List<ServiceError> Errors { get; private set; } 

    /// <summary> 
    /// Gets the <see cref="ValidationResults"/> validation results. 
    /// </summary> 
    [DataMember(Order = 2)] 
    public List<IValidationResult> ValidationResults { get; private set; } 

    /// <summary> 
    /// Gets the <see cref="ResponseStatus"/> indicating whether the request failed or succeeded. 
    /// </summary> 
    [DataMember(Order = 3)] 
    public ResponseStatus Status { get; private set; } 

    #endregion 
} 

此類是我使用從我的域結果返回給我的服務層或我的介紹一個基本的響應對象。它可以序列化並支持MS企業庫驗證塊。爲了支持驗證,它使用AutoMapper將Microsoft的驗證結果轉換爲我自己的ValidationResult對象。我不建議嘗試序列化MS的類,因爲它在服務中使用時很容易出錯。

重載的構造函數允許您提供單個poco或pocos的枚舉。 POCO vs DataTables ...任何時候你可以使用強類型的對象,它總是更好。使用T4模板,您的POCO可以自動從ORM模型生成。 POCO也可以輕鬆映射到DTO中進行服務操作,反之亦然。現在也不再需要DataTable了。而不是列表,您可以使用BindingList進行數據綁定的CRUD支持。

返回一個沒有填充其所有屬性的POCO是非常好的。在實體框架中,這被稱爲投影。通常我會爲此創建自定義的DTO,而不是我的域實體。


UPDATE

例的ValidationResult類:

/// <summary> 
/// Represents results returned from Microsoft Enterprise Library Validation. See <see cref="MSValidation.ValidationResult"/>. 
/// </summary> 
[DataContract] 
public sealed class ValidationResult : IValidationResult 
{ 
    [DataMember(Order = 0)] 
    public String Key { get; private set; } 

    [DataMember(Order = 1)] 
    public String Message { get; private set; } 

    [DataMember(Order = 3)] 
    public List<IValidationResult> NestedValidationResults { get; private set; } 

    [DataMember(Order = 2)] 
    public Type TargetType { get; private set; } 

    public ValidationResult(String key, String message, Type targetType, List<ValidationResult> nestedValidationResults) 
    { 
     Key = key; 
     Message = message; 
     NestedValidationResults = new List<IValidationResult>(nestedValidationResults); 
     TargetType = targetType; 
    } 
} 

舉例翻譯微軟驗證AutoMapper碼結果到的ValidationResult DTO:

Mapper.CreateMap<MSValidation.ValidationResult, IValidationResult>().ConstructUsing(
      dest => 
      new ValidationResult(
       dest.Key, 
       dest.Message, 
       dest.Target.GetType(), 
       dest.NestedValidationResults.Select(mappingManager.Map<MSValidation.ValidationResult, ValidationResult>).ToList())); 
+0

應答對象是否都是具有DAL POCO字段(例如User,Comment等)和錯誤結果等的同一類(即DatabaseResponse)的實例?還是會有單獨的響應對象類型類型取決於響應類型(也許可枚舉vs單個對象,或可能的POCO類型)? – 2011-03-10 22:30:21

+0

另外,使用POCO比DataTables有什麼優勢,除了它在代碼中看起來更漂亮?如果你退還POCO而沒有填寫所有的財產......如果這種情況永遠不會發生? – 2011-03-10 22:34:59

+0

更新了我對上述問題的回覆。 – Daniel 2011-03-11 00:16:10

0

我會推薦使用Facade模式來封裝單個對象內的所有數據訪問調用。然後將每個現有數據訪問調用重構爲對Facade對象的調用。

我對Best approach to Architect the integration of two separate databases?的另一個問題做了更深入的解釋,說明實施門面模式的方法。

+0

我看了你的文章,但需要一點澄清。業務邏輯層不是作爲底層數據庫子系統的外觀嗎? – 2011-03-10 21:38:36

+0

這聽起來像你的業務邏輯和你的數據庫邏輯都混在一起了。您應該將所有數據庫調用封裝到外觀中,並將對數據庫的所有業務對象調用重構爲對外觀的調用。 – smartcaveman 2011-03-10 23:03:46