mercredi 20 juillet 2016

c# Service design pattern implimentation

I am using the service / repository / unit of work design patterns in an application. Thus far this has always worked very well and allows me to separate my logic from POCO classes and repositories. Recently I tried to use the same pattern for use with an oracle database so I set up as follows:

public class Repository<T> : IRepository<T> where T : class, new()
{
    // private properties
    private readonly OracleCommand _command;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="command"></param>
    /// <param name="connection"></param>
    public Repository(OracleCommand command)
    {
        this._command = command;
    }

    /// <summary>
    /// Returns the datareader for the stored procedure
    /// </summary>
    /// <param name="storedProcedureName">The name of the SPROC to execute</param>
    /// <param name="parameters">The parameters needed for the SPROC</param>
    /// <returns></returns>
    public async Task<IDataReader> ExecuteReaderAsync(string storedProcedureName, IList<OracleParameter> parameters = null)
    {

        // Set up our command
        this.InitiateCommand(storedProcedureName, parameters.ToArray());

        // Return our data reader
        return await this._command.ExecuteReaderAsync();
    }

    /// <summary>
    /// Create, updates or deletes an entity
    /// </summary>
    /// <param name="storedProcedureName">The name of the SPROC to execute</param>
    /// <param name="parameters">The parameters needed for the SPROC</param>
    public async Task CreateUpdateOrDeleteAsync(string storedProcedureName, IList<OracleParameter> parameters = null)
    {

        // Set up our command
        this.InitiateCommand(storedProcedureName, parameters.ToArray());

        // Execute our command
        await this._command.ExecuteNonQueryAsync();
    }

    /// <summary>
    /// Intiates the command object
    /// </summary>
    /// <param name="storedProcedureName">The name of the SPROC to execute</param>
    /// <param name="parameters">An array of parameters</param>
    private void InitiateCommand(string storedProcedureName, OracleParameter[] parameters)
    {

        // Set up the command object
        this._command.CommandText = storedProcedureName;
        this._command.Parameters.Clear();

        // If we have any parameters
        if (parameters != null)
        {

            // Assign each parameter to the command object
            this._command.Parameters.AddRange(parameters);
        }
    }
}

The unit of work looked like this:

public class OracleUnitOfWork : IOracleUnitOfWork
{
    // Private properties
    private readonly OracleConnection _connection;
    private readonly OracleCommand _command;
    private readonly Dictionary<Type, object> _repositories;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="connectionStringName">The connection string name to use</param>
    public OracleUnitOfWork(string connectionStringName = "OracleConnection")
    {
        // Create instances for our private properties
        this._repositories = new Dictionary<Type, object>();
        this._connection = new OracleConnection(ConfigurationManager.ConnectionStrings[connectionStringName].ToString());
        this._command = new OracleCommand();

        // Set our command up
        this._command.Connection = this._connection;
        this._command.CommandType = CommandType.StoredProcedure;

        // Open our connection
        this._connection.Open();
    }

    /// <summary>
    /// Gets the entity repository
    /// </summary>
    /// <typeparam name="T">The entity model</typeparam>
    /// <returns></returns>
    public IRepository<T> GetRepository<T>() where T : class, new()
    {
        // If our repositories have a matching repository, return it
        if (_repositories.Keys.Contains(typeof(T)))
            return _repositories[typeof(T)] as IRepository<T>;

        // Create a new repository for our entity
        var repository = new Repository<T>(this._command);

        // Add to our list of repositories
        _repositories.Add(typeof(T), repository);

        // Return our repository
        return repository;
    }

    /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Disposes of any attached resources
    /// </summary>
    /// <param name="disposing">A boolean indicating whether the object is being disposed</param>
    protected virtual void Dispose(bool disposing)
    {

        // If we are disposing
        if (disposing)
        {

            // Close our connection
            this._connection.Close();
            this._connection.Dispose();
            this._command.Dispose();
        }
    }
}

So far so good, I then set up an abstract service like this:

public abstract class OracleService<T> where T : class, new()
{
    // Properties
    public IRepository<T> Repository { get; private set; }
    private readonly IOracleUnitOfWork _oracleUnitOfWork;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="oracleUnitOfWork">The Oracle UnitOfWork class</param>
    public OracleService(IOracleUnitOfWork oracleUnitOfWork)
    {

        // If not unit of work has been passed, throw an error
        ThrowIf.ArgumentIsNull(() => oracleUnitOfWork);

        // Add our references
        this._oracleUnitOfWork = oracleUnitOfWork;
        this.Repository = oracleUnitOfWork.GetRepository<T>();
    }
}

so then a typical service would look like this:

public class ProductService : OracleService<Product>
{

    /// <summary>
    /// Default Constructor
    /// </summary>
    /// <param name="oracleUnitOfWork">The Unit Of Work class for handling connections</param>
    public ProductService(IOracleUnitOfWork oracleUnitOfWork)
        : base(oracleUnitOfWork)
    {

    }

