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 && 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
}
}