From 1ba70879659a0d91fee4516d35de4d7c86049caf Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Mon, 12 Sep 2016 20:28:55 -0700 Subject: [PATCH] Advanced further with Repositories and Dynamic-Queries --- global.json | 3 +- .../Controllers/OrderingController.cs | 114 ++++++++++++++++++ .../Controllers/OrdersController.cs | 81 ------------- .../Ordering/Ordering.API/DTO/OrderDTO.cs | 11 -- .../Ordering/Ordering.API/DTO/OrderItemDTO.cs | 26 ---- ...0160909202620_MyFirstMigration.Designer.cs | 4 +- .../20160909223213_Migration2.Designer.cs | 4 +- .../20160909233852_Migration3.Designer.cs | 4 +- .../OrderingContextModelSnapshot.cs | 4 +- .../Properties/launchSettings.json | 2 +- src/Services/Ordering/Ordering.API/Startup.cs | 11 +- .../UnitOfWork/OrderingContext.cs | 19 --- .../Ordering/Ordering.API/project.json | 3 +- .../RepositoryContracts/IBuyerRepository.cs | 6 +- .../RepositoryContracts/IOrderRepository.cs | 5 +- .../Ordering.Domain/SeedWork/IRepository.cs | 84 ++----------- .../Ordering.Domain/SeedWork/IUnitOfWork.cs | 46 ------- .../Queries/OrderingQueries.cs | 45 +++++++ .../Repositories/OrderRepository.cs | 40 ++++++ .../Ordering.SqlData/SeedWork/Repository.cs | 77 +++++++++++- .../UnitOfWork/OrderingDbContext.cs | 31 +++++ .../Ordering/Ordering.SqlData/project.json | 7 +- .../Ordering.Test/DataIntegrationTests.cs | 104 ++++++++++++++++ .../Ordering.Test/Ordering.Test.xproj | 4 +- test/Services/Ordering.Test/Tests.cs | 14 --- test/Services/Ordering.Test/project.json | 6 +- 26 files changed, 461 insertions(+), 294 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs delete mode 100644 src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs delete mode 100644 src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs delete mode 100644 src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs delete mode 100644 src/Services/Ordering/Ordering.API/UnitOfWork/OrderingContext.cs delete mode 100644 src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs create mode 100644 src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs create mode 100644 src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs create mode 100644 src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs create mode 100644 test/Services/Ordering.Test/DataIntegrationTests.cs delete mode 100644 test/Services/Ordering.Test/Tests.cs diff --git a/global.json b/global.json index c7e3a898f..c4b6d22ad 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,8 @@ { "projects": [ "src", - "test" + "test", + "src/Services/Ordering" ], "sdk": { diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs new file mode 100644 index 000000000..a439925d4 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries; + +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers +{ + [Route("api/[controller]")] + public class OrderingController : Controller + { + private OrderingDbContext _context; + private IOrderRepository _orderRepository; + private OrderingQueries _queries; + + public OrderingController(IOrderRepository orderRepository, + OrderingQueries orderingQueries, + OrderingDbContext context) + { + //Injected objects from the IoC container + _orderRepository = orderRepository; + _queries = orderingQueries; + + _context = context; + } + + + // GET api/ordering/orders + [HttpGet("orders")] + public async Task GetAllOrders() + { + dynamic response = await _queries.GetAllOrdersIncludingValueObjectsAndChildEntities(); + return Ok(response); + } + + + // GET api/ordering/orders/xxxGUIDxxxx + [HttpGet("orders/{orderId:Guid}")] + public async Task GetOrderByGuid(Guid orderId) + { + //var order = await _orderRepository.Get(orderId); + + var order = await _context.Orders + .Include(o => o.ShippingAddress) + .Include(o => o.BillingAddress) + .Where(o => o.Id == orderId) + .SingleOrDefaultAsync(); + + // Dynamically generated a Response-Model that includes only the fields you need in the response. + // This keeps the JSON response minimal. + // Could also use var + dynamic response = new + { + id = order.Id, + orderDate = order.OrderDate, + shippingAddress = order.ShippingAddress, + billingAddress = order.BillingAddress, + //items = order.Items.Select(i => i.Content) + }; + + return Ok(response); + } + + //Alternate method if using several parameters instead of part of the URL + // GET api/ordering/orders/?orderId=xxxGUIDxxx&otherParam=value + //[HttpGet("orders")] + //public Order GetOrderByGuid([FromUri] Guid orderId, [FromUri] string otherParam) + + + // POST api/ordering/orders/create + [HttpPut("orders/create")] + public async Task Post([FromBody]Order order) + { + return await _orderRepository.Add(order); + + //_context.Orders.Add(order); + //return await _context.SaveChangesAsync(); + } + + // PUT api/ordering/orders/xxxOrderGUIDxxxx/update + [HttpPut("orders/{orderId:Guid}/update")] + public async Task UpdateOrder(Guid orderID, [FromBody] Order orderToUpdate) + { + return await _orderRepository.Add(orderToUpdate); + + //_context.Orders.Update(orderToUpdate); + //return await _context.SaveChangesAsync(); + } + + // DELETE api/ordering/orders/xxxOrderGUIDxxxx + [HttpDelete("orders/{orderId:Guid}/delete")] + public async Task Delete(Guid id) + { + return await _orderRepository.Remove(id); + + //Order orderToDelete = _context.Orders + // .Where(o => o.Id == id) + // .SingleOrDefault(); + + //_context.Orders.Remove(orderToDelete); + //return await _context.SaveChangesAsync(); + } + + } + +} + + diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs deleted file mode 100644 index a42dad001..000000000 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; - - -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers -{ - [Route("api/[controller]")] - public class OrdersController : Controller - { - private OrderingContext _context; - - public OrdersController(OrderingContext context) - { - //Injected DbContext from the IoC container - _context = context; - } - - // GET api/orders - [HttpGet] - public IEnumerable Get() - { - //Create generic Address ValueObject - Address sampleAddress = new Address("15703 NE 61st Ct.", - "Redmond", - "Washington", - "WA", - "United States", - "US", - "98052", - 47.661492, - -122.131309 - ); - - //Create sample Orders - Order order1 = new Order(Guid.NewGuid(), sampleAddress, sampleAddress); - _context.Orders.Add(order1); - _context.SaveChanges(); - - return _context.Orders.ToList(); - } - - // GET api/orders/xxxGUIDxxxx - [HttpGet("{id}")] - public string Get(Guid id) - { - return "value TBD"; - } - - // POST api/orders - [HttpPost] - public void Post([FromBody]Order order) - { - _context.Orders.Add(order); - _context.SaveChanges(); - } - - // PUT api/orders/xxxGUIDxxxx - [HttpPut("{id}")] - public void Put(int id, [FromBody]Order value) - { - - } - - // DELETE api/orders/xxxGUIDxxxx - [HttpDelete("{id}")] - public void Delete(Guid id) - { - - } - - } - -} - - diff --git a/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs b/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs deleted file mode 100644 index 39abc372f..000000000 --- a/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Ordering.API.DTO -{ - public class OrderDTO - { - } -} diff --git a/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs b/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs deleted file mode 100644 index 82bdb25b1..000000000 --- a/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.Serialization; - - -namespace Microsoft.eShopOnContainers.Services.Ordering.API.DTO -{ - [DataContract] - public sealed class OrderItemDTO - { - public OrderItemDTO() { } - - [DataMember] - public Guid Id { get; set; } - - [DataMember] - public int Quantity { get; set; } - - [DataMember] - public int FulfillmentRemaining { get; set; } - - public override string ToString() - { - return String.Format("ID: {0}, Quantity: {1}, Fulfillment Remaing: {2}", this.Id, this.Quantity, this.FulfillmentRemaining); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs index 02244954a..7482543fa 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs @@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { - [DbContext(typeof(OrderingContext))] + [DbContext(typeof(OrderingDbContext))] [Migration("20160909202620_MyFirstMigration")] partial class MyFirstMigration { diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs index 5287cfba2..e2be6d29d 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs @@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { - [DbContext(typeof(OrderingContext))] + [DbContext(typeof(OrderingDbContext))] [Migration("20160909223213_Migration2")] partial class Migration2 { diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs index fc54e2c3c..cfccdd048 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs @@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { - [DbContext(typeof(OrderingContext))] + [DbContext(typeof(OrderingDbContext))] [Migration("20160909233852_Migration3")] partial class Migration3 { diff --git a/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs b/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs index 01ea680c2..739e05927 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs @@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { - [DbContext(typeof(OrderingContext))] + [DbContext(typeof(OrderingDbContext))] partial class OrderingContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) diff --git a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json index 7af64c719..e45264b5a 100644 --- a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json +++ b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json @@ -11,7 +11,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "api/orders", + "launchUrl": "api/ordering/orders", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 33a7c1027..b29d95e94 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -8,8 +8,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries; namespace Microsoft.eShopOnContainers.Services.Ordering.API { @@ -36,7 +39,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API //Add EF Core Context (UnitOfWork) var connection = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; - services.AddDbContext(options => options.UseSqlServer(connection)); + services.AddDbContext(options => options.UseSqlServer(connection)); + + services.AddTransient(); + services.AddTransient(); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Ordering/Ordering.API/UnitOfWork/OrderingContext.cs b/src/Services/Ordering/Ordering.API/UnitOfWork/OrderingContext.cs deleted file mode 100644 index 83d2bd3cf..000000000 --- a/src/Services/Ordering/Ordering.API/UnitOfWork/OrderingContext.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; - -namespace Microsoft.eShopOnContainers.Services.Ordering.API.UnitOfWork -{ - public class OrderingContext : DbContext - { - public OrderingContext(DbContextOptions options) - : base(options) - { } - - public DbSet Orders { get; set; } - - } -} diff --git a/src/Services/Ordering/Ordering.API/project.json b/src/Services/Ordering/Ordering.API/project.json index c50d2bd42..8e0ffa433 100644 --- a/src/Services/Ordering/Ordering.API/project.json +++ b/src/Services/Ordering/Ordering.API/project.json @@ -18,7 +18,8 @@ "Microsoft.EntityFrameworkCore": "1.0.0", "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0", "Ordering.Domain": "1.0.0-*", - "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final" + "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", + "Ordering.SqlData": "1.0.0-*" }, "tools": { diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs index ae6756f19..826cf0e0d 100644 --- a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs @@ -3,9 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; + namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts { - interface IBuyerRepository + public interface IBuyerRepository : IRepository { + //TBD - To define Specific Actions Not In Base Repo } } diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs index bcb55f171..70e6fd377 100644 --- a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs @@ -10,8 +10,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContrac { public interface IOrderRepository : IRepository { - Order Get(int associatedConferenceId); + //TBD - To define Specific Actions Not In Base Repo - void ModifyOrder(Order seatOrder); + Task Remove(Guid id); } + } diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs index e2a7f43ba..fb19b8da1 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs @@ -6,88 +6,24 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork { - /// - /// Base interface for implement a "Repository Pattern", for + /// Base interface to implement a "Repository Pattern", for /// more information about this pattern see http://martinfowler.com/eaaCatalog/repository.html /// or http://blogs.msdn.com/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx /// /// - /// Indeed, DbSet is already a generic repository and therefore you would not need a Repo. - /// But using this Repo interface allows us to ensure PI (Persistence Ignorant) principle - /// within our domain model + /// Indeed, DbSet is already a generic repository and for Data-Driven apps + /// you might not need custom Repositories. + /// But using this interface allows us to ensure PI (Persistence Ignorance) principle + /// from the Domain and Application code /// /// Type of entity for this repository public interface IRepository : IDisposable - where TEntity : Entity + where TEntity : AggregateRoot //1:1 relationship between Repository and AggregateRoot { - /// - /// Get the unit of work in this repository - /// - IUnitOfWork UnitOfWork { get; } - - /// - /// Add item into repository - /// - /// Item to add to repository - void Add(TEntity item); - - /// - /// Delete item - /// - /// Item to delete - void Remove(TEntity item); - - /// - /// Set item as modified - /// - /// Item to modify - void Modify(TEntity item); - - /// - ///Track entity into this repository, really in UnitOfWork. - ///In EF this can be done with Attach and with Update in NH - /// - /// Item to attach - void TrackItem(TEntity item); - - /// - /// Sets modified entity into the repository. - /// When calling Commit() method in UnitOfWork - /// these changes will be saved into the storage - /// - /// The persisted item - /// The current item - void Merge(TEntity persisted, TEntity current); - - /// - /// Get element by entity key - /// - /// Entity key value - /// - TEntity Get(Guid id); - - /// - /// Get all elements of type TEntity in repository - /// - /// List of selected elements - IEnumerable GetAll(); - - /// - /// Get all elements of type TEntity in repository - /// - /// Page index - /// Number of elements in each page - /// Order by expression for this query - /// Specify if order is ascending - /// List of selected elements - IEnumerable GetPaged(int pageIndex, int pageCount, Expression> orderByExpression, bool ascending); - - /// - /// Get elements of type TEntity in repository - /// - /// Filter that each element do match - /// List of selected elements - IEnumerable GetFiltered(Expression> filter); + Task Add(TEntity item); + Task Remove(TEntity item); + Task Update(TEntity item); + Task Get(Guid id); } } diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs deleted file mode 100644 index e9c2f3935..000000000 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork -{ - /// - /// Contract for ‘UnitOfWork pattern’. For more - /// related info see http://martinfowler.com/eaaCatalog/unitOfWork.html or - /// http://msdn.microsoft.com/en-us/magazine/dd882510.aspx - /// In this Microservice, the Unit Of Work is implemented using the out-of-box - /// Entity Framework Context (EF Core DbContext) persistence engine. But in order to - /// comply the PI (Persistence Ignorant) principle in our Domain, we implement this interface/contract. - /// This interface/contract should be complied by any UoW implementation to be used with this Domain. - /// - public interface IUnitOfWork - : IDisposable - { - /// - /// Commit all changes made in a container. - /// - /// - /// If the entity have fixed properties and any optimistic concurrency problem exists, - /// then an exception is thrown - /// - void Commit(); - - /// - /// Commit all changes made in a container. - /// - /// - /// If the entity have fixed properties and any optimistic concurrency problem exists, - /// then 'client changes' are refreshed - Client wins - /// - void CommitAndRefreshChanges(); - - - /// - /// Rollback tracked changes. See references of UnitOfWork pattern - /// - void RollbackChanges(); - - } -} - diff --git a/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs b/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs new file mode 100644 index 000000000..de3a44f56 --- /dev/null +++ b/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs @@ -0,0 +1,45 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries +{ + public class OrderingQueries + { + private OrderingDbContext _dbContext; + + public OrderingQueries(OrderingDbContext orderingDbContext) + { + _dbContext = orderingDbContext; + } + + public async Task GetAllOrdersIncludingValueObjectsAndChildEntities() + { + var orders = await _dbContext.Orders + .Include(o => o.ShippingAddress) + .Include(o => o.BillingAddress) + //.Include(o => o.Items) + .ToListAsync(); + + // Dynamically generated a Response-Model that includes only the fields you need in the response. + // This keeps the JSON response minimal. + // Could also use var + dynamic response = orders.Select(o => new + { + id = o.Id, + orderDate = o.OrderDate, + shippingAddress = o.ShippingAddress, + billingAddress = o.BillingAddress, + //items = o.Items.Select(i => i.Content) + }); + + return response; + } + } +} diff --git a/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs b/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs new file mode 100644 index 000000000..4abe17b2f --- /dev/null +++ b/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.SeedWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories +{ + //1:1 relationship between Repository and Aggregate (i.e. OrderRepository and Order) + public class OrderRepository + : Repository, IOrderRepository + { + public OrderRepository(OrderingDbContext unitOfWork) + : base(unitOfWork) { } + + //TBD - To define Specific Actions Not In Base Repository class + + public async Task Remove(Guid orderId) + { + if (orderId == null) + return 0; + + Order orderToDelete = await this.Get(orderId); + + + //attach item if not exist + _unitOfWork.Attach(orderToDelete); + + //set as "removed" + _unitOfWork.Remove(orderToDelete); + + return await _unitOfWork.SaveChangesAsync(); + } + } + +} diff --git a/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs b/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs index 0820bb181..539138930 100644 --- a/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs +++ b/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs @@ -1,11 +1,82 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.SeedWork +using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.SeedWork { - public class Repository + public class Repository : IRepository + where TEntity : AggregateRoot //1:1 relationship between Repository and AggregateRoot { + protected readonly DbContext _unitOfWork; + + //DbContext injected thru DI from ASP.NET Core bootstrap + public Repository(DbContext unitOfWork) + { + if (unitOfWork == null) + throw new ArgumentNullException("unitOfWork"); + + _unitOfWork = unitOfWork; + } + + public DbContext UnitOfWork + { + get + { + return _unitOfWork; + } + } + + public virtual async Task Add(TEntity item) + { + if (item == null) + return 0; + + _unitOfWork.Set().Add(item); // add new item in this set + + return await _unitOfWork.SaveChangesAsync(); + } + + public virtual async Task Remove(TEntity item) + { + if (item == null) + return 0; + + //attach item if not exist + _unitOfWork.Set().Attach(item); + + //set as "removed" + _unitOfWork.Set().Remove(item); + + return await _unitOfWork.SaveChangesAsync(); + } + + public virtual async Task Update(TEntity item) + { + if (item == null) + return 0; + + _unitOfWork.Set().Update(item); + + return await _unitOfWork.SaveChangesAsync(); + } + + public virtual async Task Get(Guid id) + { + if (id != Guid.Empty) + return await _unitOfWork.Set().FirstOrDefaultAsync(o => o.Id == id); + else + return null; + } + + public void Dispose() + { + if (_unitOfWork != null) + _unitOfWork.Dispose(); + } + } } diff --git a/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs new file mode 100644 index 000000000..8cfb231a6 --- /dev/null +++ b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork +{ + public class OrderingDbContext : DbContext + { + public OrderingDbContext(DbContextOptions options) + : base(options) + { } + + public DbSet Orders { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + //If running from ASP.NET Core, config is done at StartUp.cs --> ConfigureServices() outside + //and injected through DI later on. The following config is used when running Tests or similar contexts + if (!optionsBuilder.IsConfigured) + { + //SQL LOCALDB + optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"); + + } + + } + } +} diff --git a/src/Services/Ordering/Ordering.SqlData/project.json b/src/Services/Ordering/Ordering.SqlData/project.json index 864b9a5f3..fd7c6dee5 100644 --- a/src/Services/Ordering/Ordering.SqlData/project.json +++ b/src/Services/Ordering/Ordering.SqlData/project.json @@ -1,8 +1,11 @@ -{ +{ "version": "1.0.0-*", "dependencies": { - "NETStandard.Library": "1.6.0" + "Microsoft.EntityFrameworkCore": "1.0.0", + "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", + "NETStandard.Library": "1.6.0", + "Ordering.Domain": "1.0.0-*" }, "frameworks": { diff --git a/test/Services/Ordering.Test/DataIntegrationTests.cs b/test/Services/Ordering.Test/DataIntegrationTests.cs new file mode 100644 index 000000000..b2edcf2d4 --- /dev/null +++ b/test/Services/Ordering.Test/DataIntegrationTests.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Xunit; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DataIntegrationTests +{ + //Basic documentation for Testing EF Core classes + // http://ef.readthedocs.io/en/latest/miscellaneous/testing.html + public class Tests + { + private static DbContextOptions CreateNewContextOptionsForInMemoryDB() + { + // Create a fresh service provider, and therefore a fresh + // InMemory database instance. + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkInMemoryDatabase() + .BuildServiceProvider(); + + // Create a new options instance telling the context to use an + // InMemory database and the new service provider. + var builder = new DbContextOptionsBuilder(); + builder.UseInMemoryDatabase() + .UseInternalServiceProvider(serviceProvider); + + return builder.Options; + } + + private static DbContextOptions CreateNewContextOptionsForSqlDB() + { + // Create a new options instance telling the context to use a Sql database + var builder = new DbContextOptionsBuilder(); + + //SQL LOCALDB + builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"); + + return builder.Options; + } + + [Fact] + public void Add_orders_to_database() + { + // All contexts that share the same service provider will share the same database + + //Using InMemory DB + var options = CreateNewContextOptionsForInMemoryDB(); + + //Using Sql LocalDB + //var options = CreateNewContextOptionsForSqlDB(); + + + // Run the test against one instance of the context + using (var context = new OrderingDbContext(options)) + { + + //Create generic Address ValueObject + Address sampleAddress = new Address("15703 NE 61st Ct.", + "Redmond", + "Washington", + "WA", + "United States", + "US", + "98052", + 47.661492, + -122.131309 + ); + //Create sample Orders + Order order1 = new Order(Guid.NewGuid(), sampleAddress, sampleAddress); + context.Orders.Add(order1); + context.SaveChanges(); + + Assert.True(true); + } + + //// Use a separate instance of the context to verify correct data was saved to database + using (var context = new OrderingDbContext(options)) + { + var orders = context.Orders + .Include(o => o.ShippingAddress) + .Include(o => o.BillingAddress) + .ToList(); + //Could be using .Load() if you don't want to create a List + + //SAMPLE + //var company = context.Companies + // .Include(co => co.Employees).ThenInclude(emp => emp.Employee_Car) + // .Include(co => co.Employees).ThenInclude(emp => emp.Employee_Country) + // .FirstOrDefault(co => co.companyID == companyID); + + //Assert when running test with a clean In-Memory DB + //Assert.Equal(1, context.Orders.Count()); + + Assert.Equal("Redmond", orders.First().ShippingAddress.City); + } + } + + } +} diff --git a/test/Services/Ordering.Test/Ordering.Test.xproj b/test/Services/Ordering.Test/Ordering.Test.xproj index a9a2e84b9..7fa7f1968 100644 --- a/test/Services/Ordering.Test/Ordering.Test.xproj +++ b/test/Services/Ordering.Test/Ordering.Test.xproj @@ -11,9 +11,11 @@ .\obj .\bin\ - 2.0 + + + \ No newline at end of file diff --git a/test/Services/Ordering.Test/Tests.cs b/test/Services/Ordering.Test/Tests.cs deleted file mode 100644 index 1bd5ced4f..000000000 --- a/test/Services/Ordering.Test/Tests.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Xunit; - -namespace Tests -{ - public class Tests - { - [Fact] - public void Test1() - { - Assert.True(true); - } - } -} diff --git a/test/Services/Ordering.Test/project.json b/test/Services/Ordering.Test/project.json index 2a9b55577..ab108e9cd 100644 --- a/test/Services/Ordering.Test/project.json +++ b/test/Services/Ordering.Test/project.json @@ -6,7 +6,11 @@ "dependencies": { "System.Runtime.Serialization.Primitives": "4.1.1", "xunit": "2.1.0", - "dotnet-test-xunit": "2.2.0-preview2-build1029" + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Ordering.Domain": "1.0.0-*", + "Ordering.SqlData": "1.0.0-*", + "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", + "Microsoft.EntityFrameworkCore.InMemory": "1.0.0" }, "testRunner": "xunit", "frameworks": {