Creating an ORM in C# Revisited – Part 2
Apparently this didn't actually get uploaded, so here's the second part of this a bit late along with the third. Anyway, last time I talked about coming up with a simple object to object mapper to help speed up and simplify the ORM. In this post we're going to worry about actually connecting to the database. In Dapper and Massive, they simply uses extension methods to DBConnections. I could easily do that but for a while now I've been using a class called SQLHelper to simplify connecting to a database (not Microsoft's SQLHelper, it's my own class). For me, it simplifies things quite a bit (I can set up the connection in one line instead of ten), so I'll be using that instead:
/\*
Copyright (c) 2011 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/
#region Usings
using System;
using System.Data;
using System.Data.SqlClient;
using System.Xml;
using System.Data.Common;
#endregion
namespace Utilities.SQL
{
/// <summary>
/// SQL Helper class
/// </summary>
public class SQLHelper : IDisposable
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
/// <param name="Command">Stored procedure/SQL Text to use</param>
/// <param name="ConnectionUsing">The connection string to user</param>
/// <param name="CommandType">The command type of the command sent in</param>
public SQLHelper(string Command, string ConnectionUsing, CommandType CommandType, string DbType = "System.Data.SqlClient")
{
Factory = DbProviderFactories.GetFactory(DbType);
Connection = Factory.CreateConnection();
Connection.ConnectionString = ConnectionUsing;
\_Command = Command;
\_CommandType = CommandType;
ExecutableCommand = Factory.CreateCommand();
ExecutableCommand.CommandText = \_Command;
ExecutableCommand.Connection = Connection;
ExecutableCommand.CommandType = CommandType;
}
#endregion
#region Properties
/// <summary>
/// Db provider factory (creates connections, etc.)
/// </summary>
protected virtual DbProviderFactory Factory { get; set; }
/// <summary>
/// Connection to the database
/// </summary>
protected virtual DbConnection Connection { get; set; }
/// <summary>
/// The executable command
/// </summary>
protected DbCommand ExecutableCommand { get; set; }
/// <summary>
/// The data reader for the query
/// </summary>
protected DbDataReader Reader { get; set; }
/// <summary>
/// The transaction associated with the query
/// </summary>
protected DbTransaction Transaction { get; set; }
/// <summary>
/// Stored procedure's name or SQL Text
/// </summary>
public virtual string Command
{
get { return \_Command; }
set
{
\_Command = value;
RecreateConnection();
}
}
private string \_Command = null;
/// <summary>
/// Command Type
/// </summary>
public virtual CommandType CommandType
{
get { return \_CommandType; }
set
{
\_CommandType = value;
RecreateConnection();
}
}
private CommandType \_CommandType;
#endregion
#region Functions
#region AddOutputParameter
/// <summary>
/// Adds an output parameter
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Type">SQL type of the parameter</param>
public virtual void AddOutputParameter(string ID, SqlDbType Type)
{
AddOutputParameter(ID, Utilities.DataTypes.DataTypeConversion.SqlDbTypeToDbType(Type));
}
/// <summary>
/// Adds an output parameter
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Type">SQL type of the parameter</param>
public virtual void AddOutputParameter(string ID, DbType Type)
{
if (ExecutableCommand != null)
{
DbParameter Parameter = null;
if (ExecutableCommand.Parameters.Contains(ID))
Parameter = ExecutableCommand.Parameters\[ID\];
else
{
Parameter = ExecutableCommand.CreateParameter();
ExecutableCommand.Parameters.Add(Parameter);
}
Parameter.ParameterName = ID;
Parameter.Value = null;
Parameter.DbType = Type;
Parameter.Direction = ParameterDirection.Output;
}
}
/// <summary>
/// Adds an output parameter
/// </summary>
/// <typeparam name="DataType">Data type of the parameter</typeparam>
/// <param name="ID">ID associated with the output parameter</param>
public virtual void AddOutputParameter<DataType>(string ID)
{
AddOutputParameter(ID, Utilities.DataTypes.DataTypeConversion.NetTypeToDbType(typeof(DataType)));
}
/// <summary>
/// Adds an output parameter
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Length">Length of the string (either -1 or 5000 should be used to indicate nvarchar(max))</param>
public virtual void AddOutputParameter(string ID, int Length)
{
if (Length == 5000)
Length = -1;
if (ExecutableCommand != null)
{
DbParameter Parameter = null;
if (ExecutableCommand.Parameters.Contains(ID))
Parameter = ExecutableCommand.Parameters\[ID\];
else
{
Parameter = ExecutableCommand.CreateParameter();
ExecutableCommand.Parameters.Add(Parameter);
}
Parameter.ParameterName = ID;
Parameter.Value = null;
Parameter.DbType = Utilities.DataTypes.DataTypeConversion.NetTypeToDbType(typeof(string));
Parameter.Direction = ParameterDirection.Output;
Parameter.Size = Length;
}
}
#endregion
#region AddParameter
/// <summary>
/// Adds a parameter to the call (for strings only)
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Value">Value to add</param>
/// <param name="Length">Size of the string(either -1 or 5000 should be used to indicate nvarchar(max))</param>
public virtual void AddParameter(string ID, string Value, int Length)
{
if (Length == 5000)
Length = -1;
if (ExecutableCommand != null)
{
DbParameter Parameter = null;
if (ExecutableCommand.Parameters.Contains(ID))
Parameter = ExecutableCommand.Parameters\[ID\];
else
{
Parameter = ExecutableCommand.CreateParameter();
ExecutableCommand.Parameters.Add(Parameter);
}
Parameter.ParameterName = ID;
Parameter.Value = (string.IsNullOrEmpty(Value)) ? System.DBNull.Value : (object)Value;
Parameter.IsNullable = (string.IsNullOrEmpty(Value));
Parameter.DbType = Utilities.DataTypes.DataTypeConversion.NetTypeToDbType(typeof(string));
Parameter.Direction = ParameterDirection.Input;
Parameter.Size = Length;
}
}
/// <summary>
/// Adds a parameter to the call (for all types other than strings)
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Value">Value to add</param>
/// <param name="Type">SQL type of the parameter</param>
public virtual void AddParameter(string ID, object Value, SqlDbType Type)
{
AddParameter(ID, Value, Utilities.DataTypes.DataTypeConversion.SqlDbTypeToDbType(Type));
}
/// <summary>
/// Adds a parameter to the call (for all types other than strings)
/// </summary>
/// <typeparam name="DataType">Data type of the parameter</typeparam>
/// <param name="ID">Name of the parameter</param>
/// <param name="Value">Value to add</param>
public virtual void AddParameter<DataType>(string ID, DataType Value)
{
AddParameter(ID, Value, Utilities.DataTypes.DataTypeConversion.NetTypeToDbType(Value.GetType()));
}
/// <summary>
/// Adds a parameter to the call (for all types other than strings)
/// </summary>
/// <param name="ID">Name of the parameter</param>
/// <param name="Value">Value to add</param>
/// <param name="Type">SQL type of the parameter</param>
public virtual void AddParameter(string ID, object Value, DbType Type)
{
if (ExecutableCommand != null)
{
DbParameter Parameter = null;
if (ExecutableCommand.Parameters.Contains(ID))
Parameter = ExecutableCommand.Parameters\[ID\];
else
{
Parameter = ExecutableCommand.CreateParameter();
ExecutableCommand.Parameters.Add(Parameter);
}
Parameter.ParameterName = ID;
Parameter.Value = (Value == null) ? System.DBNull.Value : Value;
Parameter.IsNullable = (Value == null);
Parameter.DbType = Type;
Parameter.Direction = ParameterDirection.Input;
}
}
#endregion
#region BeginTransaction
/// <summary>
/// Begins a transaction
/// </summary>
public virtual void BeginTransaction()
{
Open();
Transaction = Connection.BeginTransaction();
Command = \_Command;
}
#endregion
#region ClearParameters
/// <summary>
/// Clears the parameters
/// </summary>
public virtual void ClearParameters()
{
if (ExecutableCommand != null)
ExecutableCommand.Parameters.Clear();
}
#endregion
#region Close
/// <summary>
/// Closes the connection
/// </summary>
public virtual void Close()
{
if (ExecutableCommand != null
&& ExecutableCommand.Connection != null
&& ExecutableCommand.Connection.State != ConnectionState.Closed)
ExecutableCommand.Connection.Close();
}
#endregion
#region Commit
/// <summary>
/// Commits a transaction
/// </summary>
public virtual void Commit()
{
if (Transaction != null)
Transaction.Commit();
}
#endregion
#region ExecuteDataSet
/// <summary>
/// Executes the query and returns a data set
/// </summary>
/// <returns>A dataset filled with the results of the query</returns>
public virtual DataSet ExecuteDataSet()
{
Open();
if (ExecutableCommand != null)
{
DbDataAdapter Adapter = Factory.CreateDataAdapter();
Adapter.SelectCommand = ExecutableCommand;
DataSet ReturnSet = new DataSet();
Adapter.Fill(ReturnSet);
return ReturnSet;
}
return null;
}
#endregion
#region ExecuteNonQuery
/// <summary>
/// Executes the stored procedure as a non query
/// </summary>
/// <returns>Number of rows effected</returns>
public virtual int ExecuteNonQuery()
{
Open();
if (ExecutableCommand != null)
return ExecutableCommand.ExecuteNonQuery();
return 0;
}
#endregion
#region ExecuteReader
/// <summary>
/// Executes the stored procedure and returns a reader object
/// </summary>
public virtual void ExecuteReader()
{
Open();
if (ExecutableCommand != null)
Reader = ExecutableCommand.ExecuteReader();
}
#endregion
#region ExecuteScalar
/// <summary>
/// Executes the stored procedure as a scalar query
/// </summary>
/// <returns>The object of the first row and first column</returns>
public virtual object ExecuteScalar()
{
Open();
if (ExecutableCommand != null)
return ExecutableCommand.ExecuteScalar();
return null;
}
#endregion
#region ExecuteXmlReader
/// <summary>
/// Executes the query and returns an XmlReader
/// </summary>
/// <returns>The XmlReader filled with the data from the query</returns>
public virtual XmlReader ExecuteXmlReader()
{
Open();
if (ExecutableCommand != null && ExecutableCommand is SqlCommand)
return ((SqlCommand)ExecutableCommand).ExecuteXmlReader();
return null;
}
#endregion
#region GetParameter
/// <summary>
/// Returns a parameter's value
/// </summary>
/// <param name="ID">Parameter name</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual object GetParameter(string ID, object Default)
{
if (Reader != null && !Convert.IsDBNull(Reader\[ID\]))
return Reader\[ID\];
return Default;
}
/// <summary>
/// Returns a parameter's value
/// </summary>
/// <typeparam name="DataType">Data type of the object</typeparam>
/// <param name="ID">Parameter name</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual DataType GetParameter<DataType>(string ID, DataType Default)
{
if (Reader != null && !Convert.IsDBNull(Reader\[ID\]))
return (DataType)Convert.ChangeType(Reader\[ID\], typeof(DataType));
return Default;
}
/// <summary>
/// Returns a parameter's value
/// </summary>
/// <param name="Position">Position in the row</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual object GetParameter(int Position, object Default)
{
if (Reader != null && !Convert.IsDBNull(Reader\[Position\]))
return Reader\[Position\];
return Default;
}
/// <summary>
/// Returns a parameter's value
/// </summary>
/// <typeparam name="DataType">Data type of the object</typeparam>
/// <param name="Position">Position in the row</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual DataType GetParameter<DataType>(int Position, DataType Default)
{
if (Reader != null && !Convert.IsDBNull(Reader\[Position\]))
return (DataType)Convert.ChangeType(Reader\[Position\], typeof(DataType));
return Default;
}
#endregion
#region GetOutputParameter
/// <summary>
/// Returns an output parameter's value
/// </summary>
/// <param name="ID">Parameter name</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual object GetOutputParameter(string ID, object Default)
{
if (ExecutableCommand != null && !Convert.IsDBNull(ExecutableCommand.Parameters\[ID\]))
return ExecutableCommand.Parameters\[ID\].Value;
return Default;
}
/// <summary>
/// Returns an output parameter's value
/// </summary>
/// <typeparam name="DataType">Data type of the object</typeparam>
/// <param name="ID">Parameter name</param>
/// <param name="Default">Default value for the parameter</param>
/// <returns>if the parameter exists (and isn't null or empty), it returns the parameter's value. Otherwise the default value is returned.</returns>
public virtual DataType GetOutputParameter<DataType>(string ID, DataType Default)
{
if (ExecutableCommand != null && !Convert.IsDBNull(ExecutableCommand.Parameters\[ID\]))
return (DataType)Convert.ChangeType(ExecutableCommand.Parameters\[ID\].Value, typeof(DataType));
return Default;
}
#endregion
#region NextResult
/// <summary>
/// Goes to the next result set (used if multiple queries are sent in)
/// </summary>
public virtual void NextResult()
{
if (Reader != null)
Reader.NextResult();
}
#endregion
#region Open
/// <summary>
/// Opens the connection
/// </summary>
public virtual void Open()
{
if (ExecutableCommand != null
&& ExecutableCommand.Connection != null
&& ExecutableCommand.Connection.State != ConnectionState.Open)
ExecutableCommand.Connection.Open();
}
#endregion
#region Read
/// <summary>
/// Is there more information?
/// </summary>
/// <returns>True if there is more rows, false otherwise</returns>
public virtual bool Read()
{
return (Reader != null) ? Reader.Read() : false;
}
#endregion
#region RecreateConnection
private void RecreateConnection()
{
if (Reader != null)
{
Reader.Close();
Reader.Dispose();
Reader = null;
}
if (ExecutableCommand != null)
{
ExecutableCommand.Dispose();
ExecutableCommand = null;
}
ExecutableCommand = Factory.CreateCommand();
ExecutableCommand.CommandText = \_Command;
ExecutableCommand.Connection = Connection;
ExecutableCommand.CommandType = CommandType;
if (Transaction != null)
ExecutableCommand.Transaction = Transaction;
}
#endregion
#region Rollback
/// <summary>
/// Rolls back a transaction
/// </summary>
public virtual void Rollback()
{
if (Transaction != null)
Transaction.Rollback();
}
#endregion
#endregion
#region IDisposable Members
public virtual void Dispose()
{
Close();
if (Connection != null)
{
Connection.Dispose();
Connection = null;
}
if (Transaction != null)
{
Transaction.Dispose();
Transaction = null;
}
if (ExecutableCommand != null)
{
ExecutableCommand.Dispose();
ExecutableCommand = null;
}
if (Reader != null)
{
Reader.Dispose();
Reader = null;
}
}
#endregion
}
}
Note that you don't have to use something like this, you can simply add extension methods to DBConnection (like Massive/Dapper). This is just a bit of code to help make things easier for me. The next bit of code that is needed is a way to map the various properties of the business object to the database:
#region Map
/// <summary>
/// Maps a property to a database property name (required to actually get data from the database)
/// </summary>
/// <typeparam name="DataType">Data type of the property</typeparam>
/// <param name="Property">Property to add a mapping for</param>
/// <param name="DatabasePropertyName">Property name</param>
public virtual Mapping<ClassType> Map<DataType>(Expression<Func<ClassType, DataType>> Property, string DatabasePropertyName)
{
Check(Property, "Property");
Check(DatabasePropertyName, "DatabasePropertyName");
Check(Mappings, "Mappings");
Expression Convert = Expression.Convert(Property.Body, typeof(object));
Expression<Func<ClassType, object\>> PropertyExpression = Expression.Lambda<Func<ClassType, object\>>(Convert, Property.Parameters);
Mappings.AddMapping(PropertyExpression,
new Func<SQLHelper, object\>((x) => x.GetParameter(DatabasePropertyName, default(DataType))),
new Action<SQLHelper, object\>((x, y) => x.AddParameter(DatabasePropertyName, y)));
ParameterNames.Add(DatabasePropertyName);
return this;
}
/// <summary>
/// Maps a property to a database property name (required to actually get data from the database)
/// </summary>
/// <param name="Property">Property to add a mapping for</param>
/// <param name="DatabasePropertyName">Property name</param>
/// <param name="Length">Max length of the string</param>
public virtual Mapping<ClassType> Map(Expression<Func<ClassType, string\>> Property, string DatabasePropertyName, int Length)
{
Check(Property, "Property");
Check(DatabasePropertyName, "DatabasePropertyName");
Check(Mappings, "Mappings");
Expression Convert = Expression.Convert(Property.Body, typeof(object));
Expression<Func<ClassType, object\>> PropertyExpression = Expression.Lambda<Func<ClassType, object\>>(Convert, Property.Parameters);
Mappings.AddMapping(PropertyExpression,
new Func<SQLHelper, object\>((x) => x.GetParameter(DatabasePropertyName, "")),
new Action<SQLHelper, object\>((x, y) => x.AddParameter(DatabasePropertyName, (string)y, Length)));
ParameterNames.Add(DatabasePropertyName);
return this;
}
#endregion
This code comes from a class called Mapping, which will hold our data and make our calls to the database. These functions simply map our properties to database properties. As you can see, it takes in an expression and the name of the parameter (database side). It in turn uses that expression and our object to object mapper from the previous post to create a mapping between the SQLHelper (the GetParameter/AddParameter calls). This isn't perfect as it doesn't deal with IEnumerables that well but we're only going to deal with simple items for now (int, string, etc.). Also it returns this so we can do a nice, simple fluent interface. So now that we've seen the small bit of code that handles the mapping of the data, lets look at everything else:
/\*
Copyright (c) 2011 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/
#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities.DataMapper;
using System.Linq.Expressions;
using Utilities.SQL.MicroORM.Interfaces;
using System.Data;
#endregion
namespace Utilities.SQL.MicroORM
{
/// <summary>
/// Class that acts as a mapping within the micro ORM
/// </summary>
/// <typeparam name="ClassType">Class type that this will accept</typeparam>
public class Mapping<ClassType> : IMapping where ClassType : class,new()
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
/// <param name="Connection">Connection string</param>
/// <param name="TableName">Table name</param>
/// <param name="PrimaryKey">Primary key in the table</param>
/// <param name="AutoIncrement">Is the primary key set to auto increment?</param>
/// <param name="ParameterStarter">What the database expects as the
/// parameter starting string ("@" for SQL Server, ":" for Oracle, etc.)</param>
/// <param name="DbType">DbType for this connection</param>
public Mapping(string Connection, string TableName, string PrimaryKey, bool AutoIncrement = true, string ParameterStarter = "@", string DbType = "System.Data.SqlClient")
{
Helper = new SQLHelper("", Connection, System.Data.CommandType.Text, DbType);
Mappings = new TypeMapping<ClassType, SQLHelper>();
ParameterNames = new List<string\>();
this.TableName = TableName;
this.PrimaryKey = PrimaryKey;
this.AutoIncrement = AutoIncrement;
this.ParameterStarter = ParameterStarter;
}
/// <summary>
/// Constructor (can be used if supplying own SQLHelper)
/// </summary>
/// <param name="TableName">Table name</param>
/// <param name="PrimaryKey">Primary key</param>
/// <param name="AutoIncrement">Is the primary key set to auto increment?</param>
/// <param name="ParameterStarter">What the database expects as the
/// parameter starting string ("@" for SQL Server, ":" for Oracle, etc.)</param>
public Mapping(string TableName, string PrimaryKey, bool AutoIncrement = true, string ParameterStarter = "@")
{
Mappings = new TypeMapping<ClassType, SQLHelper>();
ParameterNames = new List<string\>();
this.TableName = TableName;
this.PrimaryKey = PrimaryKey;
this.AutoIncrement = AutoIncrement;
this.ParameterStarter = ParameterStarter;
}
#endregion
#region Properties
/// <summary>
/// SQL Helper
/// </summary>
public virtual SQLHelper Helper { get; set; }
/// <summary>
/// Mapper used to map properties to SQLHelper
/// </summary>
public virtual TypeMapping<ClassType, SQLHelper> Mappings { get; set; }
/// <summary>
/// Table name
/// </summary>
protected virtual string TableName { get; set; }
/// <summary>
/// Primar key
/// </summary>
protected virtual string PrimaryKey { get; set; }
/// <summary>
/// Auto increment?
/// </summary>
protected virtual bool AutoIncrement { get; set; }
/// <summary>
/// Parameter starter
/// </summary>
protected virtual string ParameterStarter { get; set; }
/// <summary>
/// Parameter names
/// </summary>
public virtual List<string\> ParameterNames { get; set; }
#endregion
#region Public Functions
#region All
/// <summary>
/// Gets a list of all objects that meet the specified criteria
/// </summary>
/// <param name="Command">Command to use (can be an SQL string or stored procedure)</param>
/// <param name="CommandType">Command type</param>
/// <param name="Parameters">Parameters to search by</param>
/// <returns>A list of all objects that meet the specified criteria</returns>
public virtual IEnumerable<ClassType> All(string Command, CommandType CommandType, params IParameter\[\] Parameters)
{
Check(Command, "Command");
Check(Helper, "Helper");
Check(Mappings, "Mappings");
List<ClassType> Return = new List<ClassType>();
SetupCommand(Command, CommandType, Parameters);
Helper.ExecuteReader();
while (Helper.Read())
{
ClassType Temp = new ClassType();
Mappings.Copy(Helper, Temp);
Return.Add(Temp);
}
return Return;
}
/// <summary>
/// Gets a list of all objects that meet the specified criteria
/// </summary>
/// <param name="Columns">Columns to return</param>
/// <param name="Limit">Limit on the number of items to return</param>
/// <param name="OrderBy">Order by clause</param>
/// <param name="Parameters">Parameters to search by</param>
/// <returns>A list of all objects that meet the specified criteria</returns>
public virtual IEnumerable<ClassType> All(string Columns = "\*", int Limit = 0, string OrderBy = "", params IParameter\[\] Parameters)
{
Check(Columns, "Columns");
return All(SetupSelectCommand(Columns, Limit, OrderBy, Parameters), CommandType.Text, Parameters);
}
#endregion
#region Any
/// <summary>
/// Gets a single object that fits the criteria
/// </summary>
/// <param name="Columns">Columns to select</param>
/// <param name="Parameters">Parameters to search by</param>
/// <returns>An object fitting the criteria specified or null if none are found</returns>
public virtual ClassType Any(string Columns = "\*", ClassType ObjectToReturn = null, params IParameter\[\] Parameters)
{
Check(Columns, "Columns");
return Any(SetupSelectCommand(Columns, 1, "", Parameters), CommandType.Text, ObjectToReturn, Parameters);
}
/// <summary>
/// Gets a single object that fits the criteria
/// </summary>
/// <param name="Command">Command to use (can be an SQL string or stored procedure name)</param>
/// <param name="CommandType">Command type</param>
/// <param name="ObjectToReturn">Object to return (in case the object needs to be created outside this)</param>
/// <param name="Parameters">Parameters used to search by</param>
/// <returns>An object fitting the criteria specified or null if none are found</returns>
public virtual ClassType Any(string Command, CommandType CommandType, ClassType ObjectToReturn = null, params IParameter\[\] Parameters)
{
Check(Mappings, "Mappings");
Check(Command, "Command");
Check(Helper, "Helper");
ClassType Return = (ObjectToReturn == null) ? new ClassType() : ObjectToReturn;
SetupCommand(Command, CommandType, Parameters);
Helper.ExecuteReader();
if (Helper.Read())
Mappings.Copy(Helper, Return);
return Return;
}
#endregion
#region Close
/// <summary>
/// Closes the connection to the database
/// </summary>
public virtual void Close()
{
Check(Helper, "Helper");
Helper.Close();
}
#endregion
#region Delete
/// <summary>
/// Deletes an object from the database
/// </summary>
/// <param name="Command">Command to use</param>
/// <param name="CommandType">Command type</param>
/// <param name="Object">Object to delete</param>
public virtual void Delete(string Command, CommandType CommandType, ClassType Object)
{
Check(Object, "Object");
Check(Command, "Command");
Check(Helper, "Helper");
Check(Mappings, "Mappings");
SetupCommand(Command, CommandType, null);
Mappings.Copy(Object, Helper);
Helper.ExecuteNonQuery();
}
/// <summary>
/// Deletes an object from the database
/// </summary>
/// <param name="Object">Object to delete</param>
public virtual void Delete(ClassType Object)
{
Delete(SetupDeleteCommand(), CommandType.Text, Object);
}
#endregion
#region Insert
/// <summary>
/// Inserts an object based on the command specified
/// </summary>
/// <typeparam name="DataType">Data type expected to be returned from the query (to get the ID, etc.)</typeparam>
/// <param name="Command">Command to run</param>
/// <param name="CommandType">Command type</param>
/// <param name="Object">Object to insert</param>
/// <returns>The returned object from the query (usually the newly created row's ID)</returns>
public virtual DataType Insert<DataType>(string Command, CommandType CommandType, ClassType Object)
{
Check(Object, "Object");
Check(Command, "Command");
Check(Helper, "Helper");
Check(Mappings, "Mappings");
SetupCommand(Command, CommandType, null);
Mappings.Copy(Object, Helper);
return (DataType)Convert.ChangeType(Helper.ExecuteScalar(), typeof(DataType));
}
/// <summary>
/// Inserts an object into the database
/// </summary>
/// <typeparam name="DataType">Data type expected (should be the same type as the primary key)</typeparam>
/// <param name="Object">Object to insert</param>
/// <returns>The returned object from the query (the newly created row's ID)</returns>
public virtual DataType Insert<DataType>(ClassType Object)
{
return Insert<DataType>(SetupInsertCommand(), CommandType.Text, Object);
}
#endregion
#region Map
/// <summary>
/// Maps a property to a database property name (required to actually get data from the database)
/// </summary>
/// <typeparam name="DataType">Data type of the property</typeparam>
/// <param name="Property">Property to add a mapping for</param>
/// <param name="DatabasePropertyName">Property name</param>
public virtual Mapping<ClassType> Map<DataType>(Expression<Func<ClassType, DataType>> Property, string DatabasePropertyName)
{
Check(Property, "Property");
Check(DatabasePropertyName, "DatabasePropertyName");
Check(Mappings, "Mappings");
Expression Convert = Expression.Convert(Property.Body, typeof(object));
Expression<Func<ClassType, object\>> PropertyExpression = Expression.Lambda<Func<ClassType, object\>>(Convert, Property.Parameters);
Mappings.AddMapping(PropertyExpression,
new Func<SQLHelper, object\>((x) => x.GetParameter(DatabasePropertyName, default(DataType))),
new Action<SQLHelper, object\>((x, y) => x.AddParameter(DatabasePropertyName, y)));
ParameterNames.Add(DatabasePropertyName);
return this;
}
/// <summary>
/// Maps a property to a database property name (required to actually get data from the database)
/// </summary>
/// <param name="Property">Property to add a mapping for</param>
/// <param name="DatabasePropertyName">Property name</param>
/// <param name="Length">Max length of the string</param>
public virtual Mapping<ClassType> Map(Expression<Func<ClassType, string\>> Property, string DatabasePropertyName, int Length)
{
Check(Property, "Property");
Check(DatabasePropertyName, "DatabasePropertyName");
Check(Mappings, "Mappings");
Expression Convert = Expression.Convert(Property.Body, typeof(object));
Expression<Func<ClassType, object\>> PropertyExpression = Expression.Lambda<Func<ClassType, object\>>(Convert, Property.Parameters);
Mappings.AddMapping(PropertyExpression,
new Func<SQLHelper, object\>((x) => x.GetParameter(DatabasePropertyName, "")),
new Action<SQLHelper, object\>((x, y) => x.AddParameter(DatabasePropertyName, (string)y, Length)));
ParameterNames.Add(DatabasePropertyName);
return this;
}
#endregion
#region Open
/// <summary>
/// Opens the connection to the database
/// </summary>
public virtual void Open()
{
Check(Helper, "Helper");
Helper.Open();
}
#endregion
#region PageCount
/// <summary>
/// Gets the number of pages based on the specified
/// </summary>
/// <param name="PageSize">Page size</param>
/// <param name="Parameters">Parameters to search by</param>
/// <returns>The number of pages that the table contains for the specified page size</returns>
public virtual int PageCount(int PageSize = 25, params IParameter\[\] Parameters)
{
Check(Helper, "Helper");
SetupCommand(SetupPageCountCommand(PageSize, Parameters), CommandType.Text, Parameters);
Helper.ExecuteReader();
if (Helper.Read())
{
int Total = Helper.GetParameter("Total", 0);
return Total % PageSize == 0 ? Total / PageSize : (Total / PageSize) + 1;
}
return 0;
}
#endregion
#region Paged
/// <summary>
/// Gets a paged list of objects fitting the specified criteria
/// </summary>
/// <param name="Columns">Columns to return</param>
/// <param name="OrderBy">Order by clause</param>
/// <param name="PageSize">Page size</param>
/// <param name="CurrentPage">The current page (starting at 0)</param>
/// <param name="Parameters">Parameters to search by</param>
/// <returns>A list of objects that fit the specified criteria</returns>
public virtual IEnumerable<ClassType> Paged(string Columns = "\*", string OrderBy = "", int PageSize = 25, int CurrentPage = 0, params IParameter\[\] Parameters)
{
Check(Columns, "Columns");
return All(SetupPagedCommand(Columns, OrderBy, PageSize, CurrentPage, Parameters), CommandType.Text, Parameters);
}
#endregion
#region Update
/// <summary>
/// Updates an object in the database
/// </summary>
/// <param name="Command">Command to use</param>
/// <param name="CommandType">Command type</param>
/// <param name="Object">Object to update</param>
public virtual void Update(string Command, CommandType CommandType, ClassType Object)
{
Check(Helper, "Helper");
Check(Mappings, "Mappings");
Check(Command, "Command");
SetupCommand(Command, CommandType, null);
Mappings.Copy(Object, Helper);
Helper.ExecuteNonQuery();
}
/// <summary>
/// Updates an object in the database
/// </summary>
/// <param name="Object">Object to update</param>
public virtual void Update(ClassType Object)
{
Update(SetupUpdateCommand(), CommandType.Text, Object);
}
#endregion
#endregion
#region Protected Functions
#region Check
/// <summary>
/// Checks if an object is null, throwing an exception if it is
/// </summary>
/// <param name="Object">Object to check</param>
/// <param name="Name">Parameter name</param>
protected virtual void Check(object Object, string Name)
{
if (Object == null)
throw new ArgumentNullException(Name);
}
/// <summary>
/// Checks if a string is null/empty, throwing an exception if it is
/// </summary>
/// <param name="String">String to check</param>
/// <param name="Name">Parameter name</param>
protected virtual void Check(string String, string Name)
{
if (string.IsNullOrEmpty(String))
throw new ArgumentNullException(Name);
}
#endregion
#region SetupCommand
/// <summary>
/// Sets up a command
/// </summary>
/// <param name="Command">Command to add to the SQL Helper</param>
/// <param name="CommandType">Command type</param>
/// <param name="Parameters">Parameter list</param>
protected virtual void SetupCommand(string Command, CommandType CommandType, IParameter\[\] Parameters)
{
Check(Helper, "Helper");
Check(Command, "Command");
Helper.Command = Command;
Helper.CommandType = CommandType;
if (Parameters != null)
{
foreach (IParameter Parameter in Parameters)
{
Parameter.AddParameter(Helper);
}
}
}
#endregion
#region SetupDeleteCommand
/// <summary>
/// Sets up the delete command
/// </summary>
/// <returns>The command string</returns>
protected virtual string SetupDeleteCommand()
{
return string.Format("DELETE FROM {0} WHERE {1}", TableName, PrimaryKey + "=" + ParameterStarter + PrimaryKey);
}
#endregion
#region SetupInsertCommand
/// <summary>
/// Sets up the insert command
/// </summary>
/// <returns>The command string</returns>
protected virtual string SetupInsertCommand()
{
string ParameterList = "";
string ValueList = "";
string Splitter = "";
foreach (string Name in ParameterNames)
{
if (!AutoIncrement || Name != PrimaryKey)
{
ParameterList += Splitter + Name;
ValueList += Splitter + ParameterStarter + Name;
Splitter = ",";
}
}
return string.Format("INSERT INTO {0}({1}) VALUES({2}) SELECT scope\_identity() as \[ID\]", TableName, ParameterList, ValueList);
}
#endregion
#region SetupPageCountCommand
/// <summary>
/// Sets up the page count command
/// </summary>
/// <param name="PageSize">Page size</param>
/// <param name="Parameters">Parameter list</param>
/// <returns>The string command</returns>
protected virtual string SetupPageCountCommand(int PageSize, IParameter\[\] Parameters)
{
string WhereCommand = "";
if (Parameters != null && Parameters.Length > 0)
{
WhereCommand += " WHERE ";
string Splitter = "";
foreach (IParameter Parameter in Parameters)
{
WhereCommand += Splitter + Parameter;
Splitter = " AND ";
}
}
return string.Format("SELECT COUNT({0}) as Total FROM {1} {2}", PrimaryKey, TableName, WhereCommand);
}
#endregion
#region SetupPagedCommand
/// <summary>
/// Sets up the paged select command
/// </summary>
/// <param name="Columns">Columns to return</param>
/// <param name="OrderBy">Order by clause</param>
/// <param name="PageSize">Page size</param>
/// <param name="CurrentPage">Current page</param>
/// <param name="Parameters">Parameter list</param>
/// <returns>The command string</returns>
protected virtual string SetupPagedCommand(string Columns, string OrderBy, int PageSize, int CurrentPage, IParameter\[\] Parameters)
{
if (string.IsNullOrEmpty(OrderBy))
OrderBy = PrimaryKey;
string WhereCommand = "";
if (Parameters != null && Parameters.Length > 0)
{
WhereCommand += " WHERE ";
string Splitter = "";
foreach (IParameter Parameter in Parameters)
{
WhereCommand += Splitter + Parameter;
Splitter = " AND ";
}
}
string Command = string.Format("SELECT {0} FROM (SELECT ROW\_NUMBER() OVER (ORDER BY {1}) AS Row, {0} FROM {2} {3}) AS Paged ", Columns, OrderBy, TableName, WhereCommand);
int PageStart = CurrentPage \* PageSize;
Command += string.Format(" WHERE Row>{0} AND Row<={1}", PageStart, PageStart + PageSize);
return Command;
}
#endregion
#region SetupSelectCommand
/// <summary>
/// Sets up the select command
/// </summary>
/// <param name="Columns">Columns to return</param>
/// <param name="Limit">limit on the number of items to return</param>
/// <param name="OrderBy">Order by clause</param>
/// <param name="Parameters">Parameter list</param>
/// <returns>The string command</returns>
protected virtual string SetupSelectCommand(string Columns, int Limit, string OrderBy, IParameter\[\] Parameters)
{
string Command = (Limit > 0 ? "SELECT TOP " + Limit : "SELECT") + " {0} FROM {1}";
if (Parameters != null && Parameters.Length > 0)
{
Command += " WHERE ";
string Splitter = "";
foreach (IParameter Parameter in Parameters)
{
Command += Splitter + Parameter;
Splitter = " AND ";
}
}
if (!string.IsNullOrEmpty(OrderBy))
Command += OrderBy.Trim().ToLower().StartsWith("order by", StringComparison.CurrentCultureIgnoreCase) ? " " + OrderBy : " ORDER BY " + OrderBy;
return string.Format(Command, Columns, TableName);
}
#endregion
#region SetupUpdateCommand
/// <summary>
/// Sets up the update command
/// </summary>
/// <returns>The command string</returns>
protected virtual string SetupUpdateCommand()
{
string ParameterList = "";
string WhereCommand = "";
string Splitter = "";
foreach (string Name in ParameterNames)
{
if (Name != PrimaryKey)
{
ParameterList += Splitter + Name + "=" + ParameterStarter + Name;
Splitter = ",";
}
else
WhereCommand = Name + "=" + ParameterStarter + Name;
}
return string.Format("UPDATE {0} SET {1} WHERE {2}", TableName, ParameterList, WhereCommand);
}
#endregion
#endregion
#region IDisposable
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
if (Helper != null)
{
Helper = null;
}
}
#endregion
}
}
Lets start with the top, the constructors take in (at minimum) the table name and primary key for the class. Doing this makes things a lot simpler later on (specifically inserts, updates, etc.). But basically we're doing basic data setup. The next bit is the properties that are needed, we'll skip that... The next, actually interesting bit of code is All. The All function makes a query against the database and gives us all entries that it finds. For the most part it's pretty straightforward, it takes in the Command (could be sql text or a stored procedure), the command type, and allows you to add a number of parameters if they are needed. The parameter class looks like the following:
/\*
Copyright (c) 2011 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/
#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities.SQL.MicroORM.Interfaces;
#endregion
namespace Utilities.SQL.MicroORM
{
/// <summary>
/// Parameter class
/// </summary>
/// <typeparam name="DataType">Type of the parameter</typeparam>
public class Parameter<DataType>:IParameter
{
#region Constructor
/// <summary>
/// Constructor
/// </summary>
/// <param name="Value">Value of the parameter</param>
/// <param name="ID">Name of the parameter</param>
/// <param name="ParameterStarter">What the database expects as the
/// parameter starting string ("@" for SQL Server, ":" for Oracle, etc.)</param>
public Parameter(DataType Value, string ID, string ParameterStarter = "@")
{
this.Value = Value;
this.ID = ID;
this.ParameterStarter = ParameterStarter;
}
#endregion
#region Properties
/// <summary>
/// Value of the parameter
/// </summary>
public DataType Value { get; set; }
/// <summary>
/// Name of the parameter
/// </summary>
public string ID { get; set; }
/// <summary>
/// Starting string of the parameter
/// </summary>
public string ParameterStarter { get; set; }
#endregion
#region Functions
public void AddParameter(SQLHelper Helper) { Helper.AddParameter(ID, Value); }
public override string ToString() { return ID + "=" + ParameterStarter + ID; }
#endregion
}
}
The parameter class is really just a glorified key/value pair. Anyway, the All function just creates a list, calls our command, and lets the object to object mapper fill our object for us. We have another option (the function that asks for Columns, etc.), that will allow us to simply call All() if we only want to worry about such things like an order by clause. In this call though the class is generating the select statement for us (Pretty standard: SELECT Columns FROM TableName WHERE [List of parameters are true] ORDER BY OrderByClause).
You'll find that most of the functions are similar to All. For instance, the Any function is pretty much a copy but it only returns the first item found by the query. Delete, Update, and Insert are only slightly different. Instead of copying data from the SQLHelper to the object, it simply goes the other way around. They all have the option to generate the code for you. Since we've set up our mappings beforehand, we know the names of the properties and we know the primary key from the constructor so it's not too difficult to generate the needed queries.
The only function that might give you pause are the Pages and PageCount functions. Paged is simply a paged query (automatically generated). It works similarly to All but takes in page size and the current page that you want (0 based as I'm not a big fan of starting at 1 for such things). The code that it generates is also rather straightforward (but probably not the best), if you've ever done a paged query before. That being said, it's currently pretty specific to SQL Server but I'll probably change that to make it a bit more open in the future. Anyway, I also have a separate query called PageCount to allow you to figure out the total number of pages based on the page size that you specify. I could have that as an out parameter of the Paged function, but I figure you might want that information separately. But that's it really. There are also Open/Close functions to actually open the connection to the database, but those are pretty simple.
So at this point we have our simple mapping of a single class. What we need now is a class to hold all of our mappings that we can use to make this simpler:
/\*
Copyright (c) 2011 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/
#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities.DataMapper;
using System.Linq.Expressions;
using Utilities.SQL.MicroORM.Interfaces;
using System.Data;
#endregion
namespace Utilities.SQL.MicroORM
{
/// <summary>
/// Manager class that can be used to manage
/// </summary>
public class MicroORM : SQLHelper
{
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public MicroORM(string Connection)
: base("", Connection, CommandType.Text)
{
}
#endregion
#region Properties
/// <summary>
/// Mappings
/// </summary>
protected static Dictionary<Type, IMapping> Mappings = new Dictionary<Type, IMapping>();
#endregion
#region Functions
/// <summary>
/// Creates a mapping
/// </summary>
/// <typeparam name="ClassType">Class type to map</typeparam>
/// <param name="TableName">Table name</param>
/// <param name="PrimaryKey">Primary key</param>
/// <param name="AutoIncrement">Auto incrementing primar key</param>
/// <param name="ParameterStarter">Parameter starter</param>
/// <returns>The created mapping (or an already created one if it exists</returns>
public static Mapping<ClassType> Map<ClassType>(string TableName, string PrimaryKey, bool AutoIncrement = true, string ParameterStarter = "@") where ClassType : class,new()
{
if (Mappings.ContainsKey(typeof(ClassType)))
return (Mapping<ClassType>)Mappings\[typeof(ClassType)\];
Mapping<ClassType> Mapping = new Mapping<ClassType>(TableName, PrimaryKey, AutoIncrement, ParameterStarter);
Mappings.Add(typeof(ClassType), Mapping);
return Mapping;
}
/// <summary>
/// Returns a specific mapping
/// </summary>
/// <typeparam name="ClassType">Class type to get</typeparam>
/// <returns>The mapping specified</returns>
public Mapping<ClassType> Map<ClassType>() where ClassType : class,new()
{
if (!Mappings.ContainsKey(typeof(ClassType)))
throw new ArgumentOutOfRangeException(typeof(ClassType).Name + " not found");
Mapping<ClassType> ReturnValue = (Mapping<ClassType>)Mappings\[typeof(ClassType)\];
ReturnValue.Helper = this;
return ReturnValue;
}
#endregion
#region IDisposable Members
public override void Dispose()
{
base.Dispose();
foreach (Type Key in Mappings.Keys)
{
Mappings\[Key\].Dispose();
}
Mappings.Clear();
}
#endregion
}
}
This class really only has a couple of functions, but inherits from the SQLHelper class from earlier. It basically just adds a static Map function (allowing us to store our mapping data in the MicroORM object). It also adds a nonstatic Map function on the object that we can use to actually call our mapping. For instance we can do the following:
class Program
{
static void Main(string\[\] args)
{
MicroORM.Map<User>("User\_", "ID\_")
.Map(x => x.ID, "ID\_")
.Map(x => x.DateCreated, "DateCreated\_")
.Map(x => x.DateModified, "DateModified\_")
.Map(x => x.Active, "Active\_")
.Map(x => x.UserName, "UserName\_", 128);
using (MicroORM ORM = new MicroORM("Data Source=localhost;Initial Catalog=MyDatabase;Integrated Security=SSPI;"))
{
IEnumerable<User> Users = ORM.Map<User>().All();
foreach (User User in Users)
{
Console.WriteLine(User.UserName);
}
}
Console.ReadKey();
}
}
public class User
{
public virtual int ID { get; set; }
public virtual DateTime DateModified { get; set; }
public virtual DateTime DateCreated { get; set; }
public virtual bool Active { get; set; }
public virtual string UserName { get; set; }
}
That code sets up the user object in a couple of lines of code, then it creates a MicroORM object an feeds it the connection string. From there it gets the User mapping and calls All (giving us all of the users). It then just spits the user names onto the screen. It's basic, simple, rather quick and all that you need for a micro ORM. In the future I will integrate this into my ORM project. Basically I'll show how to take a micro ORM and add on the features that you might want (database generation, lazy loading, etc.) while still keeping things simple.
Sign up for More Posts
I have no idea why you'd put yourself through that but if you'd like more posts from me then how about using my RSS feed? Take a look.