То, что вы описываете, само по себе не является проблемой.На самом деле это хороший пример дизайна приложения и использования шаблонов.То, чего ему не хватает, заставляет его казаться проблематичным, так это то, что он не использует новые технологии / методы, которые помогают в обслуживании.
Например, из вашего описания очевидно, что архитектура четко разделяет функциональные обязанности на уровни.У вас есть презентация (UI), которая взаимодействует с доменом (BLL), который, в свою очередь, использует шаблон репозитория для взаимодействия со своей инфраструктурой (DAL).Кажется, что в вашем BLL уже реализованы сквозные задачи, такие как проверка и безопасность.
Что вы можете сделать, чтобы улучшить этот дизайн, так это включить более сильный Домен путем включения Модели.Отбросьте старые методы ADO.NET DataTable и разработайте строго типизированную модель, отражающую вашу базу данных.Внедрение ORM может очень помочь в этом, поскольку оно может генерировать модель из базы данных и легко вносить изменения.
Я не буду вдаваться в дальнейшие преимущества ORM, как вы пожелаете.Ваш DAL должен вернуть POCOs и 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<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.Для поддержки проверки он использует AutoMapper для преобразования результатов проверки Microsoft в мой собственный объект ValidationResult.Я не рекомендую пытаться сериализовать классы MS, так как они оказались подвержены ошибкам при использовании в сервисах.
Перегруженные конструкторы позволяют вам предоставлять одно poco или множество pocos.POCOs против DataTables ... каждый раз, когда вы можете использовать строго типизированные объекты, это всегда лучше.С T4 шаблонами ваш POCO может автоматически генерироваться из модели ORM.POCO также могут быть легко сопоставлены с DTO для сервисных операций и наоборот.Также больше нет необходимости в DataTables.Вместо List вы можете использовать BindingList для поддержки CRUD с привязкой данных.
Возврат POCO без заполнения всех его свойств - это прекрасно.В Entity Framework это называется проекцией.Обычно я создаю собственные 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 для перевода MicrosoftРезультаты проверки для результата проверки ValidationResult:
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()));