From 4279b05a8fe428c51b0cccc18d5aaf9f9096b7fd Mon Sep 17 00:00:00 2001 From: Motsosi Date: Sun, 2 Feb 2020 20:29:34 +0200 Subject: [PATCH] Tech test solution --- .../AnyCompany.Tests/AnyCompany.Tests.csproj | 41 ++--- TechTest/AnyCompany.Tests/Class1.cs | 12 -- TechTest/AnyCompany.Tests/Program.cs | 27 +++ TechTest/AnyCompany/AnyCompany.csproj | 14 +- TechTest/AnyCompany/CustomerRepository.cs | 33 ---- TechTest/AnyCompany/IOrderService.cs | 12 ++ TechTest/AnyCompany/{ => Model}/Customer.cs | 7 +- TechTest/AnyCompany/{ => Model}/Order.cs | 4 +- TechTest/AnyCompany/OrderRepository.cs | 25 --- TechTest/AnyCompany/OrderService.cs | 58 +++++-- .../Persistence/CustomerRepository.cs | 161 ++++++++++++++++++ .../Persistence/DataAccessObjectFactory.cs | 50 ++++++ .../Persistence/DataParameterHelper.cs | 55 ++++++ .../Persistence/IDataAccessObject.cs | 13 ++ .../AnyCompany/Persistence/OrderRepository.cs | 93 ++++++++++ .../Persistence/SqlDataAccessObject.cs | 42 +++++ TechTest/TechTest.sln | 4 +- 17 files changed, 543 insertions(+), 108 deletions(-) delete mode 100644 TechTest/AnyCompany.Tests/Class1.cs create mode 100644 TechTest/AnyCompany.Tests/Program.cs delete mode 100644 TechTest/AnyCompany/CustomerRepository.cs create mode 100644 TechTest/AnyCompany/IOrderService.cs rename TechTest/AnyCompany/{ => Model}/Customer.cs (55%) rename TechTest/AnyCompany/{ => Model}/Order.cs (83%) delete mode 100644 TechTest/AnyCompany/OrderRepository.cs create mode 100644 TechTest/AnyCompany/Persistence/CustomerRepository.cs create mode 100644 TechTest/AnyCompany/Persistence/DataAccessObjectFactory.cs create mode 100644 TechTest/AnyCompany/Persistence/DataParameterHelper.cs create mode 100644 TechTest/AnyCompany/Persistence/IDataAccessObject.cs create mode 100644 TechTest/AnyCompany/Persistence/OrderRepository.cs create mode 100644 TechTest/AnyCompany/Persistence/SqlDataAccessObject.cs diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index b537fc2..da78990 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -1,11 +1,11 @@ - + Debug AnyCPU - cd5d577e-bdc9-4dfc-ac6a-b1da474995f3 - Library + {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} + Exe Properties AnyCompany.Tests AnyCompany.Tests @@ -29,25 +29,28 @@ prompt 4 + + + - - - - - - - - - - - - - - + + + + + + + + - + + + + {c7e15594-7d8f-4c18-9dd7-14f3fbb1572d} + AnyCompany + + - + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/Class1.cs b/TechTest/AnyCompany.Tests/Class1.cs deleted file mode 100644 index 5957505..0000000 --- a/TechTest/AnyCompany.Tests/Class1.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AnyCompany.Tests -{ - public class Class1 - { - } -} diff --git a/TechTest/AnyCompany.Tests/Program.cs b/TechTest/AnyCompany.Tests/Program.cs new file mode 100644 index 0000000..05e291a --- /dev/null +++ b/TechTest/AnyCompany.Tests/Program.cs @@ -0,0 +1,27 @@ +using AnyCompany.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AnyCompany.Tests +{ + public class Program + { + static void Main(string[] args) + { + //Orderservice should be injected using a dependency injection container(e.g. Unity) + IOrderService orderService = new OrderService(); + + List customers = orderService.LoadAllCustomersAndOrders(); + + Order order = new Order + { + Amount = 22d + }; + + orderService.PlaceOrder(order, 1); + } + } +} diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index 5b0498d..ec07820 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -31,6 +31,7 @@ + @@ -40,11 +41,16 @@ - - - - + + + + + + + + + diff --git a/TechTest/AnyCompany/CustomerRepository.cs b/TechTest/AnyCompany/CustomerRepository.cs deleted file mode 100644 index e3de9b7..0000000 --- a/TechTest/AnyCompany/CustomerRepository.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Data.SqlClient; - -namespace AnyCompany -{ - public static class CustomerRepository - { - private static string ConnectionString = @"Data Source=(local);Database=Customers;User Id=admin;Password=password;"; - - public static Customer Load(int customerId) - { - Customer customer = new Customer(); - - SqlConnection connection = new SqlConnection(ConnectionString); - connection.Open(); - - SqlCommand command = new SqlCommand("SELECT * FROM Customer WHERE CustomerId = " + customerId, - connection); - var reader = command.ExecuteReader(); - - while (reader.Read()) - { - customer.Name = reader["Name"].ToString(); - customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); - customer.Country = reader["Country"].ToString(); - } - - connection.Close(); - - return customer; - } - } -} diff --git a/TechTest/AnyCompany/IOrderService.cs b/TechTest/AnyCompany/IOrderService.cs new file mode 100644 index 0000000..bce55cd --- /dev/null +++ b/TechTest/AnyCompany/IOrderService.cs @@ -0,0 +1,12 @@ +using AnyCompany.Model; +using System.Collections.Generic; + +namespace AnyCompany +{ + public interface IOrderService + { + bool PlaceOrder(Order order, int customerId); + + List LoadAllCustomersAndOrders(); + } +} diff --git a/TechTest/AnyCompany/Customer.cs b/TechTest/AnyCompany/Model/Customer.cs similarity index 55% rename from TechTest/AnyCompany/Customer.cs rename to TechTest/AnyCompany/Model/Customer.cs index aa994b6..59ee8f1 100644 --- a/TechTest/AnyCompany/Customer.cs +++ b/TechTest/AnyCompany/Model/Customer.cs @@ -1,13 +1,18 @@ using System; +using System.Collections.Generic; -namespace AnyCompany +namespace AnyCompany.Model { public class Customer { + public int CustomerId { get; set; } + public string Country { get; set; } public DateTime DateOfBirth { get; set; } public string Name { get; set; } + + public List Orders { get; set; } } } diff --git a/TechTest/AnyCompany/Order.cs b/TechTest/AnyCompany/Model/Order.cs similarity index 83% rename from TechTest/AnyCompany/Order.cs rename to TechTest/AnyCompany/Model/Order.cs index fec8e7b..294ad17 100644 --- a/TechTest/AnyCompany/Order.cs +++ b/TechTest/AnyCompany/Model/Order.cs @@ -1,9 +1,11 @@ -namespace AnyCompany +namespace AnyCompany.Model { public class Order { public int OrderId { get; set; } + public double Amount { get; set; } + public double VAT { get; set; } } } diff --git a/TechTest/AnyCompany/OrderRepository.cs b/TechTest/AnyCompany/OrderRepository.cs deleted file mode 100644 index 3229885..0000000 --- a/TechTest/AnyCompany/OrderRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Data.SqlClient; - -namespace AnyCompany -{ - internal class OrderRepository - { - private static string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; - - public void Save(Order order) - { - SqlConnection connection = new SqlConnection(ConnectionString); - connection.Open(); - - SqlCommand command = new SqlCommand("INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT)", connection); - - command.Parameters.AddWithValue("@OrderId", order.OrderId); - command.Parameters.AddWithValue("@Amount", order.Amount); - command.Parameters.AddWithValue("@VAT", order.VAT); - - command.ExecuteNonQuery(); - - connection.Close(); - } - } -} diff --git a/TechTest/AnyCompany/OrderService.cs b/TechTest/AnyCompany/OrderService.cs index ebfb103..417d31b 100644 --- a/TechTest/AnyCompany/OrderService.cs +++ b/TechTest/AnyCompany/OrderService.cs @@ -1,24 +1,60 @@ -namespace AnyCompany +using AnyCompany.Model; +using System; +using System.Collections.Generic; + +namespace AnyCompany { - public class OrderService + public class OrderService : IOrderService { private readonly OrderRepository orderRepository = new OrderRepository(); + public List LoadAllCustomersAndOrders() + { + try + { + List customers = CustomerRepository.LoadAllCustomers(); + + return customers; + } + catch (Exception ex) + { + throw new Exception("Failed to load customers.", ex); + } + } + public bool PlaceOrder(Order order, int customerId) { - Customer customer = CustomerRepository.Load(customerId); + if (order == null) + throw new ArgumentNullException(); + + try + { + Customer customer = CustomerRepository.Load(customerId); + + if (order.Amount == 0) + return false; - if (order.Amount == 0) - return false; + if (customer == null) + return false; - if (customer.Country == "UK") - order.VAT = 0.2d; - else - order.VAT = 0; + switch (customer.Country) + { + case "UK": + order.VAT = 0.2d; + break; + default: + order.VAT = 0; + break; + } - orderRepository.Save(order); + orderRepository.Save(order, customerId); - return true; + return true; + } + catch (Exception ex) + { + throw new Exception($"Failed to save order for customer with customer ID {customerId}", ex); + } } } } diff --git a/TechTest/AnyCompany/Persistence/CustomerRepository.cs b/TechTest/AnyCompany/Persistence/CustomerRepository.cs new file mode 100644 index 0000000..3b80639 --- /dev/null +++ b/TechTest/AnyCompany/Persistence/CustomerRepository.cs @@ -0,0 +1,161 @@ +using AnyCompany.Model; +using AnyCompany.Persistence; +using System; +using System.Collections.Generic; + +namespace AnyCompany +{ + public static class CustomerRepository + { + private static string ConnectionString = @"Data Source=(local);Database=Customers;User Id=admin;Password=password;"; + + private static OrderRepository _orderRepository; + private static IDataAccessObject _dataAccessObject; + private static DataAccessObjectFactory _dataAccessObjectFactory; + + private static OrderRepository OrderRepository + { + get + { + if (_orderRepository == null) + { + _orderRepository = new OrderRepository(); + } + + return _orderRepository; + } + } + + private static IDataAccessObject DataAccessObject + { + get + { + if (_dataAccessObject == null) + { + _dataAccessObject = DataAccessObjectFactory.CreateDataAccess(); + } + + return _dataAccessObject; + } + } + + private static DataAccessObjectFactory DataAccessObjectFactory + { + get + { + if (_dataAccessObjectFactory == null) + { + _dataAccessObjectFactory = new DataAccessObjectFactory(ConnectionString); + } + + return _dataAccessObjectFactory; + } + } + + public static Customer Load(int customerId) + { + Customer customer = new Customer(); + + using (var connection = DataAccessObject.CreateConnection()) + { + connection.Open(); + + string commandString = $"SELECT* FROM Customer WHERE CustomerId = {customerId}"; + + var command = DataAccessObject.CreateCommand(commandString, System.Data.CommandType.Text, connection); + + var reader = command.ExecuteReader(); + + while (reader.Read()) + + { + customer.CustomerId = int.Parse(reader["CustomerId"].ToString()); + customer.Name = reader["Name"].ToString(); + customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); + customer.Country = reader["Country"].ToString(); + } + + DataAccessObject.CloseConnection(connection); + } + + return customer; + } + + public static Customer LoadCustomerAndOrders(int customerId) + { + Customer customer = Load(customerId); + + if (!string.IsNullOrEmpty(customer.Name)) + { + LoadCustomerOrders(customer); + } + else { return null; } + + return customer; + } + + private static void LoadCustomerOrders(Customer customer) + { + customer.Orders = OrderRepository.GetCustomerOrders(customer.CustomerId); + } + + public static void Save(Customer customer) + { + using (var connection = DataAccessObject.CreateConnection()) + { + connection.Open(); + + string commandString = "INSERT INTO Customer VALUES (@Name, @DateOfBirth, @Country)"; + + var command = DataAccessObject.CreateCommand(commandString, System.Data.CommandType.Text, connection); + + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@Name", 150, customer.Name, System.Data.DbType.String)); + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@DateOfBirth", customer.DateOfBirth, System.Data.DbType.Date)); + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@Country", 150, customer.Country, System.Data.DbType.String)); + + command.ExecuteNonQuery(); + + DataAccessObject.CloseConnection(connection); + } + } + + public static List LoadAllCustomers() + { + List customers = new List(); + + using (var connection = DataAccessObject.CreateConnection()) + { + connection.Open(); + + string commandString = $"SELECT * FROM Customer"; + + var command = DataAccessObject.CreateCommand(commandString, System.Data.CommandType.Text, connection); + + var reader = command.ExecuteReader(); + + while (reader.Read()) + + { + var customer = new Customer + { + CustomerId = int.Parse(reader["CustomerId"].ToString()), + Name = reader["Name"].ToString(), + DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()), + Country = reader["Country"].ToString() + }; + + customers.Add(customer); + } + + DataAccessObject.CloseConnection(connection); + } + + foreach (var customer in customers) + { + LoadCustomerOrders(customer); + } + + return customers; + } + } +} diff --git a/TechTest/AnyCompany/Persistence/DataAccessObjectFactory.cs b/TechTest/AnyCompany/Persistence/DataAccessObjectFactory.cs new file mode 100644 index 0000000..afdb9d1 --- /dev/null +++ b/TechTest/AnyCompany/Persistence/DataAccessObjectFactory.cs @@ -0,0 +1,50 @@ +using System; +using System.Configuration; + +namespace AnyCompany.Persistence +{ + class DataAccessObjectFactory + { + private string _connectionString; + private ConnectionStringSettings _connectionStringSettings; + + public DataAccessObjectFactory(ConnectionStringSettings connectionStringSettings) + { + if (connectionStringSettings == null) + throw new ArgumentNullException(); + + _connectionStringSettings = connectionStringSettings; + } + + public DataAccessObjectFactory(string connectionString) + { + if (string.IsNullOrEmpty(connectionString)) + throw new ArgumentNullException(); + + _connectionString = connectionString; + } + + public IDataAccessObject CreateDataAccess() + { + IDataAccessObject databaseObject; + + if (_connectionStringSettings != null) + { + switch (_connectionStringSettings.ProviderName.ToLower()) + { + case "system.data.sqlclient": + default: + databaseObject = new SqlDataAccessObject(_connectionStringSettings.ConnectionString); + break; + } + } + else + { + //Default to Sql Client connection. More work can be done to attempt to find the correct provider and create the correct object + databaseObject = new SqlDataAccessObject(_connectionString); + } + + return databaseObject; + } + } +} diff --git a/TechTest/AnyCompany/Persistence/DataParameterHelper.cs b/TechTest/AnyCompany/Persistence/DataParameterHelper.cs new file mode 100644 index 0000000..4480d97 --- /dev/null +++ b/TechTest/AnyCompany/Persistence/DataParameterHelper.cs @@ -0,0 +1,55 @@ +using System.Data; +using System.Data.SqlClient; + +namespace AnyCompany.Persistence +{ + class DataParameterHelper + { + public static IDbDataParameter CreateParameter(string providerName, string name, object value, DbType dbType, ParameterDirection direction = ParameterDirection.Input) + { + switch (providerName.ToLower()) + { + case "system.data.sqlclient": + default: + return CreateSqlParameter(name, value, dbType, direction); + } + } + + public static IDbDataParameter CreateParameter(string providerName, string name, int size, object value, DbType dbType, ParameterDirection direction = ParameterDirection.Input) + { + switch (providerName.ToLower()) + { + case "system.data.sqlclient": + default: + return CreateSqlParameter(name, size, value, dbType, direction); + } + } + + private static IDbDataParameter CreateSqlParameter(string name, object value, DbType dbType, ParameterDirection direction) + { + SqlParameter sqlParameter = new SqlParameter + { + DbType = dbType, + ParameterName = name, + Direction = direction, + Value = value + }; + + return sqlParameter; + } + + private static IDbDataParameter CreateSqlParameter(string name, int size, object value, DbType dbType, ParameterDirection direction) + { + SqlParameter sqlParameter = new SqlParameter + { + DbType = dbType, + Size = size, + ParameterName = name, + Direction = direction, + Value = value + }; + + return sqlParameter; + } + } +} diff --git a/TechTest/AnyCompany/Persistence/IDataAccessObject.cs b/TechTest/AnyCompany/Persistence/IDataAccessObject.cs new file mode 100644 index 0000000..7bdc38e --- /dev/null +++ b/TechTest/AnyCompany/Persistence/IDataAccessObject.cs @@ -0,0 +1,13 @@ +using System.Data; + +namespace AnyCompany.Persistence +{ + interface IDataAccessObject + { + IDbConnection CreateConnection(); + + void CloseConnection(IDbConnection connection); + + IDbCommand CreateCommand(string commandText, CommandType commandType, IDbConnection connection); + } +} diff --git a/TechTest/AnyCompany/Persistence/OrderRepository.cs b/TechTest/AnyCompany/Persistence/OrderRepository.cs new file mode 100644 index 0000000..9520ecb --- /dev/null +++ b/TechTest/AnyCompany/Persistence/OrderRepository.cs @@ -0,0 +1,93 @@ +using AnyCompany.Model; +using AnyCompany.Persistence; +using System.Collections.Generic; +using System.Data.SqlClient; + +namespace AnyCompany +{ + internal class OrderRepository + { + private static string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; + + private IDataAccessObject _dataAccessObject; + private DataAccessObjectFactory _dataAccessObjectFactory; + + private IDataAccessObject DataAccessObject + { + get + { + if (_dataAccessObject == null) + { + _dataAccessObject = DataAccessObjectFactory.CreateDataAccess(); + } + + return _dataAccessObject; + } + } + + private DataAccessObjectFactory DataAccessObjectFactory + { + get + { + if (_dataAccessObjectFactory == null) + { + _dataAccessObjectFactory = new DataAccessObjectFactory(ConnectionString); + } + + return _dataAccessObjectFactory; + } + } + + public void Save(Order order, int customerId) + + { + using (var connection = DataAccessObject.CreateConnection()) + { + connection.Open(); + + string commandString = "INSERT INTO [Orders] VALUES (@CustomerId, @Amount, @VAT)"; + + var command = DataAccessObject.CreateCommand(commandString, System.Data.CommandType.Text, connection); + + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@CustomerId", customerId, System.Data.DbType.Int32)); + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@Amount", order.Amount, System.Data.DbType.Double)); + command.Parameters.Add(DataParameterHelper.CreateParameter(string.Empty, "@VAT", order.VAT, System.Data.DbType.Double)); + + command.ExecuteNonQuery(); + + DataAccessObject.CloseConnection(connection); + } + } + + public List GetCustomerOrders(int customerId) + { + var orders = new List(); + + using (var connection = DataAccessObject.CreateConnection()) + { + connection.Open(); + + string commandString = $"SELECT * FROM [Orders] WHERE CustomerId = {customerId}"; + + var command = DataAccessObject.CreateCommand(commandString, System.Data.CommandType.Text, connection); + + var reader = command.ExecuteReader(); + + while (reader.Read()) + { + var order = new Order + { + Amount = double.Parse(reader["Amount"].ToString()), + VAT = double.Parse(reader["VAT"].ToString()), + OrderId = int.Parse(reader["OrderId"].ToString()) + }; + orders.Add(order); + } + + DataAccessObject.CloseConnection(connection); + } + + return orders; + } + } +} diff --git a/TechTest/AnyCompany/Persistence/SqlDataAccessObject.cs b/TechTest/AnyCompany/Persistence/SqlDataAccessObject.cs new file mode 100644 index 0000000..be7026d --- /dev/null +++ b/TechTest/AnyCompany/Persistence/SqlDataAccessObject.cs @@ -0,0 +1,42 @@ +using System.Data; +using System.Data.SqlClient; + +namespace AnyCompany.Persistence +{ + class SqlDataAccessObject : IDataAccessObject + { + private string _connectionString; + + public SqlDataAccessObject(string connectionString) + { + _connectionString = connectionString; + } + + public void CloseConnection(IDbConnection connection) + { + var sqlConnection = (SqlConnection)connection; + + sqlConnection.Close(); + sqlConnection.Dispose(); + } + + public IDbCommand CreateCommand(string commandText, CommandType commandType, IDbConnection connection) + { + SqlCommand sqlCommand = new SqlCommand + { + CommandText = commandText, + Connection = (SqlConnection)connection, + CommandType = commandType + }; + + return sqlCommand; + } + + public IDbConnection CreateConnection() + { + SqlConnection sqlConnection = new SqlConnection(_connectionString); + + return sqlConnection; + } + } +} diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 1c1c57a..d49b783 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29728.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany", "AnyCompany\AnyCompany.csproj", "{C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}" EndProject