2011-03-10 27 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