Click here to Skip to main content
15,896,063 members
Articles / Desktop Programming / WPF

Templates, Inversion of Control, Factories, and so on

Rate me:
Please Sign up or sign in to vote.
4.75/5 (5 votes)
8 Jul 2011CPOL11 min read 23K   270   18  
This article gives a little presentation of Control Templates, Data Templates, Inversion of Control, and Factories, explaining why they are all related and how to better use them.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Pfz.Collections;
using Pfz.Databasing.Filtering;
using Pfz.Databasing.Internal;
using Pfz.Databasing.TypeConverters;
using Pfz.Extensions;
using Pfz.Factoring;
using Pfz.Threading;

namespace Pfz.Databasing.Managers
{
	/// <summary>
	/// Does the most basic implementation to be able to use the Databasing
	/// framework. It does not threat special types, but is able to do the
	/// basic conversions.
	/// </summary>
	public sealed class LocalDatabaseManager:
		IDatabaseManager
	{
		#region Static Area
			#region Static Constructor
				static LocalDatabaseManager()
				{
					Value = new LocalDatabaseManager();
				}
			#endregion
		
			#region Value
				/// <summary>
				/// A singleton value that does the most basic functionality.
				/// </summary>
				public static LocalDatabaseManager Value { get; private set; }
			#endregion
		#endregion
		#region Normal Area
			#region Constructor
				/// <summary>
				/// Protected constructor. So, or you inherit it and do your code,
				/// or you use the DefaultValue property.
				/// </summary>
				private LocalDatabaseManager()
				{
					RegisterTypeConverter(new DateConverter());
					RegisterTypeConverter(new DateTimeConverter());
					RegisterTypeConverter(new EnumConverter());
					RegisterTypeConverter(new Int16Converter());
					RegisterTypeConverter(new Int32Converter());
					RegisterTypeConverter(new Int64Converter());
					RegisterTypeConverter(new NullableConverter());
					RegisterTypeConverter(new StringConverter());
					RegisterTypeConverter(new TimeConverter());
					RegisterTypeConverter(new DecimalConverter());
					RegisterTypeConverter(new BooleanConverter());
					RegisterTypeConverter(new SByteConverter());
					RegisterTypeConverter(new EmailStringConverter());
					RegisterTypeConverter(new ByteArrayConverter());
					RegisterTypeConverter(new TimeSpanConverter());
				}
			#endregion
			
			#region CreateConnection
				/// <summary>
				/// Creates a new connection given the name from ConnectionStrings app configuration.
				/// </summary>
				public IDatabaseConnection CreateConnection(string name = "Default")
				{
					var connectionStringParameters = ConfigurationManager.ConnectionStrings[name];
					var factory = DbProviderFactories.GetFactory(connectionStringParameters.ProviderName);
					
					var connection = factory.CreateConnection();
					try
					{
						connection.ConnectionString = connectionStringParameters.ConnectionString;
						connection.Open();
						return new DatabaseConnection(connection);
					}
					catch
					{
						connection.Dispose();
						throw;
					}
				}
			#endregion

			#region TryLoadByPrimaryKey
				/// <summary>
				/// Loads a record by it's PrimaryKey values.
				/// This is called by the lazy-load of references.
				/// </summary>
				public T TryLoadByPrimaryKey<T>(IDatabaseConnection connection, object primaryKeyValue)
				where
					T: class, IRecord
				{
					if (primaryKeyValue == null)
						throw new ArgumentNullException("primaryKeyValue");

					Filter filter = new Filter();
					filter.Add(Record.GetPrimaryKeyProperty(typeof(T)), primaryKeyValue);

					using(var reader = FastLoadByFilter<T>(connection, filter, null))
					{
						var result = reader.GetNext();

						if (result == null)
							return null;

						if (reader.GetNext() != null)
							throw new DatabaseException("More than one record was found. Primary key exception.");

						return result;
					}
				}
			#endregion

			#region Create
				/// <summary>
				/// Simple calls RecordEmitter.Create.
				/// </summary>
				/// <typeparam name="T">The type of the record to create.</typeparam>
				/// <returns>A new empty record.</returns>
				public T Create<T>(IDatabaseConnection connection)
				where
					T: class, IRecord
				{
					T oldTyped = RecordEmitter.Create<T>();
					object oldUntyped = oldTyped;
					object newUntyped = oldTyped.Clone();
					Record newRecord = (Record)newUntyped;
					newRecord.SetUntypedOldRecord(oldTyped);
					Record oldRecord = (Record)oldUntyped;
					oldRecord.SetRecordMode(RecordMode.ReadOnly);
					return (T)newUntyped;
				}
			#endregion

			#region ChangeTypeFromDatabase
				/// <summary>
				/// Converts DBNull.Value into null or default.
				/// If nullable, discovers the real type before calling Convert.ChangeType.
				/// </summary>
				/// <typeparam name="T">The type of the C# result.</typeparam>
				/// <param name="connection">The connection, if you check the connection type to decide what to do.</param>
				/// <param name="propertyInfo">The propertyInfo of the property about to be set.</param>
				/// <param name="value">The value as it comes from the database.</param>
				/// <returns>The value converted to the right type.</returns>
				public T ChangeTypeFromDatabase<T>(IDatabaseConnection connection, PropertyInfo propertyInfo, object value)
				{
					if (propertyInfo == null)
						throw new ArgumentNullException("propertyInfo");

					if (value == DBNull.Value)
						return default(T);

					var propertyType = propertyInfo.PropertyType;
					if (typeof(IRecord).IsAssignableFrom(propertyType))
					{
						propertyInfo = Record.GetPrimaryKeyProperty(propertyType);
						propertyType = propertyInfo.PropertyType;
					}
						
					var converter = GetTypeConverter(propertyType);
					return (T)converter.ConvertFromDatabase(connection, propertyInfo, value);
				}
			#endregion
			#region ChangeTypeFromDatabaseBoxed
				/// <summary>
				/// Method used internally to change a database type to a csharp type, capable of returning null even
				/// for value types.
				/// </summary>
				public object ChangeTypeFromDatabaseBoxed<T>(IDatabaseConnection connection, PropertyInfo propertyInfo, object value)
				{
					if (value == DBNull.Value)
						return null;

					if (propertyInfo == null)
						throw new ArgumentNullException("propertyInfo");

					var propertyType = propertyInfo.PropertyType;
					if (typeof(IRecord).IsAssignableFrom(propertyType))
					{
						propertyInfo = propertyType.GetPrimaryKeyProperty();
						propertyType = propertyInfo.PropertyType;
					}
						
					var converter = GetTypeConverter(propertyType);
					return converter.ConvertFromDatabase(connection, propertyInfo, value);
				}
			#endregion
			#region AddParameter
				/// <summary>
				/// Adds a parameter and tries to set DbType.
				/// It will throw an exception for unknown types.
				/// </summary>
				/// <param name="connection">The connection.</param>
				/// <param name="command">The command in which the parameter is added.</param>
				/// <param name="parameterName">The name given to the parameter.</param>
				/// <param name="propertyInfo">The propertyInfo from which the value will come.</param>
				/// <param name="value">The effective value to set.</param>
				public IDbDataParameter AddParameter(IDatabaseConnection connection, IDbCommand command, string parameterName, PropertyInfo propertyInfo, object value)
				{
					var parameter = CreateParameter(connection, command, parameterName, propertyInfo);
					SetParameterValue(connection, parameter, propertyInfo, value);
					return parameter;
				}
			#endregion
			#region CreateParameter
				/// <summary>
				/// Creates a parameter without setting it's value, but will try
				/// to set the DbType. It will throw an exception for special types.
				/// </summary>
				/// <param name="connection">The connection.</param>
				/// <param name="command">The command in which the parameter is added.</param>
				/// <param name="parameterName">The name given to the parameter.</param>
				/// <param name="propertyInfo">The propertyInfo from which the value will come.</param>
				/// <returns>The newly created parameter, which was already added to the command Parameters.</returns>
				public IDbDataParameter CreateParameter(IDatabaseConnection connection, IDbCommand command, string parameterName, PropertyInfo propertyInfo)
				{
					if (command == null)
						throw new ArgumentNullException("command");

					if (propertyInfo == null)
						throw new ArgumentNullException("propertyInfo");

					Type propertyType = propertyInfo.PropertyType;
					var converter = GetTypeConverter(propertyType);
					
					IDbDataParameter parameter = command.CreateParameter();
					parameter.ParameterName = parameterName;
					converter.InitializeParameter(connection, parameter, propertyType, propertyInfo);
					command.Parameters.Add(parameter);
					return parameter;
				}
			#endregion
			#region SetParameterValue
				/// <summary>
				/// Sets a parameter value previosly created.
				/// </summary>
				public void SetParameterValue(IDatabaseConnection connection, IDbDataParameter parameter, PropertyInfo propertyInfo, object value)
				{
					if (parameter == null)
						throw new ArgumentNullException("parameter");

					if (propertyInfo == null)
						throw new ArgumentNullException("propertyInfo");

					if (value == null)
						value = DBNull.Value;
					else
					{
						var converter = GetTypeConverter(propertyInfo.PropertyType);
						value = converter.ConvertToDatabase(connection, propertyInfo, value);
						
						if (value == null)
							value = DBNull.Value;
					}
					
					parameter.Value = value;
				}
			#endregion

			#region Apply
				/// <summary>
				/// Applies the given record using the given transaction.
				/// </summary>
				public T Apply<T>(IDatabaseConnection connection, T record)
				where
					T: class, IRecord
				{
					if (connection == null)
						throw new ArgumentNullException("connection");
					
					if (record == null)
						throw new ArgumentNullException("record");
				
					if (!connection.HasActiveTransaction)
						throw new DatabaseException("A transaction must have been initialized.");

					switch(record.GetRecordMode())
					{
						case RecordMode.Insert:
							return (T)_InsertRecord(connection, record);
						
						case RecordMode.Update:
							return (T)_UpdateRecord(connection, record);
						
						case RecordMode.ReadOnly:
							if (record.GetMustDeleteOnApply())
							{
								_DeleteRecord(connection, record);
								return null;
							}
							
							break;
					}
					
					throw new ArgumentException("record must be either of Insert, Update or Delete.", "record");
				}
			#endregion

			#region _InsertRecord
				private object _InsertRecord(IDatabaseConnection connection, IRecord record)
				{
					Type type = record.GetType();
					var databaseProperties = record.GetPropertiesDictionary().Values;
					
					string sql = type.GetField("<<InsertSql>>").GetValue(null).ToString();
					
					var realConnection = (DatabaseConnection)connection;
					using(var command = realConnection.CreateCommand())
					{
						command.CommandText = sql;
						
						int parameterIndex = -1;
						foreach (var databaseProperty in databaseProperties)
						{
							parameterIndex++;

							object value = databaseProperty.GetKeyOrRealValue(record);
							AddParameter(connection, command, "@P" + parameterIndex, databaseProperty.Property, value);
						}
						
						command.ExecuteNonQuery();
					}
					
					return record;
				}
			#endregion
			#region _UpdateRecord
				// This is similar to the delete. The commands are prepared by thread and connection.
				// But, instead of being by type, they are done by the string that creates them,
				// as each update on different fields will yield different command texts.
				//[ThreadStatic]
				//private static ConditionalWeakTable<IDatabaseConnection, WeakDictionary<string, IDbCommand>> fPreparedUpdates;
				private object _UpdateRecord(IDatabaseConnection connection, IRecord record)
				{
					IRecord oldRecord = record.GetUntypedOldRecord();
					Type type = record.GetType();
					var databaseProperties = record.GetPropertiesDictionary().Values;
					var primaryKeyProperty = record.GetPrimaryKeyProperty();

					var realConnection = (DatabaseConnection)connection;
					using(var command = realConnection.CreateCommand())
					{
						StringBuilder sql = new StringBuilder();
						sql.Append("UPDATE ");
						sql.Append(type.GetDatabaseName());
						sql.Append(" SET ");
						
						int countModified = 0;
						foreach(var databaseProperty in databaseProperties)
						{
							object newValue = databaseProperty.GetKeyOrRealValue(record);
							object oldValue = databaseProperty.GetKeyOrRealValue(oldRecord);
							
							if (object.Equals(oldValue, newValue))
								continue;
							
							var property = databaseProperty.Property;
							sql.Append(property.GetDatabaseName());
							sql.Append("=@P");
							sql.Append(countModified);
							sql.Append(", ");
							
							AddParameter(connection, command, "@P" + countModified, property, newValue);
							countModified++;
						}
						
						if (countModified == 0)
							return record;
						
						sql.Length -= 2;
						sql.Append(" WHERE ");
						sql.Append(primaryKeyProperty.DatabaseName);
						sql.Append("=@PW");
						
						object primaryKeyValue = oldRecord.GetPrimaryKeyValue();
						AddParameter(connection, command, "@PW", primaryKeyProperty.Property, primaryKeyValue);

						command.CommandText = sql.ToString();
						if (command.ExecuteNonQuery() == 0)
							throw new DatabaseException("Record was not found.");
					}
					
					return record;
				}
			#endregion
			#region _DeleteRecord
				// The idea is to have the delete commands prepared by connection/thread. Of course, each
				// object (type) has it's own delete command, so the dictionary will be that. A ConditionalWeakTable
				// for the connection, so if the connection dies, everything dies with it, and a WeakDictionary
				// for types, so the command can die if it is not used.
				//[ThreadStatic]
				//private static ConditionalWeakTable<IDatabaseConnection, WeakDictionary<Type, IDbCommand>> fPreparedDeleteCommands;
				private void _DeleteRecord(IDatabaseConnection connection, IRecord record)
				{
					Type type = record.GetType();
					var primaryKeyProperty = Record.GetPrimaryKeyProperty(type);
					
					StringBuilder sql = new StringBuilder();
					sql.Append("DELETE FROM ");
					sql.Append(type.GetDatabaseName());
					sql.Append(" WHERE ");
					sql.Append(primaryKeyProperty.GetDatabaseName());
					sql.Append("=@PW");
					
					var realConnection = (DatabaseConnection)connection;
					using(var command = realConnection.CreateCommand())
					{
						command.CommandText = sql.ToString();
						
						object primaryKeyValue = record.GetPrimaryKeyValue();
						AddParameter(connection, command, "@PW", primaryKeyProperty, primaryKeyValue);

						if (command.ExecuteNonQuery() == 0)
							throw new DatabaseException("Record was not found.");
					}
				}
			#endregion
			
			#region FastLoadByCommand
				/// <summary>
				/// Loads a record using the given command.
				/// The command must load all fields in the right order.
				/// Returns a fast enumerator.
				/// </summary>
				public static IFastEnumerator<T> FastLoadByCommand<T>(IDatabaseConnection connection, IDbCommand command)
				where
					T: class, IRecord
				{
					if (command == null)
						throw new ArgumentNullException("command");
						
					return new FastRecordEnumerator<T>(connection, command, null);
				}
			#endregion
			#region FastLoadByPartialSql
				/// <summary>
				/// Returns a fast enumerator to read records using the given
				/// partial SQL (sql that comes after the select and from clause,
				/// which is built for you).
				/// </summary>
				public IFastEnumerator<T> FastLoadByPartialSql<T>(IDatabaseConnection connection, string sql, params object[] parameterValues)
				where
					T: class, IRecord
				{
					if (connection == null)
						throw new ArgumentNullException("connection");
						
					var realConnection = (DatabaseConnection)connection;
					var command = realConnection.CreateCommand();
					try
					{
						sql = _ParsePartialSql<T>(sql, parameterValues, connection, command);
						command.CommandText = sql;
						return new FastRecordEnumerator<T>(connection, command, null);
					}
					catch
					{
						command.Dispose();
						throw;
					}
				}
			#endregion

			#region _ParsePartialSql
				private string _ParsePartialSql<T>(string sql, object[] parameterValues, IDatabaseConnection connection, IDbCommand command)
				where
					T: class, IRecord
				{
					AdvancedLoadParameters parameters = new AdvancedLoadParameters(typeof(T));

					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.Append("SELECT ");
					parameters.AddFieldsSql(stringBuilder);
					stringBuilder.Append(" FROM ");
					parameters.AddFromAndJoinsSql(stringBuilder);
					stringBuilder.Append(' ');
				
					int parameterIndex = -1;
					if (sql == null)
						sql = stringBuilder.ToString();
					else
					{
						int count = sql.Length;
						int lastBlockInit = 0;
						for (int i = 0; i < count; i++)
						{
							char c = sql[i];

							switch (c)
							{
								case '{':
									{
										i++;

										stringBuilder.Append(sql.Substring(lastBlockInit, i - lastBlockInit - 1));

										int begin = i;
										while (i < count)
										{
											c = sql[i];

											if (c == '}')
												break;

											i++;
										}

										if (i == count)
											throw new DatabaseException("Error parsing SQL. End of statement found before the parameter name ends.");

										string parameterName = sql.Substring(begin, i - begin);
										var propertyPath = _GetPropertyPath(typeof(T), parameterName);
										parameterIndex++;

										string newParameterName = "@P" + parameterIndex;
										stringBuilder.Append(newParameterName);
										
										AddParameter(connection, command, newParameterName, propertyPath[propertyPath.Length-1], parameterValues[parameterIndex]);

										lastBlockInit = i + 1;
									}
									break;

								/*case '[':
									{
										i++;

										stringBuilder.Append(sql.Substring(lastBlockInit, i - lastBlockInit - 1));

										int begin = i;
										while (i < count)
										{
											c = sql[i];

											if (c == ']')
												break;

											i++;
										}

										if (i == count)
											throw new DatabaseException("Error parsing SQL. End of statement found before the parameter name ends.");
										
										string parameterName = sql.Substring(begin, i - begin);
										var propertyPath = _GetPropertyPath(typeof(T), parameterName);
										stringBuilder.Append(propertyPath[0].GetDatabaseName());

										lastBlockInit = i + 1;
									}
									break;*/
							}
						}
						stringBuilder.Append(sql.Substring(lastBlockInit, sql.Length - lastBlockInit));
						sql = stringBuilder.ToString();
					}
					return sql;
				}
			#endregion
			#region _GetPropertyPath
				private static PropertyInfoPath _GetPropertyPath(Type type, string parameterName)
				{
					List<PropertyInfo> result = new List<PropertyInfo>();
					
					string[] parameterNames = parameterName.Split('.');
					foreach(string name in parameterNames)
					{
						PropertyInfo propertyInfo = type.TryGetInterfaceProperty(name);
						
						if (propertyInfo == null)
						{
							if (parameterNames.Length == 1)
								throw new DatabaseException("Property " + name + " does not exist.");
							
							throw new DatabaseException("Property " + name + ", of path " + parameterName + " does not exist.");
						}
						
						type = propertyInfo.PropertyType;
						result.Add(propertyInfo);
					}
					
					return new PropertyInfoPath(result.ToArray());
				}
			#endregion
			
			#region AddTypeConverter
				private TypeDictionary<ITypeConverter> _typeConverters = new TypeDictionary<ITypeConverter>();
				
				/// <summary>
				/// Registers a data-type converter.
				/// If one for the same type already exists, the new one replaces
				/// the old one.
				/// </summary>
				public void RegisterTypeConverter(ITypeConverter converter)
				{
					if (converter == null)
						throw new ArgumentNullException("converter");
						
					Type type = converter.CSharpType;
					
					_typeConverters.Set(type, converter, converter.CanParseSubTypes);
				}
			#endregion
			#region GetTypeConverter
				/// <summary>
				/// Gets the data-type converter for the given type, or throws
				/// an exception.
				/// </summary>
				public ITypeConverter GetTypeConverter(Type type)
				{
					if (type == null)
						throw new ArgumentNullException("type");

					if (typeof(IRecord).IsAssignableFrom(type))
						type = type.GetPrimaryKeyProperty().PropertyType;

					ITypeConverter result = TryGetTypeConverter(type);
					
					if (result == null)
						throw new DatabaseException("Can't find a data-type converter for " + type.FullName);
						
					return result;
				}
				
				/// <summary>
				/// Gets the data-type converter for the given type, or returns
				/// null.
				/// </summary>
				public ITypeConverter TryGetTypeConverter(Type type)
				{
					return _typeConverters.FindUpOrDefault(type);
				}
			#endregion
			
			#region MustCloneBeforeApply
				/// <summary>
				/// Returns true.
				/// </summary>
				public bool MustCloneBeforeApply
				{
					get
					{
						return true;
					}
				}
			#endregion
			
			#region FastLoadByFilter
				/// <summary>
				/// Loads records using the given filter group.
				/// Joins must by created implicity if other records are referenced.
				/// </summary>
				public IFastEnumerator<T> FastLoadByFilter<T>(IDatabaseConnection connection, Filter filter, ReadOnlyCollection<OrderByIndex> orderByIndexes = null)
				where
					T: class, IRecord
				{
					if (!typeof(T).ContainsCustomAttribute<DatabasePersistedAttribute>())
					{
						if (orderByIndexes != null)
							throw new ArgumentException("orderByIndexes can't be null when loading non-persisted records.");

						return new LoadByFilterEnumerator<T>(connection, filter);
					}

					DatabaseConnection dc = (DatabaseConnection)connection;

					StringBuilder sql = null;
					IDbCommand command = null;
					try
					{
						bool canPrepare = true;
						command = dc._GetPreparedCommand(typeof(T), filter, orderByIndexes);

						if (command == null)
						{
							command = dc.CreateCommand();
							sql = new StringBuilder();
						}

						AdvancedLoadParameters parameters = new AdvancedLoadParameters(typeof(T));
						parameters.Filter = filter;
						parameters.FillCommand(sql, connection, command);

						canPrepare = parameters.CanPrepare;

						Action cacheAction = null;
						if (sql != null)
						{
							if (orderByIndexes != null && orderByIndexes.Count > 0)
							{
								sql.Append(" ORDER BY ");

								foreach(var index in orderByIndexes)
								{
									sql.Append(index.ToString());
									sql.Append(", ");
								}

								sql.Length -= 2;
							}

							command.CommandText = sql.ToString();

							if (canPrepare)
							{
								command.Prepare();
								
								cacheAction = () => dc._CachePreparedCommand(typeof(T), filter, orderByIndexes, command);
							}
						}

						var result = new FastRecordEnumerator<T>(connection, command, cacheAction);
						return result;
					}
					catch
					{
						if (command != null)
							command.Dispose();

						throw;
					}
				}
			#endregion
			#region CountRecordsByFilter
				/// <summary>
				/// Returns the number of records that matches the given filter.
				/// </summary>
				public int CountRecordsByFilter<T>(IDatabaseConnection connection, Filter filter)
				where
					T: class, IRecord
				{
					DatabaseConnection dc = (DatabaseConnection)connection;
					StringBuilder sql = new StringBuilder();
					using(var command = dc.CreateCommand())
					{
						if (typeof(T).ContainsCustomAttribute<DatabasePersistedAttribute>())
						{
							sql.Append("SELECT COUNT(*) FROM ");

							AdvancedLoadParameters parameters = new AdvancedLoadParameters(typeof(T));
							parameters.Filter = filter;
							
							parameters.AddFromAndJoinsSql(sql);
							parameters.FillWhere(sql, connection, command);

							command.CommandText = sql.ToString();
							object result = command.ExecuteScalar();
							return Convert.ToInt32(result);
						}
						else
						{
							int result = 0;
							foreach(var persistedType in DatabasePersistedAttribute.GetPersistedSubTypes(typeof(T)))
							{
								sql.Length = 0;
								sql.Append("SELECT COUNT(*) FROM ");

								AdvancedLoadParameters parameters = new AdvancedLoadParameters(typeof(T));
								parameters.Filter = filter;
							
								parameters.AddFromAndJoinsSql(sql);
								parameters.FillWhere(sql, connection, command);

								command.CommandText = sql.ToString();
								object untypedResult = command.ExecuteScalar();
								result += Convert.ToInt32(untypedResult);
							}

							return result;
						}
					}
				}
			#endregion
			#region AdvancedLoad
				/// <summary>
				/// Creates a reader for the given query.
				/// </summary>
				public IFastEnumerator<object[]> AdvancedLoad(IDatabaseConnection connection, AdvancedLoadParameters parameters)
				{
					DatabaseConnection dc = (DatabaseConnection)connection;
					StringBuilder sql = new StringBuilder();

					IDbCommand command = null;
					try
					{
						command = dc.CreateCommand();

						parameters.FillCommand(sql, connection, command);

						var orderByIndexes = parameters.OrderByIndexes;
						if (orderByIndexes != null && orderByIndexes.Count > 0)
						{
							sql.Append(" ORDER BY ");

							foreach(var index in orderByIndexes)
							{
								sql.Append(index.ToString());
								sql.Append(", ");
							}

							sql.Length -= 2;
						}

						command.CommandText = sql.ToString();
						return new FastCommandEnumerator(connection, command, parameters._selectPaths);
					}
					catch
					{
						if (command != null)
							command.Dispose();

						throw;
					}
				}
			#endregion
			
			#region ClassGenerator methods
				/// <summary>
				/// Returns the implemented type for the given interface type.
				/// </summary>
				public Type GetImplementedTypeFor(Type type)
				{
					return RecordEmitter.GetImplementedTypeFor(type);
				}
			#endregion

			#region Delete
				/// <summary>
				/// Deletes from 0 to many records using the given search expression.
				/// Returns the number of deleted records.
				/// </summary>
				public int Delete<T>(IDatabaseConnection databaseConnection, Expression<Func<T, bool>> expression) where T : class, IRecord
				{
					if (expression == null)
						throw new ArgumentNullException("expression", "expression can't be null. Create an expression that deletes all records if you need to delete all.");

					DatabaseConnection connection = (DatabaseConnection)databaseConnection;
					using(var command = connection.CreateCommand())
					{
						StringBuilder sql = new StringBuilder();
						sql.Append("DELETE FROM ");
						sql.Append(typeof(T).GetDatabaseName());
						sql.Append(" WHERE ");

						FilterLinqWhere where = new FilterLinqWhere(expression.Body);
						var parameterBuilder = new ParameterBuilder();
						parameterBuilder.sql = sql;
						parameterBuilder._connection = databaseConnection;
						parameterBuilder._command = command;
						parameterBuilder._FillSqlForGroup(where._group);
						command.CommandText = sql.ToString();
						return command.ExecuteNonQuery();
					}
				}

			#endregion
			#region Update
				/// <summary>
				/// Updates 0 to many records at once, using the given expressions.
				/// </summary>
				/// <typeparam name="T">The type of the record to update.</typeparam>
				/// <param name="databaseConnection">The connection to use.</param>
				/// <param name="setExpression">Fake "boolean" expression. Use == as the "receives" sign, and &amp;&amp; to add other field to be set.</param>
				/// <param name="whereExpression">The expression to use to find records.</param>
				/// <returns>The number of updated records.</returns>
				public int Update<T>(IDatabaseConnection databaseConnection, Expression<Func<T, bool>> setExpression, Expression<Func<T, bool>> whereExpression) where T : class, IRecord
				{
					if (setExpression == null)
						throw new ArgumentNullException("setExpression");

					if (whereExpression == null)
						throw new ArgumentNullException("whereExpression", "whereExpression can't be null. Create an expression that updates all records if you need to update all.");

					DatabaseConnection connection = (DatabaseConnection)databaseConnection;
					using(var command = connection.CreateCommand())
					{
						StringBuilder sql = new StringBuilder();
						sql.Append("UPDATE ");
						sql.Append(typeof(T).GetDatabaseName());
						sql.Append(" SET ");

						int length = sql.Length;
						AddSets addSets = new AddSets();
						addSets._command = command;
						addSets._connection = connection;
						addSets._sql = sql;
						addSets._AddSetExpression(setExpression);

						if (length == sql.Length)
							throw new DatabaseException("There is no value to be set in setExpression.");

						sql.Length -= 2;

						sql.Append(" WHERE ");
						FilterLinqWhere where = new FilterLinqWhere(whereExpression.Body);
						var parameterBuilder = new ParameterBuilder();
						parameterBuilder.sql = sql;
						parameterBuilder._connection = databaseConnection;
						parameterBuilder._command = command;
                        parameterBuilder._parameterIndex = command.Parameters.Count;
						parameterBuilder._FillSqlForGroup(where._group);
						command.CommandText = sql.ToString();
						return command.ExecuteNonQuery();
					}
				}
			#endregion
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions