应用程序代码中的DB Access体系结构

时间:2011-03-10 17:15:48

标签: c# design-patterns database-design orm data-access-layer

我一直致力于一个大型项目,它大量使用关系数据库。该项目位于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子句的组合),或插入,更新,删除或排序等。

2 个答案:

答案 0 :(得分:2)

您所描述的内容本身并不是问题。它实际上是应用程序设计和模式使用的一个很好的例子。它缺乏的东西使它看起来有问题,因为它没有利用有助于维护的新技术/技术。

例如,根据您的描述,显而易见的是,该架构清楚地将功能职责划分为多个层。您有一个与域(BLL)通信的Presentation(UI),后者又使用Repository模式与其Infrastructure(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();

这方面的优点是您只需要创建域业务逻辑。如果需要修改数据库表,则只需更改一次业务逻辑。但是,该查询现在存在于业务逻辑中,只是使用“存储库”作为媒介与您的数据库进行通信,而有些人认为这些不正确。


更新

您可以使用泛型来创建简单的响应对象。例如:

[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 Enterprise Library Validation Block。为了支持验证,它使用AutoMapper将Microsoft的验证结果转换为我自己的ValidationResult对象。我不建议尝试序列化MS的类,因为它在服务中使用时证明容易出错。

重载的构造函数允许您提供单个poco或可枚举的pocos。 POCO与DataTables ......只要你能使用强类型对象,它总是更好。使用T4模板,您可以从ORM模型自动生成POCO。 POCO也可以很容易地映射到DTO以进行服务操作,反之亦然。 DataTables也不再需要了。您可以使用BindingList通过数据绑定来支持CRUD,而不是List。

在没有填写所有属性的情况下返回POCO是完全没问题的。在实体框架中,这称为投影。通常我会为此而不是我的域实体创建自定义DTO。


更新

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;
    }
}

用于将Microsoft验证结果转换为ValidationResult DTO的AutoMapper示例代码:

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()));

答案 1 :(得分:0)

我建议使用Facade模式封装单个对象中的所有数据访问调用。然后将每个现有的数据访问调用重构为对facade对象的调用。

我在Best approach to Architect the integration of two separate databases?处回答了另一个问题时,更深入地解释了实施外观模式。