Now that we’ve completed the first of what I’m sure will be many coding iterations of our business entity layer solution it is now time to do some testing. In this post we’ll take some time to make sure our approach holds up to a typical usage scenario. To do this, I’ve put together a Visual Studio solution that contains a web application and a couple of class libraries. One class library for our LINQ to SQL entities and another for our business entities. We’ll attempt to build out the domain model and use it in our web UI layer to demonstrate how we could interact with the business entities.
Let’s start with the LINQ to SQL class library project. In this project we have Northwind.dbml, Northwind.dbml.layout, and Northwind.designer.cs files. The files and its contents were generated by the Visual Studio 2008 IDE using SQLMetal after I dragged the Customer, Order, Order_Detail, and Product tables on to the design surface from the Server Explorer. I’ve taken a screen shot of the resulting diagram from the designer (below). Each box represents a table in the database which in turn is generated into a corresponding LINQ to SQL class complete with mappings and a DataContext.
Moving over to the business entity project, I’ve written a class for each LINQ to SQL entity as well as a business context class that wraps the DataContext class. The following code sample implements the business context (in its entirety) in much the same way we explored in Part 2 of this series. An item to note is the addition of two methods that handle attachment of disconnected entities for inserting and deleting at lines 97 and 108 respectively. In order to support all business entity types, we use reflection to find the LinqEntity member and use the composed DataContext to invoke data store logic.
1 public partial class NorthwindBusinessContext : IDisposable
2 {
3 public NorthwindBusinessContext()
4 {
5 DataContext = new NorthwindDataContext();
6 }
7
8 public event PersistStateEvent OnBeforePersistState;
9 public event PersistStateEvent OnAfterPersistState;
10
11 protected NorthwindDataContext DataContext { get; set; }
12
13 #region Factory Methods
14
15 public CustomerBus GetCustomerBusBy(string customerID)
16 {
17 var linqEntity = DataContext.GetTable<Customer>()
18 .Single(entity => entity.CustomerID == customerID);
19 var ret = new CustomerBus(linqEntity);
20 return ret;
21 }
22
23 public IEnumerable<CustomerBus> GetAllCustomerBuses()
24 {
25 var ret = new Collection<CustomerBus>();
26 var result = DataContext.GetTable<Customer>();
27 foreach (var linqEntity in result)
28 {
29 var entity = new CustomerBus(linqEntity);
30 ret.Add(entity);
31 }
32 return ret;
33 }
34
35 public OrderBus GetOrderBusBy(int orderID)
36 {
37 var linqEntity = DataContext.GetTable<Order>()
38 .Single(entity => entity.OrderID == orderID);
39 var ret = new OrderBus(linqEntity);
40 return ret;
41 }
42
43 public IEnumerable<OrderBus> GetAllOrderBuses()
44 {
45 var ret = new Collection<OrderBus>();
46 var result = DataContext.GetTable<Order>();
47 foreach (var linqEntity in result)
48 {
49 var entity = new OrderBus(linqEntity);
50 ret.Add(entity);
51 }
52 return ret;
53 }
54
55 public Detail GetDetailBy(int orderID, int productID)
56 {
57 var linqEntity = DataContext.GetTable<Order_Detail>()
58 .Single(entity => entity.OrderID == orderID && entity.ProductID == productID);
59 var ret = new Detail(linqEntity);
60 return ret;
61 }
62
63 public IEnumerable<Detail> GetAllDetails()
64 {
65 var ret = new Collection<Detail>();
66 var result = DataContext.GetTable<Order_Detail>();
67 foreach (var linqEntity in result)
68 {
69 var entity = new Detail(linqEntity);
70 ret.Add(entity);
71 }
72 return ret;
73 }
74
75 public ProductBus GetProductBusBy(int productID)
76 {
77 var linqEntity = DataContext.GetTable<Product>()
78 .Single(entity => entity.ProductID == productID);
79 var ret = new ProductBus(linqEntity);
80 return ret;
81 }
82
83 public IEnumerable<ProductBus> GetAllProductBuses()
84 {
85 var ret = new Collection<ProductBus>();
86 var result = DataContext.GetTable<Product>();
87 foreach (var linqEntity in result)
88 {
89 var entity = new ProductBus(linqEntity);
90 ret.Add(entity);
91 }
92 return ret;
93 }
94
95 #endregion
96
97 public void DeleteOnPersistState(object entity)
98 {
99 var linqEntityRef = entity.GetType().GetProperty(“LinqEntity”,
100 System.Reflection.BindingFlags.Instance
101 | System.Reflection.BindingFlags.NonPublic
102 | System.Reflection.BindingFlags.Public
103 | System.Reflection.BindingFlags.FlattenHierarchy).GetValue(entity, null);
104 var linqEntityType = linqEntityRef.GetType();
105 DataContext.GetTable(linqEntityType).DeleteOnSubmit(linqEntityRef);
106 }
107
108 public void AddOnPersistState(object entity)
109 {
110 var linqEntityRef = entity.GetType().GetProperty(“LinqEntity”,
111 System.Reflection.BindingFlags.Instance
112 | System.Reflection.BindingFlags.NonPublic
113 | System.Reflection.BindingFlags.Public
114 | System.Reflection.BindingFlags.FlattenHierarchy).GetValue(entity, null);
115 var linqEntityType = linqEntityRef.GetType();
116 DataContext.GetTable(linqEntityType).InsertOnSubmit(linqEntityRef);
117 }
118
119 public void PersistState()
120 {
121 if (OnBeforePersistState != null)
122 OnBeforePersistState(this, new EventArgs());
123
124 DataContext.SubmitChanges();
125
126 if (OnAfterPersistState != null)
127 OnAfterPersistState(this, new EventArgs());
128 }
129
130 #region IDisposable Members
131
132 public void Dispose()
133 {
134 DataContext.Dispose();
135 }
136
137 #endregion
138 }
Below is a sample of the CustomerBus business entity. I’ve removed some of the properties and associations that implement the same pattern for brevity. Each of the business classes implemented in this project employ the design constructs we explored in Part 3 of this series. Additionally, I’ve written an IValidateable interface and a SimpleDataValidator class that reads the custom attributes found on each public property and populates a collection of validation errors when any of the tests fail. More on validation in a future post. In this post we’ll focus on the business context and the business entities.
In the web application, we have a web.config file and a Default.aspx with its code file Default.aspx.cs. We won’t be making any modifications to the HTML markup in the Default.aspx file because in this test all we want to do is run some basic reads and updates on the data store. To be able to use the classes we’ve defined in the business library project we’ll add a reference to the project.
Now let’s take a look at the code written in the Page_Load() event handler. Starting with line 10, we instantiate a business context object so that we can request a CustomerBus object with one of the factory methods. We want to test the object tracking capabilities of the underlying DataContext so on line 12 we make sure the City property is updated. To test the associations we loop through the Orders collection and modify the OrderDate property. Taking this a step further, we do a similar operation and traverse the OrderDetails collection and update both quantity and price by incrementing each property. Finally, a call to the PersistState() method completes this test. Remember, each time we traverse the object graph’s entity associations, we incur a lazy-load query against the data store because such is the default behavior of LINQ to SQL. You can easily modify this option by setting the DataContext DeferredLoadingEnabled property to false. This won’t hurt our lazy-loading code in the business entity layer because the underlying DataContext will have preloaded the entities. Our business entities will simply request the objects from the DataContext.
At this point we can open up SQL Profiler and inspect the resulting queries. On the reads, we can see a total twenty of queries, one for the initial customer query and one for each lazy-loading relationship. Again, if you want the object graph to preload, setting a property on the DataContext is all that is required. On the update side, which starts at line 130, we are greated with a very nice set of batched updates that handle customers, orders, and details. This is consistent with using LINQ to SQL out of the box. Only now we’re working with our own business entities where we can add domain specific logic.
1 exec sp_reset_connection
2 go
3 exec sp_executesql N‘SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode],
4 [t0].[Country], [t0].[Phone], [t0].[Fax]
5 FROM [dbo].[Customers] AS [t0]
6 WHERE [t0].[CustomerID] = @p0′, N‘@p0 nvarchar(5)’, @p0 = N‘ALFKI’
7 go
8 exec sp_reset_connection
9 go
10 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight],
11 [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry]
12 FROM [dbo].[Orders] AS [t0]
13 WHERE [t0].[CustomerID] = @p0′, N‘@p0 nvarchar(5)’, @p0 = N‘ALFKI’
14 go
15 exec sp_reset_connection
16 go
17 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
18 FROM [dbo].[Order Details] AS [t0]
19 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 10643
20 go
21 exec sp_reset_connection
22 go
23 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
24 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
25 FROM [dbo].[Products] AS [t0]
26 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 28
27 go
28 exec sp_reset_connection
29 go
30 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
31 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
32 FROM [dbo].[Products] AS [t0]
33 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 39
34 go
35 exec sp_reset_connection
36 go
37 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
38 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
39 FROM [dbo].[Products] AS [t0]
40 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 46
41 go
42 exec sp_reset_connection
43 go
44 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
45 FROM [dbo].[Order Details] AS [t0]
46 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 10692
47 go
48 exec sp_reset_connection
49 go
50 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
51 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
52 FROM [dbo].[Products] AS [t0]
53 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 63
54 go
55 exec sp_reset_connection
56 go
57 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
58 FROM [dbo].[Order Details] AS [t0]
59 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 10702
60 go
61 exec sp_reset_connection
62 go
63 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
64 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
65 FROM [dbo].[Products] AS [t0]
66 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 3
67 go
68 exec sp_reset_connection
69 go
70 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
71 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
72 FROM [dbo].[Products] AS [t0]
73 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 76
74 go
75 exec sp_reset_connection
76 go
77 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
78 FROM [dbo].[Order Details] AS [t0]
79 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 10835
80 go
81 exec sp_reset_connection
82 go
83 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
84 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
85 FROM [dbo].[Products] AS [t0]
86 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 59
87 go
88 exec sp_reset_connection
89 go
90 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
91 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
92 FROM [dbo].[Products] AS [t0]
93 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 77
94 go
95 exec sp_reset_connection
96 go
97 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
98 FROM [dbo].[Order Details] AS [t0]
99 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 10952
100 go
101 exec sp_reset_connection
102 go
103 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
104 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
105 FROM [dbo].[Products] AS [t0]
106 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 6
107 go
108 exec sp_reset_connection
109 go
110 exec sp_executesql N‘SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
111 FROM [dbo].[Order Details] AS [t0]
112 WHERE [t0].[OrderID] = @p0′, N‘@p0 int’, @p0 = 11011
113 go
114 exec sp_reset_connection
115 go
116 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
117 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
118 FROM [dbo].[Products] AS [t0]
119 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 58
120 go
121 exec sp_reset_connection
122 go
123 exec sp_executesql N‘SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
124 [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
125 FROM [dbo].[Products] AS [t0]
126 WHERE [t0].[ProductID] = @p0′, N‘@p0 int’, @p0 = 71
127 go
128 exec sp_reset_connection
129 go
130 SET TRANSACTION ISOLATION LEVEL READ COMMITTED;BEGIN TRANSACTION
131 go
132 exec sp_executesql N‘UPDATE [dbo].[Customers]
133 SET [City] = @p1
134 WHERE [CustomerID] = @p0′, N‘@p0 nchar(5),@p1 nvarchar(6)’, @p0 = N‘ALFKI’, @p1 = N‘Berlin’
135 go
136 exec sp_executesql N‘UPDATE [dbo].[Orders]
137 SET [OrderDate] = @p1
138 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 10643, @p1 = ‘May 21 2008 12:28:08:667AM’
139 go
140 exec sp_executesql N‘UPDATE [dbo].[Orders]
141 SET [OrderDate] = @p1
142 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 10692, @p1 = ‘May 21 2008 12:28:08:677AM’
143 go
144 exec sp_executesql N‘UPDATE [dbo].[Orders]
145 SET [OrderDate] = @p1
146 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 10702, @p1 = ‘May 21 2008 12:28:08:680AM’
147 go
148 exec sp_executesql N‘UPDATE [dbo].[Orders]
149 SET [OrderDate] = @p1
150 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 10835, @p1 = ‘May 21 2008 12:28:08:687AM’
151 go
152 exec sp_executesql N‘UPDATE [dbo].[Orders]
153 SET [OrderDate] = @p1
154 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 10952, @p1 = ‘May 21 2008 12:28:08:693AM’
155 go
156 exec sp_executesql N‘UPDATE [dbo].[Orders]
157 SET [OrderDate] = @p1
158 WHERE [OrderID] = @p0′, N‘@p0 int,@p1 datetime’, @p0 = 11011, @p1 = ‘May 21 2008 12:28:08:697AM’
159 go
160 exec sp_executesql N‘UPDATE [dbo].[Order Details]
161 SET [Quantity] = @p2
162 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10643, @p1 = 28, @p2 = 28
163 go
164 exec sp_executesql N‘UPDATE [dbo].[Order Details]
165 SET [Quantity] = @p2
166 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10643, @p1 = 39, @p2 = 34
167 go
168 exec sp_executesql N‘UPDATE [dbo].[Order Details]
169 SET [Quantity] = @p2
170 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10643, @p1 = 46, @p2 = 15
171 go
172 exec sp_executesql N‘UPDATE [dbo].[Order Details]
173 SET [Quantity] = @p2
174 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10692, @p1 = 63, @p2 = 33
175 go
176 exec sp_executesql N‘UPDATE [dbo].[Order Details]
177 SET [Quantity] = @p2
178 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10702, @p1 = 3, @p2 = 19
179 go
180 exec sp_executesql N‘UPDATE [dbo].[Order Details]
181 SET [Quantity] = @p2
182 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10702, @p1 = 76, @p2 = 28
183 go
184 exec sp_executesql N‘UPDATE [dbo].[Order Details]
185 SET [Quantity] = @p2
186 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10835, @p1 = 59, @p2 = 28
187 go
188 exec sp_executesql N‘UPDATE [dbo].[Order Details]
189 SET [Quantity] = @p2
190 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10835, @p1 = 77, @p2 = 15
191 go
192 exec sp_executesql N‘UPDATE [dbo].[Order Details]
193 SET [Quantity] = @p2
194 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10952, @p1 = 6, @p2 = 29
195 go
196 exec sp_executesql N‘UPDATE [dbo].[Order Details]
197 SET [Quantity] = @p2
198 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 10952, @p1 = 28, @p2 = 15
199 go
200 exec sp_executesql N‘UPDATE [dbo].[Order Details]
201 SET [Quantity] = @p2
202 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 11011, @p1 = 58, @p2 = 53
203 go
204 exec sp_executesql N‘UPDATE [dbo].[Order Details]
205 SET [Quantity] = @p2
206 WHERE ([OrderID] = @p0) AND ([ProductID] = @p1)’, N‘@p0 int,@p1 int,@p2 smallint’, @p0 = 11011, @p1 = 71, @p2 = 33
207 go
208 exec sp_executesql N‘UPDATE [dbo].[Products]
209 SET [UnitPrice] = @p1
210 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 3, @p1 = $19.0000
211 go
212 exec sp_executesql N‘UPDATE [dbo].[Products]
213 SET [UnitPrice] = @p1
214 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 6, @p1 = $34.0000
215 go
216 exec sp_executesql N‘UPDATE [dbo].[Products]
217 SET [UnitPrice] = @p1
218 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 28, @p1 = $63.6000
219 go
220 exec sp_executesql N‘UPDATE [dbo].[Products]
221 SET [UnitPrice] = @p1
222 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 39, @p1 = $27.0000
223 go
224 exec sp_executesql N‘UPDATE [dbo].[Products]
225 SET [UnitPrice] = @p1
226 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 46, @p1 = $21.0000
227 go
228 exec sp_executesql N‘UPDATE [dbo].[Products]
229 SET [UnitPrice] = @p1
230 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 58, @p1 = $22.2500
231 go
232 exec sp_executesql N‘UPDATE [dbo].[Products]
233 SET [UnitPrice] = @p1
234 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 59, @p1 = $64.0000
235 go
236 exec sp_executesql N‘UPDATE [dbo].[Products]
237 SET [UnitPrice] = @p1
238 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 63, @p1 = $52.9000
239 go
240 exec sp_executesql N‘UPDATE [dbo].[Products]
241 SET [UnitPrice] = @p1
242 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 71, @p1 = $30.5000
243 go
244 exec sp_executesql N‘UPDATE [dbo].[Products]
245 SET [UnitPrice] = @p1
246 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 76, @p1 = $27.0000
247 go
248 exec sp_executesql N‘UPDATE [dbo].[Products]
249 SET [UnitPrice] = @p1
250 WHERE [ProductID] = @p0′, N‘@p0 int,@p1 money’, @p0 = 77, @p1 = $22.0000
251 go
252 COMMIT TRANSACTION
253 go
Sorry for the long post. I’ll try to break it up more next time. The take away here is that our efforts to abstract away the LINQ to SQL entities and DataContext have been successful so far. I didn’t want to get too wrapped up in the minutia, but for those of you who absolutely need to loosen the coupling between the business layer from the LINQ to SQL layer I would recommend extracting the interface out of each of the LINQ to SQL entities and its DataContext. This would allow for the flexibility of dependency injection (interface injection in this case) and TDD at the business layer. Keep in mind that at some point the benefits of this loose coupling versus the return may start to diminish depending on your project. I’ll let you decide what’s best for your project.
In the next part of this series, we’ll take a look at what we can do to finally marry the ActiveRecord pattern to our abstracted model. I will concede that at this point, there may be no need to come full circle with AR. I’m completely sold on the idea of using the Unit of Work pattern (context) in lieu of AR. What I grappled with at first was this issue of trust. I needed to trust the context and relinquish control. The AR pattern gave me ultimate control of what DML operations were called and when they were executed on the data store. As a developer, you’re going to need to develop a trusting relationship with the context. Then and only then will you be able to embrace its liberating capabilities.