我一直致力于一个大型项目,它大量使用关系数据库。该项目位于C#中,不使用ORM。我发现应用程序难以使用,因为它在应用程序代码中访问数据库的方式,但我没有足够的经验来处理大型项目如何更好(不是我认为这是一个好主意改变了大量的遗留代码,但我想知道如何更好地为下一个项目做好准备)。我不在乎您的答案是否与C#或使用ORM有关,我只想阅读解决此问题的各种方法。
以下是该项目的工作方式概述:
GetUserById(id)
,GetUserByLastName(lastName)
,AddUser(firstname, lastName)
的函数, GetCommentsByDateAndPostId(date, postId)
,GetCommentsByDateAndPostIdSortedByDate(date, postId)
等......)。所有这些函数都调用手写SQL查询并基本上返回表的内存表示(即results[0].lastName
是行0的列lastName
。在C#中,这是一个DataTable。所以如果我今天要以新的方式访问数据库,我必须修改数据库,然后创建一个数据访问层函数,调用自定义编写的SQL查询到数据库,然后创建一个调用的业务逻辑函数数据访问层功能。然后可能修改一堆现有函数以包含此更改。我甚至不知道在这样一个不稳定的环境中从哪里开始自动化测试。
这就是我想要修改或添加数据库的单个列。如果我想添加一个新表,那么可以添加一些新函数,用于所有可以选择的方式(WHERE子句的组合),或插入,更新,删除或排序等。
答案 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<TDto>"/> 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<TDto>"/> 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<TDto>"/> 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<TDto>"/> 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<TDto>"/> 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<TDto>"/> 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?处回答了另一个问题时,更深入地解释了实施外观模式。