    /// <summary>
    /// Get a single Product entity
    /// </summary>
    /// <param name="productCode"></param>
    public async Task<Product> GetAsync(string productCode)
    {

        // Create our parameters
        var parameters = new List<OracleParameter>();
        parameters.Add(new OracleParameter("P_CUR", OracleDbType.RefCursor, ParameterDirection.Output));
        parameters.Add(new OracleParameter("P_PRODCODE", productCode));

        // Get our reader and return our model
        using (var reader = await Repository.ExecuteReaderAsync("ibpa_products.readProduct", parameters))
            while (reader.Read())
                return OracleMapper.Product.Map(reader);

        // Fallback
        return null;
    }

    /// <summary>
    /// Get a single Product entity
    /// </summary>
    /// <param name="search">Search criteria</param>
    /// <param name="accountNumber">The account number to search</param>
    /// <returns>Product Object</returns>
    public async Task<Product> GetAsync(string search, string accountNumber)
    {

        // Create our parameters
        var parameters = new List<OracleParameter>();
        parameters.Add(new OracleParameter("P_SEARCH", search));
        parameters.Add(new OracleParameter("P_ACCOUNT", accountNumber));

        // Get our reader and return our model
        using (var reader = await Repository.ExecuteReaderAsync("ibpa_products.productSearch", parameters))
            while (reader.Read())
                return OracleMapper.Product.Map(reader);

        // Fallback
        return null;
    }
}

When it is simple like that, everything is fine. But I have a need now to combine a few services. So I created a provider which looks something like this:

public class StockServiceProvider
{
    private readonly StockService _stockService;
    private readonly OrderServiceProvider _orderServiceProvider;
    private readonly ProductServiceProvider _productServiceProvider;
    private readonly ITroposUnitOfWork _troposUnitOfWork;

    /// <summary>
    /// Default constructor of the StockServiceProvider
    /// </summary>
    /// <param name="oracleUnitOfWork">The Unit Of Work class for handling connections</param>
    public StockServiceProvider(IOracleUnitOfWork oracleUnitOfWork, connectionType connectionType)
    {
        _stockService = new StockService(oracleUnitOfWork);

        _orderServiceProvider = new OrderServiceProvider(oracleUnitOfWork, _troposUnitOfWork, connectionType);
        _orderServiceProvider.OrderService.OrderLineService = new OrderLineService(oracleUnitOfWork);
        _productServiceProvider = new ProductServiceProvider(oracleUnitOfWork, connectionType);
    }

    /// <summary>
    /// Get a stock object async
    /// </summary>
    /// <param name="lotNumber">Lot number to fetch</param>
    /// <param name="includes">List of string paramaters to include with account object</param>
    /// <returns>Account Object</returns>
    public async Task<Stock> GetAsync(string lotNumber, params string[] includes)
    {

        // Get our model
        var model = await this._stockService.GetAsync(lotNumber);

        // If we don't find the model, throw an error
        if (model == null)
            throw new NullReferenceException($"{ lotNumber } does not exist in the stock table.");

        // Fake eager load
        await EagerLoader.LoadAsync(SetPropertyValue, model, includes);

        // Return our full model
        return model;
    }

    /// <summary>
    /// Sets the property value async
    /// </summary>
    /// <param name="model">The parent model</param>
    /// <param name="property">The current property</param>
    /// <param name="includes">List of params to include</param>
    private async Task SetPropertyValue(Stock model, PropertyInfo property, string[] includes)
    {

        // Switch on the property name
        switch (property.Name)
        {
            case "AllocatedItems":

                // Add allocated items to our model
                var orders = await _orderServiceProvider.OrderService.ListAllocatedAsync(model.LotNumber);

                //Assign the orders to allocated items. 
                model.AllocatedItems = orders;

                //Exit
                break;

            case "RelatedItems":

                // Add the related items to the 
                var relatedItems = await _stockService.ListAsync(model.LotNumber);
                model.RelatedItems = relatedItems.ToList();

                //Exit
                break;

            case "Product":

                // Add the product to the stock model.
                var product = await _productServiceProvider.GetAsync(model.ProductCode);
                model.Product = product;

                //Exit
                break;
            default:

                // Do nothing
                break;
        }
    }
}

There are a few issues I have with this.

  1. The services / providers are tightly bound because I am not injecting their interfaces into the constructor, but I am doing this on purpose because there is potential to be a lot of interfaces that would need to be injected
  2. I used the UserManager in Identity.Framework as an example and tried to do something similar in my services; i.e. it has public properties that are other "Managers" like the RoleManager. This is assigned whenever a role should be modified. The problem I have with this method is that my "providers" are where the logic happens so if I have multiple services instantiated in the constructor, it makes sense to assign the properties, but this negates the need of the properties because they will always be set...

Does anyone know of a design pattern or can help me better handle these problems?

Aucun commentaire:

Enregistrer un commentaire