我一直在从事一个大型项目,该项目大量使用了关系数据库。该项目使用C#,并且不使用ORM。我发现应用程序很难使用,因为它访问应用程序代码中的数据库的方式,但是我没有足够的大型项目经验来说明如何做得更好(并不是我认为这是一个好主意)更改大量的旧代码,但我想知道如何为下一个项目做得更好。我不在乎您的答案是否与C#或是否使用ORM有关,我只想阅读解决此问题的各种方法。

这是该项目工作方式的概述:


有很多表,视图和少量存储过程。
应用程序代码中有一个数据访问层,用于处理对数据库的原始访问(这一层是一堆函数,看起来像GetUserById(id)GetUserByLastName(lastName)AddUser(firstname, lastName)GetCommentsByDateAndPostId(date, postId)GetCommentsByDateAndPostIdSortedByDate(date, postId)等...)。所有这些函数都调用一个手写的SQL查询并基本上返回表的内存表示形式(即results[0].lastName是第0行的列lastName)。在C#中,这是一个数据表。
数据访问层上方有一层用于业务处理规则。它是每个数据访问功能的包装,但是在调用相应的(即,同名的)数据访问功能之前,可能需要进行一些业务逻辑检查。在所有情况下,它都返回与数据访问层相同的东西。应用程序代码仅通过此层访问数据库,而不直接访问数据访问层。
在野外没有一次性的查询。数据访问层(以及业务逻辑层)中的查询和功能之间存在一一对应的关系。由于数据库已规范化,因此大多数查询都有一个视图,因为必须进行联接。
到处都有很少使用的存储过程


因此,如果今天我想以新的方式访问数据库,则必须修改数据库,然后创建一个数据访问层函数,该函数调用对数据库的自定义书面SQL查询,然后创建一个业务逻辑函数,该函数调用数据访问图层功能。然后可能会修改一堆现有功能以包含或不包含此更改。我什至不知道在如此多变的环境中从哪里开始自动化测试。

那就是我要修改或添加数据库的单个列。如果我想添加一个新表,那么可以为它的所有选择方式(WHERE子句的组合),插入,更新,删除或排序等添加一堆新函数。

最佳答案

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

例如,根据您的描述,很明显,该体系结构将功能职责明确地划分为多个层。您有一个演示文稿(UI),该演示文稿与域(BLL)通信,而该域又使用存储库模式与其基础结构(DAL)通信。您的BLL似乎已经实现了跨领域的关注,例如验证和安全性。

您可以在此设计上进行改进的方法是通过包含模型来合并更强大的Domain。放弃旧的ADO.NET DataTable技术,并设计一个反映您的数据库的强类型模型。合并ORM可以极大地帮助实现这一目标,因为它具有从数据库生成模型并轻松维护更改的能力。

我不会像您期望的那样进一步发挥ORM的优势。您的DAL应该返回POCO和Enumerables。让您的BLL返回响应对象(我喜欢将它们称为服务响应对象或演示文稿传输对象),其中可能包含诸如POCO数据,错误处理结果,验证结果之类的内容。

另一个可能的解决方案是将您的存储库模式实现更改为通用存储库,尽管现在这会将您的基础结构逻辑渗入到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 vs DataTables ...只要您可以使用强类型对象,它总是更好。使用T4模板可以自动从ORM模型生成POCO。 POCO也可以轻松地映射到DTO中以进行服务操作,反之亦然。也不再真正需要DataTables。可以使用BindingList代替List来实现对数据绑定的CRUD支持。

在没有填充其所有属性的情况下返回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()));

07-24 17:54
查看更多