Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="cdmdotnet Limited">
4 : // // Copyright cdmdotnet Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections;
11 : using System.Collections.Generic;
12 : using System.Data.Linq;
13 : using System.Linq;
14 : using System.Linq.Expressions;
15 : using System.Transactions;
16 : using cdmdotnet.Logging;
17 : using Cqrs.Configuration;
18 : using Cqrs.Entities;
19 :
20 : namespace Cqrs.DataStores
21 : {
22 : /// <summary>
23 : /// A <see cref="IDataStore{TData}"/> using simplified SQL.
24 : /// </summary>
25 : public class SqlDataStore<TData> : IDataStore<TData>
26 : where TData : Entity
27 1 : {
28 : internal const string SqlDataStoreDbFileOrServerOrConnectionApplicationKey = @"SqlDataStoreDbFileOrServerOrConnection";
29 :
30 : internal const string SqlDataStoreConnectionNameApplicationKey = @"Cqrs.SqlDataStore.ConnectionStringName";
31 :
32 : internal const string SqlReadableDataStoreConnectionStringKey = "Cqrs.SqlDataStore.Read.ConnectionStringName";
33 :
34 : internal const string SqlWritableDataStoreConnectionStringKey = "Cqrs.SqlDataStore.Write.ConnectionStringName";
35 :
36 : /// <summary />
37 : protected IConfigurationManager ConfigurationManager { get; private set; }
38 :
39 : /// <summary>
40 : /// Instantiates a new instance of the <see cref="SqlDataStore{TData}"/> class
41 : /// </summary>
42 1 : public SqlDataStore(IConfigurationManager configurationManager, ILogger logger)
43 : {
44 : ConfigurationManager = configurationManager;
45 : Logger = logger;
46 : // ReSharper disable DoNotCallOverridableMethodsInConstructor
47 : DbDataContext = CreateDbDataContext();
48 : WriteableConnectionStrings = GetWriteableConnectionStrings();
49 : // ReSharper restore DoNotCallOverridableMethodsInConstructor
50 :
51 : // Get a typed table to run queries.
52 : Table = DbDataContext.GetTable<TData>();
53 : }
54 :
55 : /// <summary>
56 : /// Gets or sets the DataContext.
57 : /// </summary>
58 : protected DataContext DbDataContext { get; private set; }
59 :
60 : /// <summary>
61 : /// Gets or sets the list of writeable connection strings for data mirroring
62 : /// </summary>
63 : protected IEnumerable<string> WriteableConnectionStrings { get; private set; }
64 :
65 : /// <summary>
66 : /// Gets or sets the list of writeable DataContexts for data mirroring
67 : /// </summary>
68 : private IList<DataContext> _writeableConnections;
69 :
70 : /// <summary>
71 : /// Gets or sets the list of writeable DataContexts for data mirroring
72 : /// </summary>
73 : protected IEnumerable<DataContext> WriteableConnections
74 : {
75 : get
76 : {
77 : if (_writeableConnections == null)
78 : {
79 : _writeableConnections = new List<DataContext>();
80 : foreach (string writeableConnectionString in WriteableConnectionStrings)
81 : _writeableConnections.Add(new DataContext(writeableConnectionString));
82 : }
83 : return _writeableConnections;
84 : }
85 : }
86 :
87 : /// <summary>
88 : /// Gets or sets the readable Table
89 : /// </summary>
90 : protected Table<TData> Table { get; private set; }
91 :
92 : /// <summary>
93 : /// Gets or sets the Logger
94 : /// </summary>
95 : protected ILogger Logger { get; private set; }
96 :
97 : /// <summary>
98 : /// Locate the connection settings and create a <see cref="DataContext"/>.
99 : /// </summary>
100 1 : protected virtual DataContext CreateDbDataContext()
101 : {
102 : string connectionStringKey;
103 : string applicationKey;
104 :
105 : // Try read/write connection values first
106 : if (!ConfigurationManager.TryGetSetting(SqlReadableDataStoreConnectionStringKey, out applicationKey) || string.IsNullOrEmpty(applicationKey))
107 : {
108 : // Try single connection value
109 : if (!ConfigurationManager.TryGetSetting(SqlDataStoreConnectionNameApplicationKey, out applicationKey) || string.IsNullOrEmpty(applicationKey))
110 : {
111 : // Default to old connection value
112 : if (!ConfigurationManager.TryGetSetting(SqlDataStoreDbFileOrServerOrConnectionApplicationKey, out connectionStringKey) || string.IsNullOrEmpty(connectionStringKey))
113 : throw new NullReferenceException(string.Format("No application setting named '{0}' was found in the configuration file with the name of a connection string to look for.", SqlDataStoreConnectionNameApplicationKey));
114 : return new DataContext(connectionStringKey);
115 : }
116 : }
117 :
118 : try
119 : {
120 : connectionStringKey = System.Configuration.ConfigurationManager.ConnectionStrings[applicationKey].ConnectionString;
121 : }
122 : catch (NullReferenceException exception)
123 : {
124 : throw new NullReferenceException(string.Format("No connection string setting named '{0}' was found in the configuration file with the SQL Data Store connection string.", applicationKey), exception);
125 : }
126 :
127 : return new DataContext(connectionStringKey);
128 : }
129 :
130 : /// <summary>
131 : /// Locate the connection settings for persisting data.
132 : /// </summary>
133 1 : protected virtual IEnumerable<string> GetWriteableConnectionStrings()
134 : {
135 : Logger.LogDebug("Getting SQL data store writeable connection strings", "SqlDataStore\\GetWritableConnectionStrings");
136 :
137 : string connectionStringKey;
138 : string applicationKey;
139 :
140 : // Try read/write connection values first
141 : if (!ConfigurationManager.TryGetSetting(SqlWritableDataStoreConnectionStringKey, out applicationKey) || string.IsNullOrEmpty(applicationKey))
142 : {
143 : Logger.LogDebug(string.Format("No application setting named '{0}' was found in the configuration file with the name of a connection string to look for.", SqlWritableDataStoreConnectionStringKey), "SqlDataStore\\GetWriteableConnectionStrings");
144 : // Try single connection value
145 : if (!ConfigurationManager.TryGetSetting(SqlDataStoreConnectionNameApplicationKey, out applicationKey) || string.IsNullOrEmpty(applicationKey))
146 : {
147 : Logger.LogDebug(string.Format("No application setting named '{0}' was found in the configuration file with the name of a connection string to look for.", SqlDataStoreConnectionNameApplicationKey), "SqlDataStore\\GetWriteableConnectionStrings");
148 : // Default to old connection value
149 : if (!ConfigurationManager.TryGetSetting(SqlDataStoreDbFileOrServerOrConnectionApplicationKey, out connectionStringKey) || string.IsNullOrEmpty(connectionStringKey))
150 : throw new NullReferenceException(string.Format("No application setting named '{0}' was found in the configuration file with the name of a connection string to look for.", SqlDataStoreConnectionNameApplicationKey));
151 : _writeableConnections = new List<DataContext> { DbDataContext };
152 : return new List<string> {connectionStringKey};
153 : }
154 : }
155 :
156 : try
157 : {
158 : var collection = new List<string>();
159 : string rootApplicationKey = applicationKey;
160 :
161 : int writeIndex = 1;
162 : while (!string.IsNullOrWhiteSpace(applicationKey))
163 : {
164 : try
165 : {
166 : connectionStringKey = System.Configuration.ConfigurationManager.ConnectionStrings[applicationKey].ConnectionString;
167 : collection.Add(connectionStringKey);
168 : }
169 : catch (NullReferenceException exception)
170 : {
171 : throw new NullReferenceException(string.Format("No connection string setting named '{0}' was found in the configuration file with a SQL connection string.", applicationKey), exception);
172 : }
173 :
174 : if (!ConfigurationManager.TryGetSetting(string.Format("{0}.{1}", rootApplicationKey, writeIndex), out applicationKey))
175 : applicationKey = null;
176 :
177 : writeIndex++;
178 : }
179 :
180 : if (!collection.Any())
181 : throw new NullReferenceException();
182 :
183 : return collection;
184 : }
185 : catch (NullReferenceException exception)
186 : {
187 : throw new NullReferenceException(string.Format("No application setting named '{0}' was found in the configuration file with a SQL connection string.", SqlWritableDataStoreConnectionStringKey), exception);
188 : }
189 : finally
190 : {
191 : Logger.LogDebug("Getting SQL writeable connection string... Done", "SqlDataStore\\GetWriteableConnectionStrings");
192 : }
193 : }
194 :
195 : #region Implementation of IEnumerable
196 :
197 : /// <summary>
198 : /// Returns an enumerator that iterates through the collection.
199 : /// </summary>
200 : /// <returns>
201 : /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
202 : /// </returns>
203 1 : public IEnumerator<TData> GetEnumerator()
204 : {
205 : return Table.AsQueryable().GetEnumerator();
206 : }
207 :
208 : /// <summary>
209 : /// Returns an enumerator that iterates through a collection.
210 : /// </summary>
211 : /// <returns>
212 : /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
213 : /// </returns>
214 : IEnumerator IEnumerable.GetEnumerator()
215 : {
216 : return GetEnumerator();
217 : }
218 :
219 : #endregion
220 :
221 : #region Implementation of IQueryable
222 :
223 : /// <summary>
224 : /// Gets the expression tree that is associated with the instance of <see cref="T:System.Linq.IQueryable"/>.
225 : /// </summary>
226 : /// <returns>
227 : /// The <see cref="T:System.Linq.Expressions.Expression"/> that is associated with this instance of <see cref="T:System.Linq.IQueryable"/>.
228 : /// </returns>
229 : public Expression Expression
230 : {
231 : get { return Table.AsQueryable().Expression; }
232 : }
233 :
234 : /// <summary>
235 : /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of <see cref="T:System.Linq.IQueryable"/> is executed.
236 : /// </summary>
237 : /// <returns>
238 : /// A <see cref="T:System.Type"/> that represents the type of the element(s) that are returned when the expression tree associated with this object is executed.
239 : /// </returns>
240 : public Type ElementType
241 : {
242 : get { return Table.AsQueryable().ElementType; }
243 : }
244 :
245 : /// <summary>
246 : /// Gets the query provider that is associated with this data source.
247 : /// </summary>
248 : /// <returns>
249 : /// The <see cref="T:System.Linq.IQueryProvider"/> that is associated with this data source.
250 : /// </returns>
251 : public IQueryProvider Provider
252 : {
253 : get { return Table.AsQueryable().Provider; }
254 : }
255 :
256 : #endregion
257 :
258 : #region Implementation of IDisposable
259 :
260 : /// <summary>
261 : /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
262 : /// </summary>
263 1 : public void Dispose()
264 : {
265 : Table = null;
266 : DbDataContext.Dispose();
267 : DbDataContext = null;
268 : }
269 :
270 : #endregion
271 :
272 : #region Implementation of IDataStore<TData>
273 :
274 : /// <summary>
275 : /// Add the provided <paramref name="data"/> to the data store and persist the change.
276 : /// </summary>
277 1 : public virtual void Add(TData data)
278 : {
279 : Logger.LogDebug("Adding data to the SQL database", "SqlDataStore\\Add");
280 : try
281 : {
282 : DateTime start = DateTime.Now;
283 : using (var transaction = new TransactionScope())
284 : {
285 : foreach (DataContext writeableConnection in WriteableConnections)
286 : {
287 : Table<TData> table = Table;
288 : // This optimises for single connection handling
289 : if (writeableConnection != DbDataContext)
290 : table = writeableConnection.GetTable<TData>();
291 : table.InsertOnSubmit(data);
292 : writeableConnection.SubmitChanges();
293 : }
294 : transaction.Complete();
295 : }
296 : DateTime end = DateTime.Now;
297 : Logger.LogDebug(string.Format("Adding data in the SQL database took {0}.", end - start), "SqlDataStore\\Add");
298 : }
299 : finally
300 : {
301 : Logger.LogDebug("Adding data to the SQL database... Done", "SqlDataStore\\Add");
302 : }
303 : }
304 :
305 : /// <summary>
306 : /// Add the provided <paramref name="data"/> to the data store and persist the change.
307 : /// </summary>
308 1 : public virtual void Add(IEnumerable<TData> data)
309 : {
310 : Logger.LogDebug("Adding data collection to the SQL database", "SqlDataStore\\Add\\Collection");
311 : try
312 : {
313 : DateTime start = DateTime.Now;
314 : // Multiple enumeration optimisation
315 : data = data.ToList();
316 : using (var transaction = new TransactionScope())
317 : {
318 : foreach (DataContext writeableConnection in WriteableConnections)
319 : {
320 : Table<TData> table = Table;
321 : // This optimises for single connection handling
322 : if (writeableConnection != DbDataContext)
323 : table = writeableConnection.GetTable<TData>();
324 : table.InsertAllOnSubmit(data);
325 : writeableConnection.SubmitChanges();
326 : }
327 : transaction.Complete();
328 : }
329 : DateTime end = DateTime.Now;
330 : Logger.LogDebug(string.Format("Adding data in the SQL database took {0}.", end - start), "SqlDataStore\\Add\\Collection");
331 : }
332 : finally
333 : {
334 : Logger.LogDebug("Adding data collection to the SQL database... Done", "SqlDataStore\\Add\\Collection");
335 : }
336 : }
337 :
338 : /// <summary>
339 : /// Will mark the <paramref name="data"/> as logically (or soft) deleted by setting <see cref="Entity.IsLogicallyDeleted"/> to true in the data store and persist the change.
340 : /// </summary>
341 1 : public virtual void Remove(TData data)
342 : {
343 : Logger.LogDebug("Removing data from the Sql database", "SqlDataStore\\Remove");
344 : try
345 : {
346 : DateTime start = DateTime.Now;
347 : data.IsLogicallyDeleted = true;
348 : Update(data);
349 : DateTime end = DateTime.Now;
350 : Logger.LogDebug(string.Format("Removing data from the Sql database took {0}.", end - start), "SqlDataStore\\Remove");
351 : }
352 : finally
353 : {
354 : Logger.LogDebug("Removing data from the Sql database... Done", "SqlDataStore\\Remove");
355 : }
356 : }
357 :
358 : /// <summary>
359 : /// Remove the provided <paramref name="data"/> (normally by <see cref="IEntity.Rsn"/>) from the data store and persist the change.
360 : /// </summary>
361 1 : public void Destroy(TData data)
362 : {
363 : Logger.LogDebug("Removing data from the SQL database", "SqlDataStore\\Destroy");
364 : try
365 : {
366 : DateTime start = DateTime.Now;
367 : using (var transaction = new TransactionScope())
368 : {
369 : foreach (DataContext writeableConnection in WriteableConnections)
370 : {
371 : Table<TData> table = Table;
372 : // This optimises for single connection handling
373 : if (writeableConnection != DbDataContext)
374 : table = writeableConnection.GetTable<TData>();
375 : try
376 : {
377 : table.DeleteOnSubmit(data);
378 : }
379 : catch (InvalidOperationException exception)
380 : {
381 : if (exception.Message != "Cannot remove an entity that has not been attached.")
382 : throw;
383 : try
384 : {
385 : table.Attach(data);
386 : writeableConnection.Refresh(RefreshMode.KeepCurrentValues, data);
387 : }
388 : catch (DuplicateKeyException)
389 : {
390 : // We're using the same context apparently
391 : }
392 : table.DeleteOnSubmit(data);
393 : }
394 : try
395 : {
396 : writeableConnection.SubmitChanges();
397 : }
398 : catch (ChangeConflictException)
399 : {
400 : writeableConnection.Refresh(RefreshMode.KeepCurrentValues, data);
401 : writeableConnection.SubmitChanges();
402 : }
403 : }
404 : transaction.Complete();
405 : }
406 : DateTime end = DateTime.Now;
407 : Logger.LogDebug(string.Format("Removing data from the SQL database took {0}.", end - start), "SqlDataStore\\Destroy");
408 : }
409 : finally
410 : {
411 : Logger.LogDebug("Removing data from the SQL database... Done", "SqlDataStore\\Destroy");
412 : }
413 : }
414 :
415 : /// <summary>
416 : /// Remove all contents (normally by use of a truncate operation) from the data store and persist the change.
417 : /// </summary>
418 1 : public virtual void RemoveAll()
419 : {
420 : Logger.LogDebug("Removing all from the SQL database", "SqlDataStore\\RemoveAll");
421 : try
422 : {
423 : using (var transaction = new TransactionScope())
424 : {
425 : foreach (DataContext writeableConnection in WriteableConnections)
426 : {
427 : Table<TData> table = Table;
428 : // This optimises for single connection handling
429 : if (writeableConnection != DbDataContext)
430 : table = writeableConnection.GetTable<TData>();
431 : table.Truncate();
432 : writeableConnection.SubmitChanges();
433 : }
434 : transaction.Complete();
435 : }
436 : }
437 : finally
438 : {
439 : Logger.LogDebug("Removing all from the SQL database... Done", "SqlDataStore\\RemoveAll");
440 : }
441 : }
442 :
443 : /// <summary>
444 : /// Update the provided <paramref name="data"/> in the data store and persist the change.
445 : /// </summary>
446 1 : public virtual void Update(TData data)
447 : {
448 : Logger.LogDebug("Updating data in the SQL database", "SqlDataStore\\Update");
449 : try
450 : {
451 : DateTime start = DateTime.Now;
452 : using (var transaction = new TransactionScope())
453 : {
454 : foreach (DataContext writeableConnection in WriteableConnections)
455 : {
456 : Table<TData> table = Table;
457 : // This optimises for single connection handling
458 : if (writeableConnection != DbDataContext)
459 : table = writeableConnection.GetTable<TData>();
460 : try
461 : {
462 : table.Attach(data);
463 : writeableConnection.Refresh(RefreshMode.KeepCurrentValues, data);
464 : }
465 : catch (DuplicateKeyException)
466 : {
467 : // We're using the same context apparently
468 : }
469 : writeableConnection.SubmitChanges();
470 : }
471 : transaction.Complete();
472 : }
473 : DateTime end = DateTime.Now;
474 : Logger.LogDebug(string.Format("Updating data in the SQL database took {0}.", end - start), "SqlDataStore\\Update");
475 : }
476 : finally
477 : {
478 : Logger.LogDebug("Updating data to the SQL database... Done", "SqlDataStore\\Update");
479 : }
480 : }
481 :
482 : #endregion
483 : }
484 : }
|