diff --git a/Libraries/FastReflection/FastReflection.dll b/Libraries/FastReflection/FastReflection.dll
new file mode 100644
index 000000000..947231aab
Binary files /dev/null and b/Libraries/FastReflection/FastReflection.dll differ
diff --git a/Marr.Data/Converters/BooleanIntConverter.cs b/Marr.Data/Converters/BooleanIntConverter.cs
new file mode 100644
index 000000000..5979628e5
--- /dev/null
+++ b/Marr.Data/Converters/BooleanIntConverter.cs
@@ -0,0 +1,78 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+
+namespace Marr.Data.Converters
+{
+ public class BooleanIntConverter : IConverter
+ {
+ public object FromDB(ColumnMap map, object dbValue)
+ {
+ if (dbValue == DBNull.Value)
+ {
+ return DBNull.Value;
+ }
+
+ int val = (int)dbValue;
+
+ if (val == 1)
+ {
+ return true;
+ }
+ else if (val == 0)
+ {
+ return false;
+ }
+ else
+ {
+ throw new ConversionException(
+ string.Format(
+ "The BooleanCharConverter could not convert the value '{0}' to a boolean.",
+ dbValue));
+ }
+ }
+
+ public object ToDB(object clrValue)
+ {
+ bool? val = (bool?)clrValue;
+
+ if (val == true)
+ {
+ return 1;
+ }
+ else if (val == false)
+ {
+ return 0;
+ }
+ else
+ {
+ return DBNull.Value;
+ }
+ }
+
+ public Type DbType
+ {
+ get
+ {
+ return typeof(int);
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Converters/BooleanYNConverter.cs b/Marr.Data/Converters/BooleanYNConverter.cs
new file mode 100644
index 000000000..3f3acf0c1
--- /dev/null
+++ b/Marr.Data/Converters/BooleanYNConverter.cs
@@ -0,0 +1,78 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+
+namespace Marr.Data.Converters
+{
+ public class BooleanYNConverter : IConverter
+ {
+ public object FromDB(ColumnMap map, object dbValue)
+ {
+ if (dbValue == DBNull.Value)
+ {
+ return DBNull.Value;
+ }
+
+ string val = dbValue.ToString();
+
+ if (val == "Y")
+ {
+ return true;
+ }
+ else if (val == "N")
+ {
+ return false;
+ }
+ else
+ {
+ throw new ConversionException(
+ string.Format(
+ "The BooleanYNConverter could not convert the value '{0}' to a boolean.",
+ dbValue));
+ }
+ }
+
+ public object ToDB(object clrValue)
+ {
+ bool? val = (bool?)clrValue;
+
+ if (val == true)
+ {
+ return "Y";
+ }
+ else if (val == false)
+ {
+ return "N";
+ }
+ else
+ {
+ return DBNull.Value;
+ }
+ }
+
+ public Type DbType
+ {
+ get
+ {
+ return typeof(string);
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Converters/CastConverter.cs b/Marr.Data/Converters/CastConverter.cs
new file mode 100644
index 000000000..2c1913966
--- /dev/null
+++ b/Marr.Data/Converters/CastConverter.cs
@@ -0,0 +1,49 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Marr.Data.Converters
+{
+ public class CastConverter : Marr.Data.Converters.IConverter
+ where TClr : IConvertible
+ where TDb : IConvertible
+ {
+ #region IConversion Members
+
+ public Type DbType
+ {
+ get { return typeof(TDb); }
+ }
+
+ public object FromDB(Marr.Data.Mapping.ColumnMap map, object dbValue)
+ {
+ TDb val = (TDb)dbValue;
+ return val.ToType(typeof(TClr), System.Globalization.CultureInfo.InvariantCulture);
+ }
+
+ public object ToDB(object clrValue)
+ {
+ TClr val = (TClr)clrValue;
+ return val.ToType(typeof(TDb), System.Globalization.CultureInfo.InvariantCulture);
+ }
+
+ #endregion
+ }
+}
+
diff --git a/Marr.Data/Converters/ConversionException.cs b/Marr.Data/Converters/ConversionException.cs
new file mode 100644
index 000000000..074b5604f
--- /dev/null
+++ b/Marr.Data/Converters/ConversionException.cs
@@ -0,0 +1,29 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Marr.Data.Converters
+{
+ public class ConversionException : Exception
+ {
+ public ConversionException(string message)
+ : base(message)
+ { }
+ }
+}
diff --git a/Marr.Data/Converters/EnumIntConverter.cs b/Marr.Data/Converters/EnumIntConverter.cs
new file mode 100644
index 000000000..41515737e
--- /dev/null
+++ b/Marr.Data/Converters/EnumIntConverter.cs
@@ -0,0 +1,50 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+
+namespace Marr.Data.Converters
+{
+ public class EnumIntConverter : IConverter
+ {
+ public object FromDB(ColumnMap map, object dbValue)
+ {
+ if (dbValue == null || dbValue == DBNull.Value)
+ return null;
+ else
+ return Enum.ToObject(map.FieldType, (int)dbValue);
+ }
+
+ public object ToDB(object clrValue)
+ {
+ if (clrValue == null)
+ return DBNull.Value;
+ else
+ return (int)clrValue;
+ }
+
+ public Type DbType
+ {
+ get
+ {
+ return typeof(int);
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Converters/EnumStringConverter.cs b/Marr.Data/Converters/EnumStringConverter.cs
new file mode 100644
index 000000000..964e5824c
--- /dev/null
+++ b/Marr.Data/Converters/EnumStringConverter.cs
@@ -0,0 +1,50 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+
+namespace Marr.Data.Converters
+{
+ public class EnumStringConverter : IConverter
+ {
+ public object FromDB(ColumnMap map, object dbValue)
+ {
+ if (dbValue == null || dbValue == DBNull.Value)
+ return null;
+ else
+ return Enum.Parse(map.FieldType, (string)dbValue);
+ }
+
+ public object ToDB(object clrValue)
+ {
+ if (clrValue == null)
+ return DBNull.Value;
+ else
+ return clrValue.ToString();
+ }
+
+ public Type DbType
+ {
+ get
+ {
+ return typeof(string);
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Converters/IConverter.cs b/Marr.Data/Converters/IConverter.cs
new file mode 100644
index 000000000..00b1fa47b
--- /dev/null
+++ b/Marr.Data/Converters/IConverter.cs
@@ -0,0 +1,30 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+
+namespace Marr.Data.Converters
+{
+ public interface IConverter
+ {
+ object FromDB(ColumnMap map, object dbValue);
+ object ToDB(object clrValue);
+ Type DbType { get; }
+ }
+}
diff --git a/Marr.Data/DataHelper.cs b/Marr.Data/DataHelper.cs
new file mode 100644
index 000000000..721511a2a
--- /dev/null
+++ b/Marr.Data/DataHelper.cs
@@ -0,0 +1,165 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Data;
+using System.Reflection;
+using Marr.Data.Mapping;
+using System.Linq.Expressions;
+
+namespace Marr.Data
+{
+ ///
+ /// This class contains misc. extension methods that are used throughout the project.
+ ///
+ internal static class DataHelper
+ {
+ public static bool HasColumn(this IDataReader dr, string columnName)
+ {
+ for (int i=0; i < dr.FieldCount; i++)
+ {
+ if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
+ return true;
+ }
+ return false;
+ }
+
+ public static string ParameterPrefix(this IDbCommand command)
+ {
+ string commandType = command.GetType().Name.ToLower();
+ return commandType.Contains("oracle") ? ":" : "@";
+ }
+
+ ///
+ /// Returns the mapped name, or the member name.
+ ///
+ ///
+ ///
+ public static string GetTableName(this MemberInfo member)
+ {
+ string tableName = MapRepository.Instance.GetTableName(member.DeclaringType);
+ return tableName ?? member.DeclaringType.Name;
+ }
+
+ public static string GetTableName(this Type memberType)
+ {
+ return MapRepository.Instance.GetTableName(memberType);
+ }
+
+ public static string GetColumName(this IColumnInfo col, bool useAltName)
+ {
+ if (useAltName)
+ {
+ return col.TryGetAltName();
+ }
+ else
+ {
+ return col.Name;
+ }
+ }
+
+ ///
+ /// Returns the mapped column name, or the member name.
+ ///
+ ///
+ ///
+ public static string GetColumnName(Type declaringType, string propertyName, bool useAltName)
+ {
+ // Initialize column name as member name
+ string columnName = propertyName;
+
+ var columnMap = MapRepository.Instance.GetColumns(declaringType).GetByFieldName(propertyName);
+ if (useAltName)
+ {
+ columnName = columnMap.ColumnInfo.TryGetAltName();
+ }
+ else
+ {
+ columnName = columnMap.ColumnInfo.Name;
+ }
+
+ return columnName;
+ }
+
+ ///
+ /// Determines a property name from a passed in expression.
+ /// Ex: p => p.FirstName -> "FirstName
+ ///
+ ///
+ ///
+ ///
+ public static string GetMemberName(this Expression> member)
+ {
+ var memberExpression = (member.Body as MemberExpression);
+ if (memberExpression == null)
+ {
+ memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression;
+ }
+
+ return memberExpression.Member.Name;
+ }
+
+ public static string GetMemberName(this LambdaExpression exp)
+ {
+ var memberExpression = (exp.Body as MemberExpression);
+ if (memberExpression == null)
+ {
+ memberExpression = (exp.Body as UnaryExpression).Operand as MemberExpression;
+ }
+
+ return memberExpression.Member.Name;
+ }
+
+ public static bool ContainsMember(this List list, MemberInfo member)
+ {
+ foreach (var m in list)
+ {
+ if (m.EqualsMember(member))
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool EqualsMember(this MemberInfo member, MemberInfo otherMember)
+ {
+ return member.Name == otherMember.Name && member.DeclaringType == otherMember.DeclaringType;
+ }
+
+ ///
+ /// Determines if a type is not a complex object.
+ ///
+ public static bool IsSimpleType(Type type)
+ {
+ Type underlyingType = !IsNullableType(type) ? type : type.GetGenericArguments()[0];
+
+ return
+ underlyingType.IsPrimitive ||
+ underlyingType.Equals(typeof(string)) ||
+ underlyingType.Equals(typeof(DateTime)) ||
+ underlyingType.Equals(typeof(decimal)) ||
+ underlyingType.IsEnum;
+ }
+
+ public static bool IsNullableType(Type theType)
+ {
+ return (theType.IsGenericType && theType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
+ }
+
+ }
+}
diff --git a/Marr.Data/DataMapper.cs b/Marr.Data/DataMapper.cs
new file mode 100644
index 000000000..8ad948eae
--- /dev/null
+++ b/Marr.Data/DataMapper.cs
@@ -0,0 +1,996 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Data.Common;
+using System.Data.SqlClient;
+using System.Reflection;
+using System.Collections;
+using System.Linq;
+using Marr.Data.Mapping;
+using Marr.Data.Converters;
+using Marr.Data.Parameters;
+using Marr.Data.QGen;
+using System.Linq.Expressions;
+using System.Diagnostics;
+
+namespace Marr.Data
+{
+ ///
+ /// This class is the main access point for making database related calls.
+ ///
+ public class DataMapper : IDataMapper
+ {
+
+ #region - Contructor, Members -
+
+ private DbProviderFactory _dbProviderFactory;
+ private string _connectionString;
+ private DbCommand _command;
+
+ ///
+ /// Initializes a DataMapper for the given provider type and connection string.
+ ///
+ /// Ex:
+ /// The database connection string.
+ public DataMapper(string providerName, string connectionString)
+ : this(DbProviderFactories.GetFactory(providerName), connectionString)
+ { }
+
+ ///
+ /// A database provider agnostic initialization.
+ ///
+ /// The database connection string.
+ public DataMapper(DbProviderFactory dbProviderFactory, string connectionString)
+ {
+ if (dbProviderFactory == null)
+ throw new ArgumentNullException("dbProviderFactory instance cannot be null.");
+
+ if (string.IsNullOrEmpty(connectionString))
+ throw new ArgumentNullException("connectionString cannot be null or empty.");
+
+ _dbProviderFactory = dbProviderFactory;
+
+ _connectionString = connectionString;
+ }
+
+ public string ConnectionString
+ {
+ get
+ {
+ return _connectionString;
+ }
+ }
+
+ public DbProviderFactory ProviderFactory
+ {
+ get
+ {
+ return _dbProviderFactory;
+ }
+ }
+
+ ///
+ /// Creates a new command utilizing the connection string.
+ ///
+ private DbCommand CreateNewCommand()
+ {
+ DbConnection conn = _dbProviderFactory.CreateConnection();
+ conn.ConnectionString = _connectionString;
+ DbCommand cmd = conn.CreateCommand();
+ SetSqlMode(cmd);
+ return cmd;
+ }
+
+ ///
+ /// Creates a new command utilizing the connection string with a given SQL command.
+ ///
+ private DbCommand CreateNewCommand(string sql)
+ {
+ DbCommand cmd = CreateNewCommand();
+ cmd.CommandText = sql;
+ return cmd;
+ }
+
+ ///
+ /// Gets or creates a DbCommand object.
+ ///
+ public DbCommand Command
+ {
+ get
+ {
+ // Lazy load
+ if (_command == null)
+ _command = CreateNewCommand();
+ else
+ SetSqlMode(_command); // Set SqlMode every time.
+
+ return _command;
+ }
+ }
+
+ #endregion
+
+ #region - Parameters -
+
+ public DbParameterCollection Parameters
+ {
+ get
+ {
+ return Command.Parameters;
+ }
+ }
+
+ public ParameterChainMethods AddParameter(string name, object value)
+ {
+ return new ParameterChainMethods(Command, name, value);
+ }
+
+ public IDbDataParameter AddParameter(IDbDataParameter parameter)
+ {
+ // Convert null values to DBNull.Value
+ if (parameter.Value == null)
+ parameter.Value = DBNull.Value;
+
+ this.Parameters.Add(parameter);
+ return parameter;
+ }
+
+ #endregion
+
+ #region - SP / SQL Mode -
+
+ private SqlModes _sqlMode = SqlModes.StoredProcedure; // Defaults to SP.
+ ///
+ /// Gets or sets a value that determines whether the DataMapper will
+ /// use a stored procedure or a sql text command to access
+ /// the database. The default is stored procedure.
+ ///
+ public SqlModes SqlMode
+ {
+ get
+ {
+ return _sqlMode;
+ }
+ set
+ {
+ _sqlMode = value;
+ }
+ }
+
+ ///
+ /// Sets the DbCommand objects CommandType to the current SqlMode.
+ ///
+ /// The DbCommand object we are modifying.
+ /// Returns the same DbCommand that was passed in.
+ private DbCommand SetSqlMode(DbCommand command)
+ {
+ if (SqlMode == SqlModes.StoredProcedure)
+ command.CommandType = CommandType.StoredProcedure;
+ else
+ command.CommandType = CommandType.Text;
+
+ return command;
+ }
+
+ #endregion
+
+ #region - ExecuteScalar, ExecuteNonQuery, ExecuteReader -
+
+ ///
+ /// Executes a stored procedure that returns a scalar value.
+ ///
+ /// The SQL command to execute.
+ /// A scalar value
+ public object ExecuteScalar(string sql)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+ else
+ Command.CommandText = sql;
+
+ try
+ {
+ OpenConnection();
+ return Command.ExecuteScalar();
+ }
+ finally
+ {
+ CloseConnection();
+ }
+ }
+
+ ///
+ /// Executes a non query that returns an integer.
+ ///
+ /// The SQL command to execute.
+ /// An integer value
+ public int ExecuteNonQuery(string sql)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+ else
+ Command.CommandText = sql;
+
+ try
+ {
+ OpenConnection();
+ return Command.ExecuteNonQuery();
+ }
+ finally
+ {
+ CloseConnection();
+ }
+ }
+
+ ///
+ /// Executes a DataReader that can be controlled using a Func delegate.
+ /// (Note that reader.Read() will be called automatically).
+ ///
+ /// The type that will be return in the result set.
+ /// The sql statement that will be executed.
+ /// The function that will build the the TResult set.
+ /// An IEnumerable of TResult.
+ public IEnumerable ExecuteReader(string sql, Func func)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+ else
+ Command.CommandText = sql;
+
+ try
+ {
+ OpenConnection();
+
+ List list = new List();
+ DbDataReader reader = null;
+ try
+ {
+ reader = Command.ExecuteReader();
+
+ while (reader.Read())
+ {
+ list.Add(func(reader));
+ }
+
+ return list;
+ }
+ finally
+ {
+ if (reader != null) reader.Close();
+ }
+ }
+ finally
+ {
+ CloseConnection();
+ }
+ }
+
+ ///
+ /// Executes a DataReader that can be controlled using an Action delegate.
+ ///
+ /// The sql statement that will be executed.
+ /// The delegate that will work with the result set.
+ public void ExecuteReader(string sql, Action action)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+ else
+ Command.CommandText = sql;
+
+ try
+ {
+ OpenConnection();
+
+ DbDataReader reader = null;
+ try
+ {
+ reader = Command.ExecuteReader();
+
+ while (reader.Read())
+ {
+ action(reader);
+ }
+ }
+ finally
+ {
+ if (reader != null) reader.Close();
+ }
+ }
+ finally
+ {
+ CloseConnection();
+ }
+ }
+
+ #endregion
+
+ #region - DataSets -
+
+ public DataSet GetDataSet(string sql)
+ {
+ return GetDataSet(sql, new DataSet(), null);
+ }
+
+ public DataSet GetDataSet(string sql, DataSet ds, string tableName)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+
+ try
+ {
+ using (DbDataAdapter adapter = _dbProviderFactory.CreateDataAdapter())
+ {
+ Command.CommandText = sql;
+ adapter.SelectCommand = Command;
+
+ if (ds == null)
+ ds = new DataSet();
+
+ OpenConnection();
+
+ if (string.IsNullOrEmpty(tableName))
+ adapter.Fill(ds);
+ else
+ adapter.Fill(ds, tableName);
+
+ return ds;
+ }
+ }
+ finally
+ {
+ CloseConnection(); // Clears parameters
+ }
+ }
+
+ public DataTable GetDataTable(string sql)
+ {
+ return GetDataTable(sql, null, null);
+ }
+
+ public DataTable GetDataTable(string sql, DataTable dt, string tableName)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+
+ try
+ {
+ using (DbDataAdapter adapter = _dbProviderFactory.CreateDataAdapter())
+ {
+ Command.CommandText = sql;
+ adapter.SelectCommand = Command;
+
+ if (dt == null)
+ dt = new DataTable();
+
+ adapter.Fill(dt);
+
+ if (!string.IsNullOrEmpty(tableName))
+ dt.TableName = tableName;
+
+ return dt;
+ }
+ }
+ finally
+ {
+ CloseConnection(); // Clears parameters
+ }
+ }
+
+ public int UpdateDataSet(DataSet ds, string sql)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+
+ if (ds == null)
+ throw new ArgumentNullException("ds", "DataSet cannot be null.");
+
+ DbDataAdapter adapter = null;
+
+ try
+ {
+ adapter = _dbProviderFactory.CreateDataAdapter();
+
+ adapter.UpdateCommand = Command;
+ adapter.UpdateCommand.CommandText = sql;
+
+ return adapter.Update(ds);
+ }
+ finally
+ {
+ if (adapter.UpdateCommand != null)
+ adapter.UpdateCommand.Dispose();
+
+ adapter.Dispose();
+ }
+ }
+
+ public int InsertDataTable(DataTable table, string insertSP)
+ {
+ return this.InsertDataTable(table, insertSP, UpdateRowSource.None);
+ }
+
+ public int InsertDataTable(DataTable dt, string sql, UpdateRowSource updateRowSource)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+
+ if (dt == null)
+ throw new ArgumentNullException("dt", "DataTable cannot be null.");
+
+ DbDataAdapter adapter = null;
+
+ try
+ {
+ adapter = _dbProviderFactory.CreateDataAdapter();
+
+ adapter.InsertCommand = Command;
+ adapter.InsertCommand.CommandText = sql;
+
+ adapter.InsertCommand.UpdatedRowSource = updateRowSource;
+
+ return adapter.Update(dt);
+ }
+ finally
+ {
+ if (adapter.InsertCommand != null)
+ adapter.InsertCommand.Dispose();
+
+ adapter.Dispose();
+ }
+ }
+
+ public int DeleteDataTable(DataTable dt, string sql)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
+
+ if (dt == null)
+ throw new ArgumentNullException("dt", "DataSet cannot be null.");
+
+ DbDataAdapter adapter = null;
+
+ try
+ {
+ adapter = _dbProviderFactory.CreateDataAdapter();
+
+ adapter.DeleteCommand = Command;
+ adapter.DeleteCommand.CommandText = sql;
+
+ return adapter.Update(dt);
+ }
+ finally
+ {
+ if (adapter.DeleteCommand != null)
+ adapter.DeleteCommand.Dispose();
+
+ adapter.Dispose();
+ }
+ }
+
+ #endregion
+
+ #region - Find -
+
+ public T Find(string sql)
+ {
+ return this.Find(sql, default(T));
+ }
+
+ ///
+ /// Returns an entity of type T.
+ ///
+ /// The type of entity that is to be instantiated and loaded with values.
+ /// The SQL command to execute.
+ /// An instantiated and loaded entity of type T.
+ public T Find(string sql, T ent)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A stored procedure name has not been specified for 'Find'.");
+
+ Type entityType = typeof(T);
+ Command.CommandText = sql;
+
+ MapRepository repository = MapRepository.Instance;
+ ColumnMapCollection mappings = repository.GetColumns(entityType);
+
+ bool isSimpleType = DataHelper.IsSimpleType(typeof(T));
+
+ try
+ {
+ OpenConnection();
+ var mappingHelper = new MappingHelper(this);
+
+ using (DbDataReader reader = Command.ExecuteReader())
+ {
+ if (reader.Read())
+ {
+ if (isSimpleType)
+ {
+ return mappingHelper.LoadSimpleValueFromFirstColumn(reader);
+ }
+ else
+ {
+ if (ent == null)
+ ent = (T)mappingHelper.CreateAndLoadEntity(mappings, reader, false);
+ else
+ mappingHelper.LoadExistingEntity(mappings, reader, ent, false);
+ }
+ }
+ }
+ }
+ finally
+ {
+ CloseConnection();
+ }
+
+ return ent;
+ }
+
+ #endregion
+
+ #region - Query -
+
+ ///
+ /// Creates a QueryBuilder that allows you to build a query.
+ ///
+ /// The type of object that will be queried.
+ /// Returns a QueryBuilder of T.
+ public QueryBuilder Query()
+ {
+ var dialect = QGen.QueryFactory.CreateDialect(this);
+ return new QueryBuilder(this, dialect);
+ }
+
+ ///
+ /// Returns the results of a query.
+ /// Uses a List of type T to return the data.
+ ///
+ /// Returns a list of the specified type.
+ public List Query(string sql)
+ {
+ return (List)Query(sql, new List());
+ }
+
+ ///
+ /// Returns the results of a SP query.
+ ///
+ /// Returns a list of the specified type.
+ public ICollection Query(string sql, ICollection entityList)
+ {
+ return Query(sql, entityList, false);
+ }
+
+ internal ICollection Query(string sql, ICollection entityList, bool useAltName)
+ {
+ if (entityList == null)
+ throw new ArgumentNullException("entityList", "ICollection instance cannot be null.");
+
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "A query or stored procedure has not been specified for 'Query'.");
+
+ var mappingHelper = new MappingHelper(this);
+ Type entityType = typeof(T);
+ Command.CommandText = sql;
+ ColumnMapCollection mappings = MapRepository.Instance.GetColumns(entityType);
+
+ bool isSimpleType = DataHelper.IsSimpleType(typeof(T));
+
+ try
+ {
+ OpenConnection();
+ using (DbDataReader reader = Command.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ if (isSimpleType)
+ {
+ entityList.Add(mappingHelper.LoadSimpleValueFromFirstColumn(reader));
+ }
+ else
+ {
+ entityList.Add((T)mappingHelper.CreateAndLoadEntity(mappings, reader, useAltName));
+ }
+ }
+ }
+ }
+ finally
+ {
+ CloseConnection();
+ }
+
+ return entityList;
+ }
+
+ #endregion
+
+ #region - Query to Graph -
+
+ public List QueryToGraph(string sql)
+ {
+ return (List)QueryToGraph(sql, new List());
+ }
+
+ public ICollection QueryToGraph(string sql, ICollection entityList)
+ {
+ EntityGraph graph = new EntityGraph(typeof(T), (IList)entityList);
+ return QueryToGraph(sql, graph, new List());
+ }
+
+ ///
+ /// Queries a view that joins multiple tables and returns an object graph.
+ ///
+ ///
+ ///
+ ///
+ /// Coordinates loading all objects in the graph..
+ ///
+ internal ICollection QueryToGraph(string sql, EntityGraph graph, List childrenToLoad)
+ {
+ if (string.IsNullOrEmpty(sql))
+ throw new ArgumentNullException("sql", "sql");
+
+ var mappingHelper = new MappingHelper(this);
+ Type parentType = typeof(T);
+ Command.CommandText = sql;
+
+ try
+ {
+ OpenConnection();
+ using (DbDataReader reader = Command.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // The entire EntityGraph is traversed for each record,
+ // and multiple entities are created from each view record.
+ foreach (EntityGraph lvl in graph)
+ {
+ // If is child relationship entity, and childrenToLoad are specified, and entity is not listed,
+ // then skip this entity.
+ if (childrenToLoad.Count > 0 && !lvl.IsRoot && !childrenToLoad.ContainsMember(lvl.Member)) // lvl.Member.Name
+ {
+ continue;
+ }
+
+ if (lvl.IsNewGroup(reader))
+ {
+ var newEntity = mappingHelper.CreateAndLoadEntity(lvl.EntityType, lvl.Columns, reader, true);
+
+ // Add entity to the appropriate place in the object graph
+ lvl.AddEntity(newEntity);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ CloseConnection();
+ }
+
+ return (ICollection)graph.RootList;
+ }
+
+ #endregion
+
+ #region - Update -
+
+ public UpdateQueryBuilder Update()
+ {
+ return new UpdateQueryBuilder(this);
+ }
+
+ public int Update(T entity, Expression> filter)
+ {
+ return Update()
+ .Entity(entity)
+ .Where(filter)
+ .Execute();
+ }
+
+ public int Update(string tableName, T entity, Expression> filter)
+ {
+ return Update()
+ .TableName(tableName)
+ .Entity(entity)
+ .Where(filter)
+ .Execute();
+ }
+
+ public int Update(T entity, string sql)
+ {
+ return Update()
+ .Entity(entity)
+ .QueryText(sql)
+ .Execute();
+ }
+
+ #endregion
+
+ #region - Insert -
+
+ ///
+ /// Creates an InsertQueryBuilder that allows you to build an insert statement.
+ /// This method gives you the flexibility to manually configure all options of your insert statement.
+ /// Note: You must manually call the Execute() chaining method to run the query.
+ ///
+ public InsertQueryBuilder Insert()
+ {
+ return new InsertQueryBuilder(this);
+ }
+
+ ///
+ /// Generates and executes an insert query for the given entity.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ public object Insert(T entity)
+ {
+ var columns = MapRepository.Instance.GetColumns(typeof(T));
+ var dialect = QueryFactory.CreateDialect(this);
+ var builder = Insert().Entity(entity);
+
+ // If an auto-increment column exists and this dialect has an identity query...
+ if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
+ {
+ builder.GetIdentity();
+ }
+
+ return builder.Execute();
+ }
+
+ ///
+ /// Generates and executes an insert query for the given entity.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ public object Insert(string tableName, T entity)
+ {
+ var columns = MapRepository.Instance.GetColumns(typeof(T));
+ var dialect = QueryFactory.CreateDialect(this);
+ var builder = Insert().Entity(entity).TableName(tableName);
+
+ // If an auto-increment column exists and this dialect has an identity query...
+ if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
+ {
+ builder.GetIdentity();
+ }
+
+ return builder.Execute();
+ }
+
+ ///
+ /// Executes an insert query for the given entity using the given sql insert statement.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ public object Insert(T entity, string sql)
+ {
+ var columns = MapRepository.Instance.GetColumns(typeof(T));
+ var dialect = QueryFactory.CreateDialect(this);
+ var builder = Insert().Entity(entity).QueryText(sql);
+
+ // If an auto-increment column exists and this dialect has an identity query...
+ if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
+ {
+ builder.GetIdentity();
+ }
+
+ return builder.Execute();
+ }
+
+ #endregion
+
+ #region - Delete -
+
+ public int Delete(Expression> filter)
+ {
+ return Delete(null, filter);
+ }
+
+ public int Delete(string tableName, Expression> filter)
+ {
+ // Remember sql mode
+ var previousSqlMode = this.SqlMode;
+ SqlMode = SqlModes.Text;
+
+ var mappingHelper = new MappingHelper(this);
+ if (tableName == null)
+ {
+ tableName = MapRepository.Instance.GetTableName(typeof(T));
+ }
+ var dialect = QGen.QueryFactory.CreateDialect(this);
+ TableCollection tables = new TableCollection();
+ tables.Add(new Table(typeof(T)));
+ var where = new WhereBuilder(Command, dialect, filter, tables, false, false);
+ IQuery query = QueryFactory.CreateDeleteQuery(dialect, tables[0], where.ToString());
+ Command.CommandText = query.Generate();
+
+ int rowsAffected = 0;
+
+ try
+ {
+ OpenConnection();
+ rowsAffected = Command.ExecuteNonQuery();
+ }
+ finally
+ {
+ CloseConnection();
+ }
+
+ // Return to previous sql mode
+ SqlMode = previousSqlMode;
+
+ return rowsAffected;
+ }
+
+ #endregion
+
+ #region - Events -
+
+ public event EventHandler OpeningConnection;
+
+ public event EventHandler ClosingConnection;
+
+ #endregion
+
+ #region - Connections / Transactions -
+
+ protected virtual void OnOpeningConnection()
+ {
+ if (OpeningConnection != null)
+ OpeningConnection(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnClosingConnection()
+ {
+ WriteToTraceLog();
+
+ if (ClosingConnection != null)
+ ClosingConnection(this, EventArgs.Empty);
+ }
+
+ protected internal void OpenConnection()
+ {
+ OnOpeningConnection();
+
+ if (Command.Connection.State != ConnectionState.Open)
+ Command.Connection.Open();
+ }
+
+ protected internal void CloseConnection()
+ {
+ OnClosingConnection();
+
+ Command.Parameters.Clear();
+ Command.CommandText = string.Empty;
+
+ if (Command.Transaction == null)
+ Command.Connection.Close(); // Only close if no transaction is present
+
+ UnbindEvents();
+ }
+
+ private void WriteToTraceLog()
+ {
+ if (MapRepository.Instance.EnableTraceLogging)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine();
+ sb.AppendLine("==== Begin Query Trace ====");
+ sb.AppendLine();
+ sb.AppendLine("QUERY TYPE:");
+ sb.AppendLine(Command.CommandType.ToString());
+ sb.AppendLine();
+ sb.AppendLine("QUERY TEXT:");
+ sb.AppendLine(Command.CommandText);
+ sb.AppendLine();
+ sb.AppendLine("PARAMETERS:");
+ foreach (IDbDataParameter p in Parameters)
+ {
+ object val = (p.Value != null && p.Value is string) ? string.Format("\"{0}\"", p.Value) : p.Value;
+ sb.AppendFormat("{0} = [{1}]", p.ParameterName, val ?? "NULL").AppendLine();
+ }
+ sb.AppendLine();
+ sb.AppendLine("==== End Query Trace ====");
+ sb.AppendLine();
+
+ Trace.Write(sb.ToString());
+ }
+ }
+
+ private void UnbindEvents()
+ {
+ if (OpeningConnection != null)
+ OpeningConnection = null;
+
+ if (ClosingConnection != null)
+ ClosingConnection = null;
+ }
+
+ public void BeginTransaction()
+ {
+ OpenConnection();
+ DbTransaction trans = Command.Connection.BeginTransaction();
+ Command.Transaction = trans;
+ }
+
+ public void RollBack()
+ {
+ try
+ {
+ if (Command.Transaction != null)
+ Command.Transaction.Rollback();
+ }
+ finally
+ {
+ Command.Connection.Close();
+ }
+ }
+
+ public void Commit()
+ {
+ try
+ {
+ if (Command.Transaction != null)
+ Command.Transaction.Commit();
+ }
+ finally
+ {
+ Command.Connection.Close();
+ }
+ }
+
+ #endregion
+
+ #region - IDisposable Members -
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this); // In case a derived class implements a finalizer
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (Command.Transaction != null)
+ {
+ Command.Transaction.Dispose();
+ Command.Transaction = null;
+ }
+
+ if (Command.Connection != null)
+ {
+ Command.Connection.Dispose();
+ Command.Connection = null;
+ }
+
+ if (Command != null)
+ {
+ Command.Dispose();
+ _command = null;
+ }
+ }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/Marr.Data/DataMappingException.cs b/Marr.Data/DataMappingException.cs
new file mode 100644
index 000000000..2ab5619be
--- /dev/null
+++ b/Marr.Data/DataMappingException.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Marr.Data
+{
+ public class DataMappingException : Exception
+ {
+ public DataMappingException()
+ : base()
+ {
+ }
+
+ public DataMappingException(string message)
+ : base(message)
+ {
+ }
+
+ public DataMappingException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/Marr.Data/EntityGraph.cs b/Marr.Data/EntityGraph.cs
new file mode 100644
index 000000000..b28fbc69c
--- /dev/null
+++ b/Marr.Data/EntityGraph.cs
@@ -0,0 +1,376 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Data.Common;
+using System.Linq;
+using Marr.Data.Mapping;
+using System.Reflection;
+
+namespace Marr.Data
+{
+ ///
+ /// Holds metadata about an object graph that is being queried and eagerly loaded.
+ /// Contains all metadata needed to instantiate the object and fill it with data from a DataReader.
+ /// Does not iterate through lazy loaded child relationships.
+ ///
+ internal class EntityGraph : IEnumerable
+ {
+ private MapRepository _repos;
+ private EntityGraph _parent;
+ private Type _entityType;
+ private Relationship _relationship;
+ private ColumnMapCollection _columns;
+ private RelationshipCollection _relationships;
+ private List _children;
+ private object _entity;
+ private GroupingKeyCollection _groupingKeyColumns;
+ private Dictionary _entityReferences;
+
+ public IList RootList { get; private set; }
+
+ ///
+ /// Recursively builds an entity graph of the given parent type.
+ ///
+ ///
+ public EntityGraph(Type entityType, IList rootList)
+ : this(entityType, null, null) // Recursively constructs hierarchy
+ {
+ RootList = rootList;
+ }
+
+ ///
+ /// Recursively builds entity graph hierarchy.
+ ///
+ ///
+ ///
+ ///
+ private EntityGraph(Type entityType, EntityGraph parent, Relationship relationship)
+ {
+ _repos = MapRepository.Instance;
+
+ _entityType = entityType;
+ _parent = parent;
+ _relationship = relationship;
+ _columns = _repos.GetColumns(entityType);
+ _relationships = _repos.GetRelationships(entityType);
+ _children = new List();
+ Member = relationship != null ? relationship.Member : null;
+ _entityReferences = new Dictionary();
+
+ // Create a new EntityGraph for each child relationship that is not lazy loaded
+ foreach (Relationship childRelationship in this.Relationships)
+ {
+ if (!childRelationship.IsLazyLoaded)
+ {
+ _children.Add(new EntityGraph(childRelationship.RelationshipInfo.EntityType, this, childRelationship));
+ }
+ }
+ }
+
+ public MemberInfo Member { get; private set; }
+
+ ///
+ /// Gets the parent of this EntityGraph.
+ ///
+ public EntityGraph Parent
+ {
+ get
+ {
+ return _parent;
+ }
+ }
+
+ ///
+ /// Gets the Type of this EntityGraph.
+ ///
+ public Type EntityType
+ {
+ get { return _entityType; }
+ }
+
+ ///
+ /// Gets a boolean than indicates whether this entity is the root node in the graph.
+ ///
+ public bool IsRoot
+ {
+ get
+ {
+ return _parent == null;
+ }
+ }
+
+ ///
+ /// Gets a boolean that indicates whether this entity is a child.
+ ///
+ public bool IsChild
+ {
+ get
+ {
+ return _parent != null;
+ }
+ }
+
+ ///
+ /// Gets the columns mapped to this entity.
+ ///
+ public ColumnMapCollection Columns
+ {
+ get { return _columns; }
+ }
+
+ ///
+ /// Gets the relationships mapped to this entity.
+ ///
+ public RelationshipCollection Relationships
+ {
+ get { return _relationships; }
+ }
+
+ ///
+ /// A list of EntityGraph objects that hold metadata about the child entities that will be loaded.
+ ///
+ public List Children
+ {
+ get { return _children; }
+ }
+
+ ///
+ /// Adds an entity to the appropriate place in the object graph.
+ ///
+ ///
+ public void AddEntity(object entityInstance)
+ {
+ _entity = entityInstance;
+
+ // Add newly created entityInstance to list (Many) or set it to field (One)
+ if (this.IsRoot)
+ {
+ RootList.Add(entityInstance);
+ }
+ else if (_relationship.RelationshipInfo.RelationType == RelationshipTypes.Many)
+ {
+ var list = _parent._entityReferences[_parent.GroupingKeyColumns.GroupingKey]
+ .ChildLists[_relationship.Member.Name];
+
+ list.Add(entityInstance);
+ }
+ else // RelationTypes.One
+ {
+ _repos.ReflectionStrategy.SetFieldValue(_parent._entity, _relationship.Member.Name, entityInstance);
+ }
+
+ EntityReference entityRef = new EntityReference(entityInstance);
+ _entityReferences.Add(GroupingKeyColumns.GroupingKey, entityRef);
+
+ InitOneToManyChildLists(entityRef);
+ }
+
+ ///
+ /// Initializes the owning lists on many-to-many Children.
+ ///
+ ///
+ private void InitOneToManyChildLists(EntityReference entityRef)
+ {
+ // Get a reference to the parent's the childrens' OwningLists to the parent entity
+ for (int i = 0; i < Relationships.Count; i++)
+ {
+ Relationship relationship = Relationships[i];
+ if (relationship.RelationshipInfo.RelationType == RelationshipTypes.Many)
+ {
+ try
+ {
+ IList list = (IList)_repos.ReflectionStrategy.CreateInstance(relationship.MemberType);
+ _repos.ReflectionStrategy.SetFieldValue(entityRef.Entity, relationship.Member.Name, list);
+
+ // Save a reference to each 1-M list
+ entityRef.AddChildList(relationship.Member.Name, list);
+ }
+ catch (Exception ex)
+ {
+ throw new DataMappingException(
+ string.Format("{0}.{1} is a \"Many\" relationship type so it must derive from IList.",
+ entityRef.Entity.GetType().Name, relationship.Member.Name),
+ ex);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Recursively adds primary key columns from contiguous child graphs with a one-to-one relationship type to the pKeys collection..
+ ///
+ ///
+ ///
+ private void AddOneToOneChildKeys(ColumnMapCollection pKeys, EntityGraph entity)
+ {
+ var oneToOneChildren = entity.Children
+ .Where(c => c._relationship.RelationshipInfo.RelationType == RelationshipTypes.One);
+
+ foreach (var child in oneToOneChildren)
+ {
+ pKeys.AddRange(child.Columns.PrimaryKeys);
+ AddOneToOneChildKeys(pKeys, child);
+ }
+ }
+
+ ///
+ /// Concatenates the values of the GroupingKeys property and compares them
+ /// against the LastKeyGroup property. Returns true if the values are different,
+ /// or false if the values are the same.
+ /// The currently concatenated keys are saved in the LastKeyGroup property.
+ ///
+ ///
+ ///
+ public bool IsNewGroup(DbDataReader reader)
+ {
+ bool isNewGroup = false;
+
+ // Get primary keys from parent entity and any one-to-one child entites
+ GroupingKeyCollection groupingKeyColumns = this.GroupingKeyColumns;
+
+ // Concatenate column values
+ KeyGroupInfo keyGroupInfo = groupingKeyColumns.CreateGroupingKey(reader);
+
+ if (!keyGroupInfo.HasNullKey && !_entityReferences.ContainsKey(keyGroupInfo.GroupingKey))
+ {
+ isNewGroup = true;
+ }
+
+ return isNewGroup;
+ }
+
+ ///
+ /// Gets the GroupingKeys for this entity.
+ /// GroupingKeys determine when to create and add a new entity to the graph.
+ ///
+ ///
+ /// A simple entity with no relationships will return only its PrimaryKey columns.
+ /// A parent entity with one-to-one child relationships will include its own PrimaryKeys,
+ /// and it will recursively traverse all Children with one-to-one relationships and add their PrimaryKeys.
+ /// A child entity that has a one-to-one relationship with its parent will use the same
+ /// GroupingKeys already defined by its parent.
+ ///
+ public GroupingKeyCollection GroupingKeyColumns
+ {
+ get
+ {
+ if (_groupingKeyColumns == null)
+ _groupingKeyColumns = GetGroupingKeyColumns();
+
+ return _groupingKeyColumns;
+ }
+ }
+
+ ///
+ /// Gets a list of keys to group by.
+ ///
+ ///
+ /// When converting an unnormalized set of data from a database view,
+ /// a new entity is only created when the grouping keys have changed.
+ /// NOTE: This behavior works on the assumption that the view result set
+ /// has been sorted by the root entity primary key(s), followed by the
+ /// child entity primary keys.
+ ///
+ ///
+ private GroupingKeyCollection GetGroupingKeyColumns()
+ {
+ // Get primary keys for this parent entity
+ GroupingKeyCollection groupingKeyColumns = new GroupingKeyCollection();
+ groupingKeyColumns.PrimaryKeys.AddRange(Columns.PrimaryKeys);
+
+ // The following conditions should fail with an exception:
+ // 1) Any parent entity (entity with children) must have at least one PK specified or an exception will be thrown
+ // 2) All 1-M relationship entities must have at least one PK specified
+ // * Only 1-1 entities with no children are allowed to have 0 PKs specified.
+ if ((groupingKeyColumns.PrimaryKeys.Count == 0 && _children.Count > 0) ||
+ (groupingKeyColumns.PrimaryKeys.Count == 0 && !IsRoot && _relationship.RelationshipInfo.RelationType == RelationshipTypes.Many))
+ throw new MissingPrimaryKeyException(string.Format("There are no primary key mappings defined for the following entity: '{0}'.", this.EntityType.Name));
+
+ // Add parent's keys
+ if (IsChild)
+ groupingKeyColumns.ParentPrimaryKeys.AddRange(Parent.GroupingKeyColumns);
+
+ return groupingKeyColumns;
+ }
+
+ #region IEnumerable Members
+
+ public IEnumerator GetEnumerator()
+ {
+ return TraverseGraph(this);
+ }
+
+ ///
+ /// Recursively traverses through every entity in the EntityGraph.
+ ///
+ ///
+ ///
+ private static IEnumerator TraverseGraph(EntityGraph entityGraph)
+ {
+ Stack stack = new Stack();
+ stack.Push(entityGraph);
+
+ while (stack.Count > 0)
+ {
+ EntityGraph node = stack.Pop();
+ yield return node;
+
+ foreach (EntityGraph childGraph in node.Children)
+ {
+ stack.Push(childGraph);
+ }
+ }
+ }
+
+
+ #endregion
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ #endregion
+ }
+}
+
+public struct KeyGroupInfo
+{
+ private string _groupingKey;
+ private bool _hasNullKey;
+
+ public KeyGroupInfo(string groupingKey, bool hasNullKey)
+ {
+ _groupingKey = groupingKey;
+ _hasNullKey = hasNullKey;
+ }
+
+ public string GroupingKey
+ {
+ get { return _groupingKey; }
+ }
+
+ public bool HasNullKey
+ {
+ get { return _hasNullKey; }
+ }
+}
\ No newline at end of file
diff --git a/Marr.Data/EntityMerger.cs b/Marr.Data/EntityMerger.cs
new file mode 100644
index 000000000..717b2e46c
--- /dev/null
+++ b/Marr.Data/EntityMerger.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Linq.Expressions;
+
+namespace Marr.Data
+{
+ ///
+ /// This utility class allows you to join two existing entity collections.
+ ///
+ public class EntityMerger
+ {
+ ///
+ /// Joines to existing entity collections.
+ ///
+ /// The parent entity type.
+ /// The child entity type.
+ /// The parent entities.
+ /// The child entities
+ /// A predicate that defines the relationship between the parent and child entities. Returns true if they are related.
+ /// An action that adds a related child to the parent.
+ public static void Merge(IEnumerable parentList, IEnumerable childList, Func relationship, Action mergeAction)
+ {
+ foreach (TParent parent in parentList)
+ {
+ foreach (TChild child in childList)
+ {
+ if (relationship(parent, child))
+ {
+ mergeAction(parent, child);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Marr.Data/EntityReference.cs b/Marr.Data/EntityReference.cs
new file mode 100644
index 000000000..84a68b98b
--- /dev/null
+++ b/Marr.Data/EntityReference.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Collections;
+
+namespace Marr.Data
+{
+ ///
+ /// Stores an entity along with all of its 1-M IList references.
+ ///
+ public class EntityReference
+ {
+ public EntityReference(object entity)
+ {
+ Entity = entity;
+ ChildLists = new Dictionary();
+ }
+
+ public object Entity { get; private set; }
+ public Dictionary ChildLists { get; private set; }
+
+ public void AddChildList(string memberName, IList list)
+ {
+ if (ChildLists.ContainsKey(memberName))
+ ChildLists[memberName] = list;
+ else
+ ChildLists.Add(memberName, list);
+ }
+ }
+}
diff --git a/Marr.Data/ExtensionMethods.cs b/Marr.Data/ExtensionMethods.cs
new file mode 100644
index 000000000..b33093c33
--- /dev/null
+++ b/Marr.Data/ExtensionMethods.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Data.Common;
+
+namespace Marr.Data
+{
+ ///
+ /// This class contains public extension methods.
+ ///
+ public static class ExtensionMethods
+ {
+ ///
+ /// Gets a value from a DbDataReader by using the column name;
+ ///
+ public static T GetValue(this DbDataReader reader, string columnName)
+ {
+ int ordinal = reader.GetOrdinal(columnName);
+ return (T)reader.GetValue(ordinal);
+ }
+ }
+}
diff --git a/Marr.Data/GroupingKeyCollection.cs b/Marr.Data/GroupingKeyCollection.cs
new file mode 100644
index 000000000..8f81a7482
--- /dev/null
+++ b/Marr.Data/GroupingKeyCollection.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping;
+using System.Data.Common;
+
+namespace Marr.Data
+{
+ public class GroupingKeyCollection : IEnumerable
+ {
+ public GroupingKeyCollection()
+ {
+ PrimaryKeys = new ColumnMapCollection();
+ ParentPrimaryKeys = new ColumnMapCollection();
+ }
+
+ public ColumnMapCollection PrimaryKeys { get; private set; }
+ public ColumnMapCollection ParentPrimaryKeys { get; private set; }
+
+ public int Count
+ {
+ get
+ {
+ return PrimaryKeys.Count + ParentPrimaryKeys.Count;
+ }
+ }
+
+ ///
+ /// Gets the PK values that define this entity in the graph.
+ ///
+ internal string GroupingKey { get; private set; }
+
+ ///
+ /// Returns a concatented string containing the primary key values of the current record.
+ ///
+ /// The open data reader.
+ /// Returns the primary key value(s) as a string.
+ internal KeyGroupInfo CreateGroupingKey(DbDataReader reader)
+ {
+ StringBuilder pkValues = new StringBuilder();
+ bool hasNullValue = false;
+
+ foreach (ColumnMap pkColumn in this)
+ {
+ object pkValue = reader[pkColumn.ColumnInfo.GetColumName(true)];
+
+ if (pkValue == DBNull.Value)
+ hasNullValue = true;
+
+ pkValues.Append(pkValue.ToString());
+ }
+
+ GroupingKey = pkValues.ToString();
+
+ return new KeyGroupInfo(GroupingKey, hasNullValue);
+ }
+
+ #region IEnumerable Members
+
+ public IEnumerator GetEnumerator()
+ {
+ foreach (ColumnMap map in ParentPrimaryKeys)
+ {
+ yield return map;
+ }
+
+ foreach (ColumnMap map in PrimaryKeys)
+ {
+ yield return map;
+ }
+ }
+
+ #endregion
+
+ #region IEnumerable Members
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/Marr.Data/IDataMapper.cs b/Marr.Data/IDataMapper.cs
new file mode 100644
index 000000000..4f0f9014a
--- /dev/null
+++ b/Marr.Data/IDataMapper.cs
@@ -0,0 +1,220 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Data;
+using System.Data.Common;
+using System.Collections.Generic;
+using Marr.Data.Parameters;
+using System.Linq.Expressions;
+using Marr.Data.QGen;
+using System.Reflection;
+
+namespace Marr.Data
+{
+ public interface IDataMapper : IDisposable
+ {
+ #region - Contructor, Members -
+
+ string ConnectionString { get; }
+ DbProviderFactory ProviderFactory { get; }
+ DbCommand Command { get; }
+
+ ///
+ /// Gets or sets a value that determines whether the DataMapper will
+ /// use a stored procedure or a sql text command to access
+ /// the database. The default is stored procedure.
+ ///
+ SqlModes SqlMode { get; set; }
+
+ #endregion
+
+ #region - Update -
+
+ UpdateQueryBuilder Update();
+ int Update(T entity, Expression> filter);
+ int Update(string tableName, T entity, Expression> filter);
+ int Update(T entity, string sql);
+
+ #endregion
+
+ #region - Insert -
+
+ ///
+ /// Creates an InsertQueryBuilder that allows you to build an insert statement.
+ /// This method gives you the flexibility to manually configure all options of your insert statement.
+ /// Note: You must manually call the Execute() chaining method to run the query.
+ ///
+ InsertQueryBuilder Insert();
+
+ ///
+ /// Generates and executes an insert query for the given entity.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ object Insert(T entity);
+
+ ///
+ /// Generates and executes an insert query for the given entity.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ object Insert(string tableName, T entity);
+
+ ///
+ /// Executes an insert query for the given entity using the given sql insert statement.
+ /// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
+ /// and if an identity query has been implemented for your current database dialect.
+ ///
+ object Insert(T entity, string sql);
+
+ #endregion
+
+ #region - Delete -
+
+ int Delete(Expression> filter);
+ int Delete(string tableName, Expression> filter);
+
+ #endregion
+
+ #region - Connections / Transactions -
+
+ void BeginTransaction();
+ void RollBack();
+ void Commit();
+ event EventHandler OpeningConnection;
+
+ #endregion
+
+ #region - ExecuteScalar, ExecuteNonQuery, ExecuteReader -
+
+ ///
+ /// Executes a non query that returns an integer.
+ ///
+ /// The SQL command to execute.
+ /// An integer value
+ int ExecuteNonQuery(string sql);
+
+ ///
+ /// Executes a stored procedure that returns a scalar value.
+ ///
+ /// The SQL command to execute.
+ /// A scalar value
+ object ExecuteScalar(string sql);
+
+ ///
+ /// Executes a DataReader that can be controlled using a Func delegate.
+ /// (Note that reader.Read() will be called automatically).
+ ///
+ /// The type that will be return in the result set.
+ /// The sql statement that will be executed.
+ /// The function that will build the the TResult set.
+ /// An IEnumerable of TResult.
+ IEnumerable ExecuteReader(string sql, Func func);
+
+ ///
+ /// Executes a DataReader that can be controlled using an Action delegate.
+ ///
+ /// The sql statement that will be executed.
+ /// The delegate that will work with the result set.
+ void ExecuteReader(string sql, Action action);
+
+ #endregion
+
+ #region - DataSets -
+
+ DataSet GetDataSet(string sql);
+ DataSet GetDataSet(string sql, DataSet ds, string tableName);
+ DataTable GetDataTable(string sql, DataTable dt, string tableName);
+ DataTable GetDataTable(string sql);
+ int InsertDataTable(DataTable table, string insertSP);
+ int InsertDataTable(DataTable table, string insertSP, UpdateRowSource updateRowSource);
+ int UpdateDataSet(DataSet ds, string updateSP);
+ int DeleteDataTable(DataTable dt, string deleteSP);
+
+ #endregion
+
+ #region - Parameters -
+
+ DbParameterCollection Parameters { get; }
+ ParameterChainMethods AddParameter(string name, object value);
+ IDbDataParameter AddParameter(IDbDataParameter parameter);
+
+ #endregion
+
+ #region - Find -
+
+ ///
+ /// Returns an entity of type T.
+ ///
+ /// The type of entity that is to be instantiated and loaded with values.
+ /// The SQL command to execute.
+ /// An instantiated and loaded entity of type T.
+ T Find(string sql);
+
+ ///
+ /// Returns an entity of type T.
+ ///
+ /// The type of entity that is to be instantiated and loaded with values.
+ /// The SQL command to execute.
+ /// A previously instantiated entity that will be loaded with values.
+ /// An instantiated and loaded entity of type T.
+ T Find(string sql, T ent);
+
+ #endregion
+
+ #region - Query -
+
+ ///
+ /// Creates a QueryBuilder that allows you to build a query.
+ ///
+ /// The type of object that will be queried.
+ /// Returns a QueryBuilder of T.
+ QueryBuilder Query();
+
+ ///
+ /// Returns the results of a query.
+ /// Uses a List of type T to return the data.
+ ///
+ /// The type of object that will be queried.
+ /// Returns a list of the specified type.
+ List Query(string sql);
+
+ ///
+ /// Returns the results of a query or a stored procedure.
+ ///
+ /// The type of object that will be queried.
+ /// The sql query or stored procedure name to run.
+ /// A previously instantiated list to populate.
+ /// Returns a list of the specified type.
+ ICollection Query(string sql, ICollection entityList);
+
+ #endregion
+
+ #region - Query to Graph -
+
+ ///
+ /// Runs a query and then tries to instantiate the entire object graph with entites.
+ ///
+ List QueryToGraph(string sql);
+
+ ///
+ /// Runs a query and then tries to instantiate the entire object graph with entites.
+ ///
+ ICollection QueryToGraph(string sql, ICollection entityList);
+
+ #endregion
+ }
+}
diff --git a/Marr.Data/LazyLoaded.cs b/Marr.Data/LazyLoaded.cs
new file mode 100644
index 000000000..d492b18ba
--- /dev/null
+++ b/Marr.Data/LazyLoaded.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Data.Common;
+
+namespace Marr.Data
+{
+ public interface ILazyLoaded : ICloneable
+ {
+ void Prepare(Func dbCreator, object parent);
+ void LazyLoad();
+ }
+
+ ///
+ /// Allows a field to be lazy loaded.
+ ///
+ ///
+ public class LazyLoaded : ILazyLoaded
+ {
+ protected TChild _child;
+ protected bool _isLoaded;
+
+ public LazyLoaded()
+ {
+ }
+
+ public LazyLoaded(TChild val)
+ {
+ _child = val;
+ _isLoaded = true;
+ }
+
+ public TChild Value
+ {
+ get
+ {
+ LazyLoad();
+ return _child;
+ }
+ }
+
+ public virtual void Prepare(Func dbCreator, object parent)
+ { }
+
+ public virtual void LazyLoad()
+ { }
+
+ public static implicit operator LazyLoaded(TChild val)
+ {
+ return new LazyLoaded(val);
+ }
+
+ public static implicit operator TChild(LazyLoaded lazy)
+ {
+ return lazy.Value;
+ }
+
+ public object Clone()
+ {
+ return this.MemberwiseClone();
+ }
+ }
+
+ ///
+ /// This is the lazy loading proxy.
+ ///
+ /// The parent entity that contains the lazy loaded entity.
+ /// The child entity that is being lazy loaded.
+ internal class LazyLoaded : LazyLoaded
+ {
+ private TParent _parent;
+ private Func _query;
+
+ private Func _dbCreator;
+
+ internal LazyLoaded(Func query)
+ : base()
+ {
+ _query = query;
+ }
+
+ public LazyLoaded(TChild val)
+ : base(val)
+ {
+ _child = val;
+ _isLoaded = true;
+ }
+
+ ///
+ /// The second part of the initialization happens when the entity is being built.
+ ///
+ /// Knows how to instantiate a new IDataMapper.
+ /// The parent entity.
+ public override void Prepare(Func dbCreator, object parent)
+ {
+ _dbCreator = dbCreator;
+ _parent = (TParent)parent;
+ }
+
+ public bool IsLoaded
+ {
+ get { return _isLoaded; }
+ }
+
+ public override void LazyLoad()
+ {
+ if (!_isLoaded)
+ {
+ using (IDataMapper db = _dbCreator())
+ {
+ _child = _query(db, _parent);
+ _isLoaded = true;
+ }
+ }
+ }
+
+ public static implicit operator LazyLoaded(TChild val)
+ {
+ return new LazyLoaded(val);
+ }
+
+ public static implicit operator TChild(LazyLoaded lazy)
+ {
+ return lazy.Value;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Marr.Data/MapRepository.cs b/Marr.Data/MapRepository.cs
new file mode 100644
index 000000000..25489d15f
--- /dev/null
+++ b/Marr.Data/MapRepository.cs
@@ -0,0 +1,265 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Reflection;
+using System.Data.Common;
+using Marr.Data.Converters;
+using Marr.Data.Parameters;
+using Marr.Data.Mapping;
+using Marr.Data.Mapping.Strategies;
+using Marr.Data.Reflection;
+
+namespace Marr.Data
+{
+ public class MapRepository
+ {
+ private static readonly object _tablesLock = new object();
+ private static readonly object _columnsLock = new object();
+ private static readonly object _relationshipsLock = new object();
+
+ private IDbTypeBuilder _dbTypeBuilder;
+ private Dictionary _columnMapStrategies;
+
+ internal Dictionary Tables { get; set; }
+ internal Dictionary Columns { get; set; }
+ internal Dictionary Relationships { get; set; }
+ internal Dictionary TypeConverters { get; set; }
+
+ // Explicit static constructor to tell C# compiler
+ // not to mark type as beforefieldinit
+ static MapRepository()
+ { }
+
+ private MapRepository()
+ {
+ Tables = new Dictionary();
+ Columns = new Dictionary();
+ Relationships = new Dictionary();
+ TypeConverters = new Dictionary();
+
+ // Register a default IReflectionStrategy
+ ReflectionStrategy = new CachedReflectionStrategy();
+
+ // Register a default type converter for Enums
+ TypeConverters.Add(typeof(Enum), new Converters.EnumStringConverter());
+
+ // Register a default IDbTypeBuilder
+ _dbTypeBuilder = new Parameters.DbTypeBuilder();
+
+ _columnMapStrategies = new Dictionary();
+ RegisterDefaultMapStrategy(new AttributeMapStrategy());
+
+ EnableTraceLogging = false;
+ }
+
+ private readonly static MapRepository _instance = new MapRepository();
+
+ ///
+ /// Gets a reference to the singleton MapRepository.
+ ///
+ public static MapRepository Instance
+ {
+ get
+ {
+ return _instance;
+ }
+ }
+
+ ///
+ /// Gets or sets a boolean that determines whether debug information should be written to the trace log.
+ /// The default is false.
+ ///
+ public bool EnableTraceLogging { get; set; }
+
+ #region - Column Map Strategies -
+
+ public void RegisterDefaultMapStrategy(IMapStrategy strategy)
+ {
+ RegisterMapStrategy(typeof(object), strategy);
+ }
+
+ public void RegisterMapStrategy(Type entityType, IMapStrategy strategy)
+ {
+ if (_columnMapStrategies.ContainsKey(entityType))
+ _columnMapStrategies[entityType] = strategy;
+ else
+ _columnMapStrategies.Add(entityType, strategy);
+ }
+
+ private IMapStrategy GetMapStrategy(Type entityType)
+ {
+ if (_columnMapStrategies.ContainsKey(entityType))
+ {
+ // Return entity specific column map strategy
+ return _columnMapStrategies[entityType];
+ }
+ else
+ {
+ // Return the default column map strategy
+ return _columnMapStrategies[typeof(object)];
+ }
+ }
+
+ #endregion
+
+ #region - Table repository -
+
+ internal string GetTableName(Type entityType)
+ {
+ if (!Tables.ContainsKey(entityType))
+ {
+ lock (_tablesLock)
+ {
+ if (!Tables.ContainsKey(entityType))
+ {
+ string tableName = GetMapStrategy(entityType).MapTable(entityType);
+ Tables.Add(entityType, tableName);
+ return tableName;
+ }
+ }
+ }
+
+ return Tables[entityType];
+ }
+
+ #endregion
+
+ #region - Columns repository -
+
+ public ColumnMapCollection GetColumns(Type entityType)
+ {
+ if (!Columns.ContainsKey(entityType))
+ {
+ lock (_columnsLock)
+ {
+ if (!Columns.ContainsKey(entityType))
+ {
+ ColumnMapCollection columnMaps = GetMapStrategy(entityType).MapColumns(entityType);
+ Columns.Add(entityType, columnMaps);
+ return columnMaps;
+ }
+ }
+ }
+
+ return Columns[entityType];
+ }
+
+ #endregion
+
+ #region - Relationships repository -
+
+ public RelationshipCollection GetRelationships(Type type)
+ {
+ if (!Relationships.ContainsKey(type))
+ {
+ lock (_relationshipsLock)
+ {
+ if (!Relationships.ContainsKey(type))
+ {
+ RelationshipCollection relationships = GetMapStrategy(type).MapRelationships(type);
+ Relationships.Add(type, relationships);
+ return relationships;
+ }
+ }
+ }
+
+ return Relationships[type];
+ }
+
+ #endregion
+
+ #region - Reflection Strategy -
+
+ ///
+ /// Gets or sets the reflection strategy that the DataMapper will use to load entities.
+ /// By default the CachedReflector will be used, which provides a performance increase over the SimpleReflector.
+ /// However, the SimpleReflector can be used in Medium Trust enviroments.
+ ///
+ public IReflectionStrategy ReflectionStrategy { get; set; }
+
+ #endregion
+
+ #region - Type Converters -
+
+ ///
+ /// Registers a converter for a given type.
+ ///
+ /// The CLR data type that will be converted.
+ /// An IConverter object that will handle the data conversion.
+ public void RegisterTypeConverter(Type type, IConverter converter)
+ {
+ if (TypeConverters.ContainsKey(type))
+ {
+ TypeConverters[type] = converter;
+ }
+ else
+ {
+ TypeConverters.Add(type, converter);
+ }
+ }
+
+ ///
+ /// Checks for a type converter (if one exists).
+ /// 1) Checks for a converter registered for the current columns data type.
+ /// 2) Checks to see if a converter is registered for all enums (type of Enum) if the current column is an enum.
+ /// 3) Checks to see if a converter is registered for all objects (type of Object).
+ ///
+ /// The current data map.
+ /// Returns an IConverter object or null if one does not exist.
+ internal IConverter GetConverter(Type dataType)
+ {
+ if (TypeConverters.ContainsKey(dataType))
+ {
+ // User registered type converter
+ return TypeConverters[dataType];
+ }
+ else if (TypeConverters.ContainsKey(typeof(Enum)) && dataType.IsEnum)
+ {
+ // A converter is registered to handled enums
+ return TypeConverters[typeof(Enum)];
+ }
+ else if (TypeConverters.ContainsKey(typeof(object)))
+ {
+ // User registered default converter
+ return TypeConverters[typeof(object)];
+ }
+ else
+ {
+ // No conversion
+ return null;
+ }
+ }
+
+ #endregion
+
+ #region - DbTypeBuilder -
+
+ ///
+ /// Gets or sets the IDBTypeBuilder that is responsible for converting parameter DbTypes based on the parameter value.
+ /// Defaults to use the DbTypeBuilder.
+ /// You can replace this with a more specific builder if you want more control over the way the parameter types are set.
+ ///
+ public IDbTypeBuilder DbTypeBuilder
+ {
+ get { return _dbTypeBuilder; }
+ set { _dbTypeBuilder = value; }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Marr.Data/Mapping/ColumnAttribute.cs b/Marr.Data/Mapping/ColumnAttribute.cs
new file mode 100644
index 000000000..9d63e5b7c
--- /dev/null
+++ b/Marr.Data/Mapping/ColumnAttribute.cs
@@ -0,0 +1,122 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Data.OleDb;
+using System.Data.Common;
+
+namespace Marr.Data.Mapping
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class ColumnAttribute : Attribute, IColumnInfo
+ {
+ private string _name;
+ private string _altName;
+ private int _size = 0;
+ private bool _isPrimaryKey;
+ private bool _isAutoIncrement;
+ private bool _returnValue;
+ private ParameterDirection _paramDirection = ParameterDirection.Input;
+
+ public ColumnAttribute()
+ {
+ }
+
+ public ColumnAttribute(string name)
+ {
+ _name = name;
+ }
+
+ ///
+ /// Gets or sets the column name.
+ ///
+ public string Name
+ {
+ get { return _name; }
+ set { _name = value; }
+ }
+
+ ///
+ /// Gets or sets an alternate name that is used to define this column in views.
+ /// If an AltName is present, it is used in the QueryViewToObjectGraph method.
+ /// If an AltName is not present, it will return the Name property value.
+ ///
+ public string AltName
+ {
+ get { return _altName; }
+ set { _altName = value; }
+ }
+
+ ///
+ /// Gets or sets the column size.
+ ///
+ public int Size
+ {
+ get { return _size; }
+ set { _size = value; }
+ }
+
+ ///
+ /// Gets or sets a value that determines whether the column is the Primary Key.
+ ///
+ public bool IsPrimaryKey
+ {
+ get { return _isPrimaryKey; }
+ set { _isPrimaryKey = value; }
+ }
+
+ ///
+ /// Gets or sets a value that determines whether the column is an auto-incrementing seed column.
+ ///
+ public bool IsAutoIncrement
+ {
+ get { return _isAutoIncrement; }
+ set { _isAutoIncrement = value; }
+ }
+
+ ///
+ /// Gets or sets a value that determines whether the column has a return value.
+ ///
+ public bool ReturnValue
+ {
+ get { return _returnValue; }
+ set { _returnValue = value; }
+ }
+
+ ///
+ /// Gets or sets the ParameterDirection.
+ ///
+ public ParameterDirection ParamDirection
+ {
+ get { return _paramDirection; }
+ set { _paramDirection = value; }
+ }
+
+ public string TryGetAltName()
+ {
+ if (!string.IsNullOrEmpty(AltName) && AltName != Name)
+ {
+ return AltName;
+ }
+ else
+ {
+ return Name;
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Mapping/ColumnInfo.cs b/Marr.Data/Mapping/ColumnInfo.cs
new file mode 100644
index 000000000..de1a66da4
--- /dev/null
+++ b/Marr.Data/Mapping/ColumnInfo.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Marr.Data.Mapping
+{
+ public class ColumnInfo : IColumnInfo
+ {
+ public ColumnInfo()
+ {
+ IsPrimaryKey = false;
+ IsAutoIncrement = false;
+ ReturnValue = false;
+ ParamDirection = System.Data.ParameterDirection.Input;
+ }
+
+ public string Name { get; set; }
+ public string AltName { get; set; }
+ public int Size { get; set; }
+ public bool IsPrimaryKey { get; set; }
+ public bool IsAutoIncrement { get; set; }
+ public bool ReturnValue { get; set; }
+ public System.Data.ParameterDirection ParamDirection { get; set; }
+
+ public string TryGetAltName()
+ {
+ if (!string.IsNullOrEmpty(AltName) && AltName != Name)
+ {
+ return AltName;
+ }
+ else
+ {
+ return Name;
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Mapping/ColumnMap.cs b/Marr.Data/Mapping/ColumnMap.cs
new file mode 100644
index 000000000..3ae479b4c
--- /dev/null
+++ b/Marr.Data/Mapping/ColumnMap.cs
@@ -0,0 +1,69 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Reflection;
+using Marr.Data.Converters;
+
+namespace Marr.Data.Mapping
+{
+ ///
+ /// Contains information about the class fields and their associated stored proc parameters
+ ///
+ public class ColumnMap
+ {
+ ///
+ /// Creates a column map with an empty ColumnInfo object.
+ ///
+ /// The .net member that is being mapped.
+ public ColumnMap(MemberInfo member)
+ : this(member, new ColumnInfo())
+ { }
+
+ public ColumnMap(MemberInfo member, IColumnInfo columnInfo)
+ {
+ FieldName = member.Name;
+
+ // If the column name is not specified, the field name will be used.
+ if (string.IsNullOrEmpty(columnInfo.Name))
+ columnInfo.Name = member.Name;
+
+ FieldType = ReflectionHelper.GetMemberType(member);
+
+ Type paramNetType = FieldType;
+ MapRepository repository = MapRepository.Instance;
+
+ IConverter converter = repository.GetConverter(FieldType);
+ if (converter != null)
+ {
+ // Handle conversions
+ paramNetType = converter.DbType;
+ }
+
+ // Get database specific DbType and store with column map in cache
+ DBType = repository.DbTypeBuilder.GetDbType(paramNetType);
+
+ ColumnInfo = columnInfo;
+ }
+
+ public string FieldName { get; set; }
+ public Type FieldType { get; set; }
+ public Enum DBType { get; set; }
+ public IColumnInfo ColumnInfo { get; set; }
+ }
+}
diff --git a/Marr.Data/Mapping/ColumnMapBuilder.cs b/Marr.Data/Mapping/ColumnMapBuilder.cs
new file mode 100644
index 000000000..ad8b470ee
--- /dev/null
+++ b/Marr.Data/Mapping/ColumnMapBuilder.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Linq.Expressions;
+using System.Data;
+using Marr.Data.Mapping.Strategies;
+
+namespace Marr.Data.Mapping
+{
+ ///
+ /// This class has fluent methods that are used to easily configure column mappings.
+ ///
+ ///
+ public class ColumnMapBuilder
+ {
+ private FluentMappings.MappingsFluentEntity _fluentEntity;
+ private string _currentPropertyName;
+
+ public ColumnMapBuilder(FluentMappings.MappingsFluentEntity fluentEntity, ColumnMapCollection mappedColumns)
+ {
+ _fluentEntity = fluentEntity;
+ MappedColumns = mappedColumns;
+ }
+
+ ///
+ /// Gets the list of column mappings that are being configured.
+ ///
+ public ColumnMapCollection MappedColumns { get; private set; }
+
+ #region - Fluent Methods -
+
+ ///
+ /// Initializes the configurator to configure the given property.
+ ///
+ ///
+ ///
+ public ColumnMapBuilder For(Expression> property)
+ {
+ For(property.GetMemberName());
+ return this;
+ }
+
+ ///
+ /// Initializes the configurator to configure the given property or field.
+ ///
+ ///
+ ///
+ public ColumnMapBuilder For(string propertyName)
+ {
+ _currentPropertyName = propertyName;
+
+ // Try to add the column map if it doesn't exist
+ if (MappedColumns.GetByFieldName(_currentPropertyName) == null)
+ {
+ TryAddColumnMapForField(_currentPropertyName);
+ }
+
+ return this;
+ }
+
+ public ColumnMapBuilder SetPrimaryKey()
+ {
+ AssertCurrentPropertyIsSet();
+ return SetPrimaryKey(_currentPropertyName);
+ }
+
+ public ColumnMapBuilder SetPrimaryKey(string propertyName)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsPrimaryKey = true;
+ return this;
+ }
+
+ public ColumnMapBuilder SetAutoIncrement()
+ {
+ AssertCurrentPropertyIsSet();
+ return SetAutoIncrement(_currentPropertyName);
+ }
+
+ public ColumnMapBuilder SetAutoIncrement(string propertyName)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsAutoIncrement = true;
+ return this;
+ }
+
+ public ColumnMapBuilder SetColumnName(string columnName)
+ {
+ AssertCurrentPropertyIsSet();
+ return SetColumnName(_currentPropertyName, columnName);
+ }
+
+ public ColumnMapBuilder SetColumnName(string propertyName, string columnName)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.Name = columnName;
+ return this;
+ }
+
+ public ColumnMapBuilder SetReturnValue()
+ {
+ AssertCurrentPropertyIsSet();
+ return SetReturnValue(_currentPropertyName);
+ }
+
+ public ColumnMapBuilder SetReturnValue(string propertyName)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.ReturnValue = true;
+ return this;
+ }
+
+ public ColumnMapBuilder SetSize(int size)
+ {
+ AssertCurrentPropertyIsSet();
+ return SetSize(_currentPropertyName, size);
+ }
+
+ public ColumnMapBuilder SetSize(string propertyName, int size)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.Size = size;
+ return this;
+ }
+
+ public ColumnMapBuilder SetAltName(string altName)
+ {
+ AssertCurrentPropertyIsSet();
+ return SetAltName(_currentPropertyName, altName);
+ }
+
+ public ColumnMapBuilder SetAltName(string propertyName, string altName)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.AltName = altName;
+ return this;
+ }
+
+ public ColumnMapBuilder SetParamDirection(ParameterDirection direction)
+ {
+ AssertCurrentPropertyIsSet();
+ return SetParamDirection(_currentPropertyName, direction);
+ }
+
+ public ColumnMapBuilder SetParamDirection(string propertyName, ParameterDirection direction)
+ {
+ MappedColumns.GetByFieldName(propertyName).ColumnInfo.ParamDirection = direction;
+ return this;
+ }
+
+ public ColumnMapBuilder Ignore(Expression> property)
+ {
+ string propertyName = property.GetMemberName();
+ return Ignore(propertyName);
+ }
+
+ public ColumnMapBuilder Ignore(string propertyName)
+ {
+ var columnMap = MappedColumns.GetByFieldName(propertyName);
+ MappedColumns.Remove(columnMap);
+ return this;
+ }
+
+ public ColumnMapBuilder PrefixAltNames(string prefix)
+ {
+ MappedColumns.PrefixAltNames(prefix);
+ return this;
+ }
+
+ public ColumnMapBuilder SuffixAltNames(string suffix)
+ {
+ MappedColumns.SuffixAltNames(suffix);
+ return this;
+ }
+
+ public FluentMappings.MappingsFluentTables Tables
+ {
+ get
+ {
+ if (_fluentEntity == null)
+ {
+ throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
+ }
+
+ return _fluentEntity.Table;
+ }
+ }
+
+ public FluentMappings.MappingsFluentRelationships Relationships
+ {
+ get
+ {
+ if (_fluentEntity == null)
+ {
+ throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
+ }
+
+ return _fluentEntity.Relationships;
+ }
+ }
+
+ public FluentMappings.MappingsFluentEntity Entity()
+ {
+ return new FluentMappings.MappingsFluentEntity(true);
+ }
+
+ ///
+ /// Tries to add a ColumnMap for the given field name.
+ /// Throws and exception if field cannot be found.
+ ///
+ private void TryAddColumnMapForField(string fieldName)
+ {
+ // Set strategy to filter for public or private fields
+ ConventionMapStrategy strategy = new ConventionMapStrategy(false);
+
+ // Find the field that matches the given field name
+ strategy.ColumnPredicate = mi => mi.Name == fieldName;
+ ColumnMap columnMap = strategy.MapColumns(typeof(TEntity)).FirstOrDefault();
+
+ if (columnMap == null)
+ {
+ throw new DataMappingException(string.Format("Could not find the field '{0}' in '{1}'.",
+ fieldName,
+ typeof(TEntity).Name));
+ }
+ else
+ {
+ MappedColumns.Add(columnMap);
+ }
+ }
+
+ ///
+ /// Throws an exception if the "current" property has not been set.
+ ///
+ private void AssertCurrentPropertyIsSet()
+ {
+ if (string.IsNullOrEmpty(_currentPropertyName))
+ {
+ throw new DataMappingException("A property must first be specified using the 'For' method.");
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Marr.Data/Mapping/ColumnMapCollection.cs b/Marr.Data/Mapping/ColumnMapCollection.cs
new file mode 100644
index 000000000..0a035fbae
--- /dev/null
+++ b/Marr.Data/Mapping/ColumnMapCollection.cs
@@ -0,0 +1,176 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Text.RegularExpressions;
+using System.Data.Common;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Marr.Data.Mapping
+{
+ ///
+ /// This class contains a list of column mappings.
+ /// It also provides various methods to filter the collection.
+ ///
+ public class ColumnMapCollection : List
+ {
+ #region - Filters -
+
+ public ColumnMap GetByColumnName(string columnName)
+ {
+ return this.Find(m => m.ColumnInfo.Name == columnName);
+ }
+
+ public ColumnMap GetByFieldName(string fieldName)
+ {
+ return this.Find(m => m.FieldName == fieldName);
+ }
+
+ ///
+ /// Iterates through all fields marked as return values.
+ ///
+ public IEnumerable ReturnValues
+ {
+ get
+ {
+ foreach (ColumnMap map in this)
+ if (map.ColumnInfo.ReturnValue)
+ yield return map;
+ }
+ }
+
+ ///
+ /// Iterates through all fields that are not return values.
+ ///
+ public ColumnMapCollection NonReturnValues
+ {
+ get
+ {
+ ColumnMapCollection collection = new ColumnMapCollection();
+
+ foreach (ColumnMap map in this)
+ if (!map.ColumnInfo.ReturnValue)
+ collection.Add(map);
+
+ return collection;
+ }
+ }
+
+ ///
+ /// Iterates through all fields marked as Output parameters or InputOutput.
+ ///
+ public IEnumerable OutputFields
+ {
+ get
+ {
+ foreach (ColumnMap map in this)
+ if (map.ColumnInfo.ParamDirection == ParameterDirection.InputOutput ||
+ map.ColumnInfo.ParamDirection == ParameterDirection.Output)
+ yield return map;
+ }
+ }
+
+ ///
+ /// Iterates through all fields marked as primary keys.
+ ///
+ public ColumnMapCollection PrimaryKeys
+ {
+ get
+ {
+ ColumnMapCollection keys = new ColumnMapCollection();
+ foreach (ColumnMap map in this)
+ if (map.ColumnInfo.IsPrimaryKey)
+ keys.Add(map);
+
+ return keys;
+ }
+ }
+
+ ///
+ /// Parses and orders the parameters from the query text.
+ /// Filters the list of mapped columns to match the parameters found in the sql query.
+ /// All parameters starting with the '@' or ':' symbol are matched and returned.
+ ///
+ /// The command and parameters that are being parsed.
+ /// A list of mapped columns that are present in the sql statement as parameters.
+ public ColumnMapCollection OrderParameters(DbCommand command)
+ {
+ if (command.CommandType == CommandType.Text && this.Count > 0)
+ {
+ string commandTypeString = command.GetType().ToString();
+ if (commandTypeString.Contains("Oracle") || commandTypeString.Contains("OleDb"))
+ {
+ ColumnMapCollection columns = new ColumnMapCollection();
+
+ // Find all @Parameters contained in the sql statement
+ string paramPrefix = commandTypeString.Contains("Oracle") ? ":" : "@";
+ string regexString = string.Format(@"{0}[\w-]+", paramPrefix);
+ Regex regex = new Regex(regexString);
+ foreach (Match m in regex.Matches(command.CommandText))
+ {
+ ColumnMap matchingColumn = this.Find(c => string.Concat(paramPrefix, c.ColumnInfo.Name.ToLower()) == m.Value.ToLower());
+ if (matchingColumn != null)
+ columns.Add(matchingColumn);
+ }
+
+ return columns;
+ }
+ }
+
+ return this;
+ }
+
+
+ #endregion
+
+ #region - Actions -
+
+ ///
+ /// Set's each column's altname as the given prefix + the column name.
+ /// Ex:
+ /// Original column name: "ID"
+ /// Passed in prefix: "PRODUCT_"
+ /// Generated AltName: "PRODUCT_ID"
+ ///
+ /// The given prefix.
+ ///
+ public ColumnMapCollection PrefixAltNames(string prefix)
+ {
+ this.ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name.Insert(0, prefix));
+ return this;
+ }
+
+ ///
+ /// Set's each column's altname as the column name + the given prefix.
+ /// Ex:
+ /// Original column name: "ID"
+ /// Passed in suffix: "_PRODUCT"
+ /// Generated AltName: "ID_PRODUCT"
+ ///
+ ///
+ ///
+ public ColumnMapCollection SuffixAltNames(string suffix)
+ {
+ this.ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name + suffix);
+ return this;
+ }
+
+ #endregion
+ }
+}
diff --git a/Marr.Data/Mapping/EnumConversionType.cs b/Marr.Data/Mapping/EnumConversionType.cs
new file mode 100644
index 000000000..b865e84e8
--- /dev/null
+++ b/Marr.Data/Mapping/EnumConversionType.cs
@@ -0,0 +1,29 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Marr.Data.Mapping
+{
+ public enum EnumConversionType
+ {
+ NA,
+ Int,
+ String
+ }
+}
diff --git a/Marr.Data/Mapping/FluentMappings.cs b/Marr.Data/Mapping/FluentMappings.cs
new file mode 100644
index 000000000..83002440a
--- /dev/null
+++ b/Marr.Data/Mapping/FluentMappings.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+using Marr.Data.Mapping.Strategies;
+using System.Collections;
+
+namespace Marr.Data.Mapping
+{
+ ///
+ /// Provides a fluent interface for mapping domain entities and properties to database tables and columns.
+ ///
+ public class FluentMappings
+ {
+ private bool _publicOnly;
+
+ public FluentMappings()
+ : this(true)
+ { }
+
+ public FluentMappings(bool publicOnly)
+ {
+ _publicOnly = publicOnly;
+
+ }
+
+ public MappingsFluentEntity Entity()
+ {
+ return new MappingsFluentEntity(_publicOnly);
+ }
+
+ public class MappingsFluentEntity
+ {
+ public MappingsFluentEntity(bool publicOnly)
+ {
+ Columns = new MappingsFluentColumns(this, publicOnly);
+ Table = new MappingsFluentTables(this);
+ Relationships = new MappingsFluentRelationships(this, publicOnly);
+ }
+
+ ///
+ /// Contains methods that map entity properties to database table and view column names;
+ ///
+ public MappingsFluentColumns Columns { get; private set; }
+
+ ///
+ /// Contains methods that map entity classes to database table names.
+ ///
+ public MappingsFluentTables Table { get; private set; }
+
+ ///
+ /// Contains methods that map sub-entities with database table and view column names.
+ ///
+ public MappingsFluentRelationships Relationships { get; private set; }
+ }
+
+ public class MappingsFluentColumns
+ {
+ private bool _publicOnly;
+ private FluentMappings.MappingsFluentEntity _fluentEntity;
+
+ public MappingsFluentColumns(FluentMappings.MappingsFluentEntity fluentEntity, bool publicOnly)
+ {
+ _fluentEntity = fluentEntity;
+ _publicOnly = publicOnly;
+ }
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps all properties except ICollection properties.
+ ///
+ /// The type that is being built.
+ ///
+ public ColumnMapBuilder AutoMapAllProperties()
+ {
+ return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property &&
+ !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps all properties that are simple types (int, string, DateTime, etc).
+ /// ICollection properties are not included.
+ ///
+ /// The type that is being built.
+ ///
+ public ColumnMapBuilder AutoMapSimpleTypeProperties()
+ {
+ return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property &&
+ DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
+ !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates column mappings for the given type if they match the predicate.
+ ///
+ /// The type that is being built.
+ /// Determines whether a mapping should be created based on the member info.
+ ///
+ public ColumnMapBuilder AutoMapPropertiesWhere(Func predicate)
+ {
+ Type entityType = typeof(TEntity);
+ ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
+ strategy.ColumnPredicate = predicate;
+ ColumnMapCollection columns = strategy.MapColumns(entityType);
+ MapRepository.Instance.Columns[entityType] = columns;
+ return new ColumnMapBuilder(_fluentEntity, columns);
+ }
+
+ ///
+ /// Creates a ColumnMapBuilder that starts out with no pre-populated columns.
+ /// All columns must be added manually using the builder.
+ ///
+ ///
+ ///
+ public ColumnMapBuilder MapProperties()
+ {
+ Type entityType = typeof(TEntity);
+ ColumnMapCollection columns = new ColumnMapCollection();
+ MapRepository.Instance.Columns[entityType] = columns;
+ return new ColumnMapBuilder(_fluentEntity, columns);
+ }
+ }
+
+ public class MappingsFluentTables
+ {
+ private FluentMappings.MappingsFluentEntity _fluentEntity;
+
+ public MappingsFluentTables(FluentMappings.MappingsFluentEntity fluentEntity)
+ {
+ _fluentEntity = fluentEntity;
+ }
+
+ ///
+ /// Provides a fluent table mapping interface.
+ ///
+ ///
+ ///
+ public TableBuilder AutoMapTable()
+ {
+ return new TableBuilder(_fluentEntity);
+ }
+
+ ///
+ /// Sets the table name for a given type.
+ ///
+ ///
+ ///
+ public TableBuilder MapTable(string tableName)
+ {
+ return new TableBuilder(_fluentEntity).SetTableName(tableName);
+ }
+ }
+
+ public class MappingsFluentRelationships
+ {
+ private FluentMappings.MappingsFluentEntity _fluentEntity;
+ private bool _publicOnly;
+
+ public MappingsFluentRelationships(FluentMappings.MappingsFluentEntity fluentEntity, bool publicOnly)
+ {
+ _fluentEntity = fluentEntity;
+ _publicOnly = publicOnly;
+ }
+
+ ///
+ /// Creates relationship mappings for the given type.
+ /// Maps all properties that implement ICollection or are not "simple types".
+ ///
+ ///
+ public RelationshipBuilder AutoMapICollectionOrComplexProperties()
+ {
+ return AutoMapPropertiesWhere(m =>
+ m.MemberType == MemberTypes.Property &&
+ (
+ typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) || !DataHelper.IsSimpleType((m as PropertyInfo).PropertyType)
+ )
+ );
+
+ }
+
+ ///
+ /// Creates relationship mappings for the given type.
+ /// Maps all properties that implement ICollection.
+ ///
+ ///
+ public RelationshipBuilder AutoMapICollectionProperties()
+ {
+ return AutoMapPropertiesWhere(m =>
+ m.MemberType == MemberTypes.Property &&
+ typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates relationship mappings for the given type.
+ /// Maps all properties that are not "simple types".
+ ///
+ ///
+ public RelationshipBuilder AutoMapComplexTypeProperties()
+ {
+ return AutoMapPropertiesWhere(m =>
+ m.MemberType == MemberTypes.Property &&
+ !DataHelper.IsSimpleType((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates relationship mappings for the given type if they match the predicate.
+ ///
+ /// Determines whether a mapping should be created based on the member info.
+ ///
+ public RelationshipBuilder AutoMapPropertiesWhere(Func predicate)
+ {
+ Type entityType = typeof(TEntity);
+ ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
+ strategy.RelationshipPredicate = predicate;
+ RelationshipCollection relationships = strategy.MapRelationships(entityType);
+ MapRepository.Instance.Relationships[entityType] = relationships;
+ return new RelationshipBuilder(_fluentEntity, relationships);
+ }
+
+ ///
+ /// Creates a RelationshipBuilder that starts out with no pre-populated relationships.
+ /// All relationships must be added manually using the builder.
+ ///
+ ///
+ public RelationshipBuilder MapProperties()
+ {
+ Type entityType = typeof(T);
+ RelationshipCollection relationships = new RelationshipCollection();
+ MapRepository.Instance.Relationships[entityType] = relationships;
+ return new RelationshipBuilder(_fluentEntity, relationships);
+ }
+ }
+ }
+}
diff --git a/Marr.Data/Mapping/IColumnInfo.cs b/Marr.Data/Mapping/IColumnInfo.cs
new file mode 100644
index 000000000..4b54b1d5b
--- /dev/null
+++ b/Marr.Data/Mapping/IColumnInfo.cs
@@ -0,0 +1,37 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Data.Common;
+using System.Data.OleDb;
+
+namespace Marr.Data.Mapping
+{
+ public interface IColumnInfo
+ {
+ string Name { get; set; }
+ string AltName { get; set; }
+ int Size { get; set; }
+ bool IsPrimaryKey { get; set; }
+ bool IsAutoIncrement { get; set; }
+ bool ReturnValue { get; set; }
+ ParameterDirection ParamDirection { get; set; }
+ string TryGetAltName();
+ }
+
+}
diff --git a/Marr.Data/Mapping/IRelationshipInfo.cs b/Marr.Data/Mapping/IRelationshipInfo.cs
new file mode 100644
index 000000000..fc28fe0a6
--- /dev/null
+++ b/Marr.Data/Mapping/IRelationshipInfo.cs
@@ -0,0 +1,34 @@
+/* Copyright (C) 2008 - 2011 Jordan Marr
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library. If not, see . */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Marr.Data.Mapping
+{
+ public interface IRelationshipInfo
+ {
+ RelationshipTypes RelationType { get; set; }
+ Type EntityType { get; set; }
+ }
+
+ public enum RelationshipTypes
+ {
+ AutoDetect,
+ One,
+ Many
+ }
+}
diff --git a/Marr.Data/Mapping/MapBuilder.cs b/Marr.Data/Mapping/MapBuilder.cs
new file mode 100644
index 000000000..bb82cc9c3
--- /dev/null
+++ b/Marr.Data/Mapping/MapBuilder.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Marr.Data.Mapping.Strategies;
+using System.Reflection;
+using System.Collections;
+
+namespace Marr.Data.Mapping
+{
+ [Obsolete("This class is obsolete. Please use the 'Mappings' class.")]
+ public class MapBuilder
+ {
+ private bool _publicOnly;
+
+ public MapBuilder()
+ : this(true)
+ { }
+
+ public MapBuilder(bool publicOnly)
+ {
+ _publicOnly = publicOnly;
+ }
+
+ #region - Columns -
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps all properties except ICollection properties.
+ ///
+ /// The type that is being built.
+ ///
+ public ColumnMapBuilder BuildColumns()
+ {
+ return BuildColumns(m => m.MemberType == MemberTypes.Property &&
+ !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps all properties that are simple types (int, string, DateTime, etc).
+ /// ICollection properties are not included.
+ ///
+ /// The type that is being built.
+ ///
+ public ColumnMapBuilder BuildColumnsFromSimpleTypes()
+ {
+ return BuildColumns(m => m.MemberType == MemberTypes.Property &&
+ DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
+ !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps properties that are included in the include list.
+ ///
+ /// The type that is being built.
+ ///
+ ///
+ public ColumnMapBuilder BuildColumns(params string[] propertiesToInclude)
+ {
+ return BuildColumns(m =>
+ m.MemberType == MemberTypes.Property &&
+ propertiesToInclude.Contains(m.Name));
+ }
+
+ ///
+ /// Creates column mappings for the given type.
+ /// Maps all properties except the ones in the exclusion list.
+ ///
+ /// The type that is being built.
+ ///
+ ///
+ public ColumnMapBuilder BuildColumnsExcept(params string[] propertiesToExclude)
+ {
+ return BuildColumns(m =>
+ m.MemberType == MemberTypes.Property &&
+ !propertiesToExclude.Contains(m.Name));
+ }
+
+ ///
+ /// Creates column mappings for the given type if they match the predicate.
+ ///
+ /// The type that is being built.
+ /// Determines whether a mapping should be created based on the member info.
+ ///
+ public ColumnMapBuilder BuildColumns(Func predicate)
+ {
+ Type entityType = typeof(T);
+ ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
+ strategy.ColumnPredicate = predicate;
+ ColumnMapCollection columns = strategy.MapColumns(entityType);
+ MapRepository.Instance.Columns[entityType] = columns;
+ return new ColumnMapBuilder(null, columns);
+ }
+
+ ///
+ /// Creates a ColumnMapBuilder that starts out with no pre-populated columns.
+ /// All columns must be added manually using the builder.
+ ///
+ ///
+ ///
+ public ColumnMapBuilder Columns()
+ {
+ Type entityType = typeof(T);
+ ColumnMapCollection columns = new ColumnMapCollection();
+ MapRepository.Instance.Columns[entityType] = columns;
+ return new ColumnMapBuilder(null, columns);
+ }
+
+ #endregion
+
+ #region - Relationships -
+
+ ///
+ /// Creates relationship mappings for the given type.
+ /// Maps all properties that implement ICollection.
+ ///
+ /// The type that is being built.
+ ///
+ public RelationshipBuilder BuildRelationships()
+ {
+ return BuildRelationships(m =>
+ m.MemberType == MemberTypes.Property &&
+ typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
+ }
+
+ ///
+ /// Creates relationship mappings for the given type.
+ /// Maps all properties that are listed in the include list.
+ ///
+ /// The type that is being built.
+ ///
+ ///
+ public RelationshipBuilder BuildRelationships(params string[] propertiesToInclude)
+ {
+ Func predicate = m =>
+ (
+ // ICollection properties
+ m.MemberType == MemberTypes.Property &&
+ typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) &&
+ propertiesToInclude.Contains(m.Name)
+ ) || ( // Single entity properties
+ m.MemberType == MemberTypes.Property &&
+ !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) &&
+ propertiesToInclude.Contains(m.Name)
+ );
+
+ return BuildRelationships(predicate);
+ }
+
+ ///
+ /// Creates relationship mappings for the given type if they match the predicate.
+ ///
+ /// The type that is being built.
+ /// Determines whether a mapping should be created based on the member info.
+ ///