- Implementation of Repository pattern example using UnitOfWork Pattern. UnitOfWork (UOW), is the common pattern that is used to resolve data concurrency issues which arise when each repository implements and maintains separate Data context object. The UnitOfWork (UOW) pattern implementation manages in-memory database operations on entities as one transaction. So, if one of the operations is failing then entire database operations will be rolled back.
- Designed a database model as shown below. This database contains two tables, “Order” and “OrderItem”. Table “Order” contains column “OrderId” of type varchar and defined as primary key and non-identity column ( Non-identity column specifies that value should be supplied from the application/user). Create a foreign key relationship between primary key column “OrderId” of table “Order” and column “OrderId” of table “OrderItem” which means value of “OrderItem.OrderId” should match the value of “Order.OrderId
- Always generating the new “Order.OrderId” using Guid.NewGuid().ToString() and passing the same value to OrderItem.OrderId
- Making the entries to both tables “Order” and “OrderItem” in a single transaction. In case, any transaction fails then we rollback the entries from both the tables
Now a question arises if the “Order.OrderId” is set to Identity Column (Identity column of a table is a column whose value increases automatically. The value in an identity column is created by the database server) in database then how will auto generated value of “Order.OrderId” be retrieved and passed to column “OrderItem.OrderId”, and still have the execution of these statements happen within a single transaction.
Now, let’s have a look at the implementation of Repository pattern example using UnitOfWork Pattern and Primary key (identity column).
Database Model Changes
- Change the type of “Order.OrderId” from varchar to int
- Add column “OrderItem.ItemId” of type int
- Add unique constraint on column “OrderItem.OrderId” and “OrderItem.ItemId” to ensure that order must contain one entry of each unique item
Database Script
-
-
-
-
-
-
-
- SET QUOTED_IDENTIFIER OFF;
- GO
- USE [OrderManagement];
- GO
- IF SCHEMA_ID(N'dbo') IS NULL EXECUTE(N'CREATE SCHEMA [dbo]');
- GO
-
-
-
-
-
- /****** Object: Table [dbo].[Orders] Script Date: 08/24/2019 19:20:39 ******/
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U'))
- DROP TABLE [dbo].[Orders]
- GO
-
- /****** Object: Table [dbo].[Orders] Script Date: 08/24/2019 19:20:39 ******/
- SET ANSI_NULLS ON
- GO
-
- SET QUOTED_IDENTIFIER ON
- GO
-
- CREATE TABLE [dbo].[Orders](
- [OrderId] [int] IDENTITY(1,1) NOT NULL,
- [OrderDate] [datetime] NOT NULL,
- [OrderStatus] [nvarchar](50) NOT NULL,
- CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
- (
- [OrderId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
-
- GO
-
- SET IDENTITY_INSERT [dbo].[Orders] ON
-
- GO
-
-
-
-
- IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderItems_Orders]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderItems]'))
- ALTER TABLE [dbo].[OrderItems] DROP CONSTRAINT [FK_OrderItems_Orders]
- GO
-
-
-
-
-
- /****** Object: Table [dbo].[OrderItems] Script Date: 08/24/2019 19:22:18 ******/
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderItems]') AND type in (N'U'))
- DROP TABLE [dbo].[OrderItems]
- GO
-
-
- CREATE TABLE [dbo].[OrderItems](
- [OrderItemId] [varchar](50) NOT NULL,
- [OrderId] [int] NOT NULL,
- [ItemName] [varchar](50) NOT NULL,
- [Quantity] [int] NOT NULL,
- [ItemId] [int] NOT NULL,
- CONSTRAINT [PK_OrderItems] PRIMARY KEY CLUSTERED
- (
- [OrderItemId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
-
- GO
-
- SET ANSI_PADDING OFF
- GO
-
-
-
-
- ALTER TABLE [dbo].[OrderItems] WITH CHECK ADD CONSTRAINT [FK_OrderItems_Orders] FOREIGN KEY([OrderId])
- REFERENCES [dbo].[Orders] ([OrderId])
- GO
-
- ALTER TABLE [dbo].[OrderItems] CHECK CONSTRAINT [FK_OrderItems_Orders]
- GO
-
-
- USE [OrderManagement]
- GO
-
- /****** Object: Index [IX_UQ_OrderItems] Script Date: 08/24/2019 19:32:47 ******/
- IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[OrderItems]') AND name = N'IX_UQ_OrderItems')
- DROP INDEX [IX_UQ_OrderItems] ON [dbo].[OrderItems] WITH ( ONLINE = OFF )
- GO
-
- USE [OrderManagement]
- GO
-
- /****** Object: Index [IX_UQ_OrderItems] Script Date: 08/24/2019 19:32:47 ******/
- CREATE NONCLUSTERED INDEX [IX_UQ_OrderItems] ON [dbo].[OrderItems]
- (
- [OrderId] ASC,
- [ItemId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- GO
-
-
-
-
-
BaseRepository class
Please note that no changes are required in the BaseRepository class. To get the auto generated value of identity we do not need to save changes in the database context after every add operation. We will save changes once to save all the transactions together or collectively.
Please refer to
Part Two for detailed source code.
UnitOfWork Class
Please note that no changes are required in the UnitOfWork class. Please refer to
Part Two for detailed source code.
Program
This function PlaceOrderToBuyUniqueItems() is used to place an order to buy Jeans and Shirts (unique items) . Please do not initialize the value of order.orderId while initializing an instance of Order class because the database will automatically assign the value as soon as UnitOfWork.SaveChanges() is called and generated “order.orderId” value will automatically pass to “OrderItem.OrderId” for saving in the database.
-
-
-
- private static void PlaceOrderToBuyUniqueItems()
- {
- try
- {
- Console.WriteLine("Place order for items (Jeans (item id = 3) and Shirt (item id = 4))");
-
- Order order = new Order { OrderDate = DateTime.Now, OrderStatus = "In Process" };
-
- using (var unitOfWork = new UnitOfWork())
- {
- unitOfWork.OrderRepository.Add(order);
-
- order.OrderItems = new List<OrderItem>
- {
- new OrderItem { OrderId = order.OrderId, ItemName = "Shirt", OrderItemId = Guid.NewGuid().ToString(), Quantity = 1, ItemId = 4 },
- new OrderItem { OrderId = order.OrderId, ItemName = "Jeans", OrderItemId = Guid.NewGuid().ToString(), Quantity = 4, ItemId = 3 }
- };
-
- foreach (var orderItem in order.OrderItems)
- {
- var orderItemAddMessage = $"Add Order Item { orderItem.ItemName} added with unique identifier { orderItem.OrderItemId}";
- Console.WriteLine(orderItemAddMessage.PadLeft(5 + orderItemAddMessage.Length));
- }
-
- Console.WriteLine("========================================================================");
- unitOfWork.OrderItemRepository.AddRange(order.OrderItems);
- unitOfWork.SaveChanges();
- Console.WriteLine("order placed successfully");
- }
- }
- catch (Exception e)
- {
- Console.WriteLine("order failed");
- var message = GetExceptionMessage(e);
- Console.WriteLine(message);
- }
Now execute the SELECT statement in the database if any data already exists,
Write a program to execute method PlaceOrderToBuyUniqueItems()
- static void Main(string[] args)
- {
- Console.WriteLine("========================================================================");
- PlaceOrderToBuyUniqueItems();
- Console.WriteLine("========================================================================");
- Console.ReadLine();
- }
Again, execute the SELECT statement in the database if is any data got saved
Again, execute method PlaceOrderToBuyUniqueItems()and SELECT statement to validate if data is inserted for second order and order contains item Jeans and Shirt,
This function PlaceOrderToBuySameItems() is used to place an order to buy two Shirts (same item) . Please do not initialize the value of order.orderId while initializing an instance of Order class because database will automatically assign the value as soon as UnitOfWork.SaveChanges() is called and the generated “order.orderId” value will automatically pass to “OrderItem.OrderId” for saving in the database but this will violate the unique index constraint and the complete transaction will be rolled back.
- private static void PlaceOrderToBuySameItems()
- {
- try
- {
- Console.WriteLine("Place order for items (Jeans (item id = 3) and Shirt (item id = 4))");
-
- Order order = new Order { OrderDate = DateTime.Now, OrderStatus = "In Process" };
-
- using (var unitOfWork = new UnitOfWork())
- {
- unitOfWork.OrderRepository.Add(order);
-
- order.OrderItems = new List<OrderItem>
- {
- new OrderItem { OrderId = order.OrderId, ItemName = "Shirt", OrderItemId = Guid.NewGuid().ToString(), Quantity = 1, ItemId = 4 },
- new OrderItem { OrderId = order.OrderId, ItemName = "Shirt", OrderItemId = Guid.NewGuid().ToString(), Quantity = 4, ItemId = 4 }
- };
-
- foreach (var orderItem in order.OrderItems)
- {
- var orderItemAddMessage = $"Add Order Item { orderItem.ItemName} added with unique identifier { orderItem.OrderItemId}";
- Console.WriteLine(orderItemAddMessage.PadLeft(5 + orderItemAddMessage.Length));
- }
-
- Console.WriteLine("========================================================================");
- unitOfWork.OrderItemRepository.AddRange(order.OrderItems);
- unitOfWork.SaveChanges();
- Console.WriteLine("order placed successfully");
- }
- }
- catch (Exception e)
- {
- Console.WriteLine("order failed");
- var message = GetExceptionMessage(e);
- Console.WriteLine(message);
- }
- }
Write a program to execute method PlaceOrderToBuyUniqueItems() and observe the output
- static void Main(string[] args)
- {
- Console.WriteLine("========================================================================");
- PlaceOrderToBuySameItems();
- Console.WriteLine("========================================================================");
- Console.ReadLine();
- }
Again, execute the SELECT statement to see if there is any change in the database – with no doubt, there should not be any impact on the data stored in the database,
Conclusion
As we have seen that there is no need to call SaveChanges() on database context after every add operation. Identity column value will be first generated and passed to the foreign key columns in a single transaction.