Commit 2e5ea74f authored by Sam Saffron's avatar Sam Saffron

merge

parents 536fed0d 04e4657b
...@@ -98,8 +98,27 @@ public void Run(int iterations) ...@@ -98,8 +98,27 @@ public void Run(int iterations)
var massiveConnection = Program.GetOpenConnection(); var massiveConnection = Program.GetOpenConnection();
tests.Add(id => massiveModel.Query("select * from Posts where Id = @0", massiveConnection, id).ToList(), "Dynamic Massive ORM Query"); tests.Add(id => massiveModel.Query("select * from Posts where Id = @0", massiveConnection, id).ToList(), "Dynamic Massive ORM Query");
// PetaPoco test with all default options
// HAND CODED var petapoco = new PetaPoco.Database(Program.connectionString, "System.Data.SqlClient");
petapoco.OpenSharedConnection();
tests.Add(id => petapoco.Fetch<Post>("SELECT * from Posts where Id=@0", id), "PetaPoco (Normal)");
// PetaPoco with some "smart" functionality disabled
var petapocoFast = new PetaPoco.Database(Program.connectionString, "System.Data.SqlClient");
petapocoFast.OpenSharedConnection();
petapocoFast.EnableAutoSelect = false;
petapocoFast.EnableNamedParams = false;
petapocoFast.ForceDateTimesToUtc = false;
tests.Add(id => petapocoFast.Fetch<Post>("SELECT * from Posts where Id=@0", id), "PetaPoco (Fast)");
// Subsonic ActiveRecord
tests.Add(id => SubSonic.Post.SingleOrDefault(x => x.Id == id), "SubSonic ActiveRecord.SingleOrDefault");
// Subsonic ActiveRecord
SubSonic.tempdbDB db=new SubSonic.tempdbDB();
tests.Add(id => new SubSonic.Query.CodingHorror(db.Provider, "select * from Posts where Id = @0", id).ExecuteTypedList<Post>(), "SubSonic Coding Horror");
// HAND CODED
var connection = Program.GetOpenConnection(); var connection = Program.GetOpenConnection();
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Reflection.Emit;
namespace PetaPoco
{
// Poco's marked [Explicit] require all column properties to be marked
[AttributeUsage(AttributeTargets.Class)]
public class ExplicitColumns : Attribute
{
}
// For non-explicit pocos, causes a property to be ignored
[AttributeUsage(AttributeTargets.Property)]
public class Ignore : Attribute
{
}
// For explicit pocos, marks property as a column and optionally supplies column name
[AttributeUsage(AttributeTargets.Property)]
public class Column : Attribute
{
public Column() { }
public Column(string name) { Name = name; }
public string Name { get; set; }
}
// For explicit pocos, marks property as a result column and optionally supplies column name
[AttributeUsage(AttributeTargets.Property)]
public class ResultColumn : Column
{
public ResultColumn() { }
public ResultColumn(string name) : base(name) { }
}
// Specify the table name of a poco
[AttributeUsage(AttributeTargets.Class)]
public class TableName : Attribute
{
public TableName(string tableName)
{
Value = tableName;
}
public string Value { get; private set; }
}
// Specific the primary key of a poco class
[AttributeUsage(AttributeTargets.Class)]
public class PrimaryKey : Attribute
{
public PrimaryKey(string primaryKey)
{
Value = primaryKey;
}
public string Value { get; private set; }
}
// Results from paged request
public class Page<T> where T : new()
{
public long CurrentPage { get; set; }
public long TotalPages { get; set; }
public long TotalItems { get; set; }
public long ItemsPerPage { get; set; }
public List<T> Items { get; set; }
}
// Optionally provide and implementation of this to Database.Mapper
public interface IMapper
{
void GetTableInfo(Type t, ref string tableName, ref string primaryKey);
bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn);
Func<object, object> GetValueConverter(PropertyInfo pi, Type SourceType);
}
// Database class ... this is where most of the action happens
public class Database : IDisposable
{
public Database(DbConnection connection)
{
_sharedConnection = connection;
_connectionString = connection.ConnectionString;
_sharedConnectionDepth = 2; // Prevent closing external connection
CommonConstruct();
}
public Database(string connectionString, string providerName)
{
_connectionString = connectionString;
_providerName = providerName;
CommonConstruct();
}
public Database(string connectionStringName)
{
// Use first?
if (connectionStringName == "")
connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;
// Work out connection string and provider name
var providerName = "System.Data.SqlClient";
if (ConfigurationManager.ConnectionStrings[connectionStringName] != null)
{
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName))
providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
}
else
{
throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'");
}
// Store factory and connection string
_connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
_providerName = providerName;
CommonConstruct();
}
// Common initialization
void CommonConstruct()
{
_transactionDepth = 0;
EnableAutoSelect = true;
EnableNamedParams = true;
ForceDateTimesToUtc = true;
if (_providerName != null)
_factory = DbProviderFactories.GetFactory(_providerName);
if (_connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0 && IsMySql())
_paramPrefix = "?";
}
// Automatically close one open shared connection
public void Dispose()
{
if (_sharedConnectionDepth > 0)
CloseSharedConnection();
}
// Who are we talking too?
bool IsMySql() { return string.Compare(_providerName, "MySql.Data.MySqlClient", true) == 0; }
bool IsSqlServer() { return string.Compare(_providerName, "System.Data.SqlClient", true) == 0; }
// Open a connection (can be nested)
public void OpenSharedConnection()
{
if (_sharedConnectionDepth == 0)
{
_sharedConnection = _factory.CreateConnection();
_sharedConnection.ConnectionString = _connectionString;
_sharedConnection.Open();
}
_sharedConnectionDepth++;
}
// Close a previously opened connection
public void CloseSharedConnection()
{
_sharedConnectionDepth--;
if (_sharedConnectionDepth == 0)
{
_sharedConnection.Dispose();
_sharedConnection = null;
}
}
// Helper to create a transaction scope
public Transaction Transaction
{
get
{
return new Transaction(this);
}
}
// Use by derived repo generated by T4 templates
public virtual void OnBeginTransaction() { }
public virtual void OnEndTransaction() { }
// Start a new transaction, can be nested, every call must be
// matched by a call to AbortTransaction or CompleteTransaction
// Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics
public void BeginTransaction()
{
_transactionDepth++;
if (_transactionDepth == 1)
{
OpenSharedConnection();
_transaction = _sharedConnection.BeginTransaction();
_transactionCancelled = false;
OnBeginTransaction();
}
}
// Internal helper to cleanup transaction stuff
void CleanupTransaction()
{
OnEndTransaction();
if (_transactionCancelled)
_transaction.Rollback();
else
_transaction.Commit();
_transaction.Dispose();
_transaction = null;
CloseSharedConnection();
}
// Abort the entire outer most transaction scope
public void AbortTransaction()
{
_transactionCancelled = true;
if ((--_transactionDepth) == 0)
CleanupTransaction();
}
// Complete the transaction
public void CompleteTransaction()
{
if ((--_transactionDepth) == 0)
CleanupTransaction();
}
// Helper to handle named parameters from object properties
static Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
public static string ProcessParams(string _sql, object[] args_src, List<object> args_dest)
{
return rxParams.Replace(_sql, m =>
{
string param = m.Value.Substring(1);
int paramIndex;
if (int.TryParse(param, out paramIndex))
{
// Numbered parameter
if (paramIndex < 0 || paramIndex >= args_src.Length)
throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql));
args_dest.Add(args_src[paramIndex]);
}
else
{
// Look for a property on one of the arguments with this name
bool found = false;
foreach (var o in args_src)
{
var pi = o.GetType().GetProperty(param);
if (pi != null)
{
args_dest.Add(pi.GetValue(o, null));
found = true;
break;
}
}
if (!found)
throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql));
}
return "@" + (args_dest.Count - 1).ToString();
}
);
}
// Add a parameter to a DB command
static void AddParam(DbCommand cmd, object item, string ParameterPrefix)
{
var p = cmd.CreateParameter();
p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count);
if (item == null)
{
p.Value = DBNull.Value;
}
else
{
if (item.GetType() == typeof(Guid))
{
p.Value = item.ToString();
p.DbType = DbType.String;
p.Size = 4000;
}
else if (item.GetType() == typeof(string))
{
p.Size = (item as string).Length + 1;
if (p.Size < 4000)
p.Size = 4000;
p.Value = item;
}
else
{
p.Value = item;
}
}
cmd.Parameters.Add(p);
}
// Create a command
public DbCommand CreateCommand(DbConnection connection, string sql, params object[] args)
{
if (EnableNamedParams)
{
// Perform named argument replacements
var new_args = new List<object>();
sql = ProcessParams(sql, args, new_args);
args = new_args.ToArray();
}
// If we're in MySQL "Allow User Variables", we need to fix up parameter prefixes
if (_paramPrefix == "?")
{
// Convert "@parameter" -> "?parameter"
Regex paramReg = new Regex(@"(?<!@)@\w+");
sql = paramReg.Replace(sql, m => "?" + m.Value.Substring(1));
// Convert @@uservar -> @uservar and @@@systemvar -> @@systemvar
sql = sql.Replace("@@", "@");
}
// Save the last sql and args
_lastSql = sql;
_lastArgs = args;
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = sql;
cmd.Transaction = _transaction;
foreach (var item in args)
{
var p = cmd.CreateParameter();
p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count);
if (item == null)
{
p.Value = DBNull.Value;
}
else
{
if (item.GetType() == typeof(Guid))
{
p.Value = item.ToString();
p.DbType = DbType.String;
p.Size = 4000;
}
else if (item.GetType() == typeof(string))
{
p.Size = (item as string).Length + 1;
if (p.Size < 4000)
p.Size = 4000;
p.Value = item;
}
else
{
p.Value = item;
}
}
cmd.Parameters.Add(p);
}
return cmd;
}
// Override this to log/capture exceptions
public virtual void OnException(Exception x)
{
System.Diagnostics.Debug.WriteLine(x.ToString());
System.Diagnostics.Debug.WriteLine(LastCommand);
}
// Execute a non-query command
public int Execute(string sql, params object[] args)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
return cmd.ExecuteNonQuery();
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
public int Execute(Sql sql)
{
return Execute(sql.SQL, sql.Arguments);
}
// Execute and cast a scalar property
public T ExecuteScalar<T>(string sql, params object[] args)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
object val = cmd.ExecuteScalar();
return (T)Convert.ChangeType(val, typeof(T));
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
public T ExecuteScalar<T>(Sql sql)
{
return ExecuteScalar<T>(sql.SQL, sql.Arguments);
}
Regex rxSelect = new Regex(@"^\s*SELECT\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string AddSelectClause<T>(string sql)
{
// Already present?
if (rxSelect.IsMatch(sql))
return sql;
// Get the poco data for this type
var pd = PocoData.ForType(typeof(T));
return string.Format("SELECT {0} FROM {1} {2}", pd.QueryColumns, pd.TableName, sql);
}
public bool EnableAutoSelect { get; set; }
public bool EnableNamedParams { get; set; }
public bool ForceDateTimesToUtc { get; set; }
// Return a typed list of pocos
public List<T> Fetch<T>(string sql, params object[] args) where T : new()
{
// Auto select clause?
if (EnableAutoSelect)
sql = AddSelectClause<T>(sql);
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
using (var r = cmd.ExecuteReader())
{
var l = new List<T>();
var pd = PocoData.ForType(typeof(T));
var factory = pd.GetFactory<T>(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r);
while (r.Read())
{
l.Add(factory(r));
}
return l;
}
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
// Optimized version when only needing a single record
public T FirstOrDefault<T>(string sql, params object[] args) where T : new()
{
// Auto select clause?
if (EnableAutoSelect)
sql = AddSelectClause<T>(sql);
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
using (var r = cmd.ExecuteReader())
{
if (!r.Read())
return default(T);
var pd = PocoData.ForType(typeof(T));
var factory = pd.GetFactory<T>(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r);
return factory(r);
}
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
// Optimized version when only wanting a single record
public T SingleOrDefault<T>(string sql, params object[] args) where T : new()
{
// Auto select clause?
if (EnableAutoSelect)
sql = AddSelectClause<T>(sql);
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
using (var r = cmd.ExecuteReader())
{
if (!r.Read())
return default(T);
var pd = PocoData.ForType(typeof(T));
var factory = pd.GetFactory<T>(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r);
T ret = factory(r);
if (r.Read())
throw new InvalidOperationException("Sequence contains more than one element");
return ret;
}
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
// Warning: scary regex follows
static Regex rxColumns = new Regex(@"^\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b",
RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*",
RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)
{
sqlSelectRemoved = null;
sqlCount = null;
sqlOrderBy = null;
// Extract the columns from "SELECT <whatever> FROM"
var m = rxColumns.Match(sql);
if (!m.Success)
return false;
// Save column list and replace with COUNT(*)
Group g = m.Groups[1];
sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);
sqlSelectRemoved = sql.Substring(g.Index);
// Look for an "ORDER BY <whatever>" clause
m = rxOrderBy.Match(sqlCount);
if (!m.Success)
return false;
g = m.Groups[0];
sqlOrderBy = g.ToString();
sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length);
return true;
}
// Fetch a page
public Page<T> Page<T>(long page, long itemsPerPage, string sql, params object[] args) where T : new()
{
// Add auto select clause
if (EnableAutoSelect)
sql = AddSelectClause<T>(sql);
// Split the SQL into the bits we need
string sqlCount, sqlSelectRemoved, sqlOrderBy;
if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy))
throw new Exception("Unable to parse SQL statement for paged query");
// Setup the paged result
var result = new Page<T>();
result.CurrentPage = page;
result.ItemsPerPage = itemsPerPage;
result.TotalItems = ExecuteScalar<long>(sqlCount, args);
result.TotalPages = result.TotalItems / itemsPerPage;
if ((result.TotalItems % itemsPerPage) != 0)
result.TotalPages++;
// Build the SQL for the actual final result
string sqlPage;
if (IsSqlServer())
{
// Ugh really?
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) AS __rn, {1}) as __paged WHERE __rn>{2} AND __rn<={3}",
sqlOrderBy, sqlSelectRemoved, (page - 1) * itemsPerPage, page * itemsPerPage);
}
else
{
// Nice
sqlPage = string.Format("{0}\nLIMIT {1} OFFSET {2}", sql, itemsPerPage, (page - 1) * itemsPerPage);
}
// Get the records
result.Items = Fetch<T>(sqlPage, args);
// Done
return result;
}
public Page<T> Page<T>(long page, long itemsPerPage, Sql sql) where T : new()
{
return Page<T>(page, itemsPerPage, sql.SQL, sql.Arguments);
}
// Return an enumerable collection of pocos
public IEnumerable<T> Query<T>(string sql, params object[] args) where T : new()
{
if (EnableAutoSelect)
sql = AddSelectClause<T>(sql);
using (var conn = new ShareableConnection(this))
{
using (var cmd = CreateCommand(conn.Connection, sql, args))
{
IDataReader r;
var pd = PocoData.ForType(typeof(T));
try
{
r = cmd.ExecuteReader();
}
catch (Exception x)
{
OnException(x);
throw;
}
var factory = pd.GetFactory<T>(sql + "-" + conn.Connection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r);
using (r)
{
while (true)
{
T poco;
try
{
if (!r.Read())
yield break;
poco = factory(r);
}
catch (Exception x)
{
OnException(x);
throw;
}
yield return poco;
}
}
}
}
}
public List<T> Fetch<T>(Sql sql) where T : new()
{
return Fetch<T>(sql.SQL, sql.Arguments);
}
public IEnumerable<T> Query<T>(Sql sql) where T : new()
{
return Query<T>(sql.SQL, sql.Arguments);
}
public T Single<T>(string sql, params object[] args) where T : new()
{
T val = SingleOrDefault<T>(sql, args);
if (val != null)
return val;
else
throw new InvalidOperationException("The sequence contains no elements");
}
public T First<T>(string sql, params object[] args) where T : new()
{
T val = FirstOrDefault<T>(sql, args);
if (val != null)
return val;
else
throw new InvalidOperationException("The sequence contains no elements");
}
public T Single<T>(Sql sql) where T : new()
{
return Single<T>(sql.SQL, sql.Arguments);
}
public T SingleOrDefault<T>(Sql sql) where T : new()
{
return SingleOrDefault<T>(sql.SQL, sql.Arguments);
}
public T FirstOrDefault<T>(Sql sql) where T : new()
{
return FirstOrDefault<T>(sql.SQL, sql.Arguments);
}
public T First<T>(Sql sql) where T : new()
{
return First<T>(sql.SQL, sql.Arguments);
}
// Insert a poco into a table. If the poco has a property with the same name
// as the primary key the id of the new record is assigned to it. Either way,
// the new id is returned.
public object Insert(string tableName, string primaryKeyName, object poco)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
var pd = PocoData.ForType(poco.GetType());
var names = new List<string>();
var values = new List<string>();
var index = 0;
foreach (var i in pd.Columns)
{
// Don't insert the primary key or result only columns
if ((primaryKeyName != null && i.Key == primaryKeyName) || i.Value.ResultColumn)
continue;
names.Add(i.Key);
values.Add(string.Format("{0}{1}", _paramPrefix, index++));
AddParam(cmd, i.Value.PropertyInfo.GetValue(poco, null), _paramPrefix);
}
cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2}); SELECT @@IDENTITY AS NewID;",
tableName,
string.Join(",", names.ToArray()),
string.Join(",", values.ToArray())
);
_lastSql = cmd.CommandText;
_lastArgs = values.ToArray();
// Insert the record, should get back it's ID
var id = cmd.ExecuteScalar();
// Assign the ID back to the primary key property
if (primaryKeyName != null)
{
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
pc.PropertyInfo.SetValue(poco, Convert.ChangeType(id, pc.PropertyInfo.PropertyType), null);
}
}
return id;
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
// Insert an annotated poco object
public object Insert(object poco)
{
var pd = PocoData.ForType(poco.GetType());
return Insert(pd.TableName, pd.PrimaryKey, poco);
}
// Update a record with values from a poco. primary key value can be either supplied or read from the poco
public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
var sb = new StringBuilder();
var index = 0;
var pd = PocoData.ForType(poco.GetType());
foreach (var i in pd.Columns)
{
// Don't update the primary key, but grab the value if we don't have it
if (i.Key == primaryKeyName)
{
if (primaryKeyValue == null)
primaryKeyValue = i.Value.PropertyInfo.GetValue(poco, null);
continue;
}
// Dont update result only columns
if (i.Value.ResultColumn)
continue;
// Build the sql
if (index > 0)
sb.Append(", ");
sb.AppendFormat("{0} = {1}{2}", i.Key, _paramPrefix, index++);
// Store the parameter in the command
AddParam(cmd, i.Value.PropertyInfo.GetValue(poco, null), _paramPrefix);
}
cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}",
tableName,
sb.ToString(),
primaryKeyName,
_paramPrefix,
index++
);
AddParam(cmd, primaryKeyValue, _paramPrefix);
_lastSql = cmd.CommandText;
_lastArgs = new object[] { primaryKeyValue };
// Do it
return cmd.ExecuteNonQuery();
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
OnException(x);
throw;
}
}
public int Update(string tableName, string primaryKeyName, object poco)
{
return Update(tableName, primaryKeyName, poco, null);
}
public int Update(object poco)
{
return Update(poco, null);
}
public int Update(object poco, object primaryKeyValue)
{
var pd = PocoData.ForType(poco.GetType());
return Update(pd.TableName, pd.PrimaryKey, poco, primaryKeyValue);
}
public int Update<T>(string sql, params object[] args)
{
var pd = PocoData.ForType(typeof(T));
return Execute(string.Format("UPDATE {0} {1}", pd.TableName, sql), args);
}
public int Update<T>(Sql sql)
{
var pd = PocoData.ForType(typeof(T));
return Execute(new Sql(string.Format("UPDATE {0}", pd.TableName)).Append(sql));
}
public int Delete(string tableName, string primaryKeyName, object poco)
{
return Delete(tableName, primaryKeyName, poco, null);
}
public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
{
// If primary key value not specified, pick it up from the object
if (primaryKeyValue == null)
{
var pd = PocoData.ForType(poco.GetType());
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
primaryKeyValue = pc.PropertyInfo.GetValue(poco, null);
}
}
// Do it
var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", tableName, primaryKeyName);
return Execute(sql, primaryKeyValue);
}
public int Delete(object poco)
{
var pd = PocoData.ForType(poco.GetType());
return Delete(pd.TableName, pd.PrimaryKey, poco);
}
public int Delete<T>(string sql, params object[] args)
{
var pd = PocoData.ForType(typeof(T));
return Execute(string.Format("DELETE FROM {0} {1}", pd.TableName, sql), args);
}
public int Delete<T>(Sql sql)
{
var pd = PocoData.ForType(typeof(T));
return Execute(new Sql(string.Format("DELETE FROM {0}", pd.TableName)).Append(sql));
}
// Check if a poco represents a new record
public bool IsNew(string primaryKeyName, object poco)
{
// If primary key value not specified, pick it up from the object
var pd = PocoData.ForType(poco.GetType());
PropertyInfo pi;
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
pi = pc.PropertyInfo;
}
else
{
pi = poco.GetType().GetProperty(primaryKeyName);
if (pi == null)
throw new ArgumentException("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName);
}
// Get it's value
var pk = pi.GetValue(poco, null);
if (pk == null)
return true;
var type = pk.GetType();
if (type.IsValueType)
{
// Common primary key types
if (type == typeof(long))
return (long)pk == 0;
else if (type == typeof(ulong))
return (ulong)pk == 0;
else if (type == typeof(int))
return (int)pk == 0;
else if (type == typeof(uint))
return (int)pk == 0;
// Create a default instance and compare
return pk == Activator.CreateInstance(pk.GetType());
}
else
{
return pk == null;
}
}
public bool IsNew(object poco)
{
var pd = PocoData.ForType(poco.GetType());
return IsNew(pd.PrimaryKey, poco);
}
// Insert new record or Update existing record
public void Save(string tableName, string primaryKeyName, object poco)
{
if (IsNew(primaryKeyName, poco))
{
Insert(tableName, primaryKeyName, poco);
}
else
{
Update(tableName, primaryKeyName, poco);
}
}
public void Save(object poco)
{
var pd = PocoData.ForType(poco.GetType());
Save(pd.TableName, pd.PrimaryKey, poco);
}
public string LastSQL { get { return _lastSql; } }
public object[] LastArgs { get { return _lastArgs; } }
public string LastCommand
{
get
{
var sb = new StringBuilder();
if (_lastSql == null)
return "";
sb.Append(_lastSql);
if (_lastArgs != null)
{
sb.Append("\r\n\r\n");
for (int i = 0; i < _lastArgs.Length; i++)
{
sb.AppendFormat("{0} - {1}\r\n", i, _lastArgs[i].ToString());
}
}
return sb.ToString();
}
}
public static IMapper Mapper
{
get;
set;
}
internal class PocoColumn
{
public string ColumnName;
public PropertyInfo PropertyInfo;
public bool ResultColumn;
}
internal class PocoData
{
public static PocoData ForType(Type t)
{
lock (m_PocoData)
{
PocoData pd;
if (!m_PocoData.TryGetValue(t, out pd))
{
pd = new PocoData(t);
m_PocoData.Add(t, pd);
}
return pd;
}
}
public PocoData(Type t)
{
// Get the table name
var a = t.GetCustomAttributes(typeof(TableName), true);
var tempTableName = a.Length == 0 ? t.Name : (a[0] as TableName).Value;
// Get the primary key
a = t.GetCustomAttributes(typeof(PrimaryKey), true);
var tempPrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKey).Value;
// Call column mapper
if (Database.Mapper != null)
Database.Mapper.GetTableInfo(t, ref tempTableName, ref tempPrimaryKey);
TableName = tempTableName;
PrimaryKey = tempPrimaryKey;
// Work out bound properties
bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumns), true).Length > 0;
Columns = new Dictionary<string, PocoColumn>(StringComparer.OrdinalIgnoreCase);
foreach (var pi in t.GetProperties())
{
// Work out if properties is to be included
var ColAttrs = pi.GetCustomAttributes(typeof(Column), true);
if (ExplicitColumns)
{
if (ColAttrs.Length == 0)
continue;
}
else
{
if (pi.GetCustomAttributes(typeof(Ignore), true).Length != 0)
continue;
}
var pc = new PocoColumn();
pc.PropertyInfo = pi;
// Work out the DB column name
if (ColAttrs.Length > 0)
{
var colattr = (Column)ColAttrs[0];
pc.ColumnName = colattr.Name;
if ((colattr as ResultColumn) != null)
pc.ResultColumn = true;
}
if (pc.ColumnName == null)
{
pc.ColumnName = pi.Name;
if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(pi, ref pc.ColumnName, ref pc.ResultColumn))
continue;
}
// Store it
Columns.Add(pc.ColumnName, pc);
}
// Build column list for automatic select
QueryColumns = string.Join(", ", (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray());
}
// Create factory function that can convert a IDataReader record into a POCO
public Func<IDataReader, T> GetFactory<T>(string key, bool ForceDateTimesToUtc, IDataReader r)
{
lock (PocoFactories)
{
// Have we already created it?
object factory;
if (PocoFactories.TryGetValue(key, out factory))
return factory as Func<IDataReader, T>;
lock (m_Converters)
{
// Create the method
var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), typeof(T), new Type[] { typeof(IDataReader) }, true);
var il = m.GetILGenerator();
// Running under mono?
int p = (int)Environment.OSVersion.Platform;
bool Mono = (p == 4) || (p == 6) || (p == 128);
// var poco=new T()
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
// Enumerate all fields generating a set assignment for the column
for (int i = 0; i < r.FieldCount; i++)
{
// Get the PocoColumn for this db column, ignore if not known
PocoColumn pc;
if (!Columns.TryGetValue(r.GetName(i), out pc))
continue;
// Get the source type for this column
var srcType = r.GetFieldType(i);
var dstType = pc.PropertyInfo.PropertyType;
// "if (!rdr.IsDBNull(i))"
il.Emit(OpCodes.Ldarg_0); // poco,rdr
il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
var lblNext = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, lblNext); // poco
il.Emit(OpCodes.Dup); // poco,poco
// Do we need to install a converter?
Func<object, object> converter = null;
// Get converter from the mapper
if (Database.Mapper != null)
{
converter = Database.Mapper.GetValueConverter(pc.PropertyInfo, srcType);
}
// Standard DateTime->Utc mapper
if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?)))
{
converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
}
// Forced type conversion
if (converter == null && !dstType.IsAssignableFrom(srcType))
{
converter = delegate(object src) { return Convert.ChangeType(src, dstType, null); };
}
// Fast
bool Handled = false;
if (converter == null)
{
var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
if (valuegetter != null
&& valuegetter.ReturnType == srcType
&& (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
{
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, valuegetter); // *,value
// Mono give IL error if we don't explicitly create Nullable instance for the assignment
if (Mono && Nullable.GetUnderlyingType(dstType) != null)
{
il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
}
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod()); // poco
Handled = true;
}
}
// Not so fast
if (!Handled)
{
// Setup stack for call to converter
int converterIndex = -1;
if (converter != null)
{
// Add the converter
converterIndex = m_Converters.Count;
m_Converters.Add(converter);
// Generate IL to push the converter onto the stack
il.Emit(OpCodes.Ldsfld, fldConverters);
il.Emit(OpCodes.Ldc_I4, converterIndex);
il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter
}
// "value = rdr.GetValue(i)"
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
// Call the converter
if (converter != null)
il.Emit(OpCodes.Callvirt, fnInvoke);
// Assign it
il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod()); // poco
}
il.MarkLabel(lblNext);
}
il.Emit(OpCodes.Ret);
// Cache it, return it
var del = (Func<IDataReader, T>)m.CreateDelegate(typeof(Func<IDataReader, T>));
PocoFactories.Add(key, del);
return del;
}
}
}
static Dictionary<Type, PocoData> m_PocoData = new Dictionary<Type, PocoData>();
static List<Func<object, object>> m_Converters = new List<Func<object, object>>();
static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) });
static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull");
static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
static MethodInfo fnListGetItem = typeof(List<Func<object, object>>).GetProperty("Item").GetGetMethod();
static MethodInfo fnInvoke = typeof(Func<object, object>).GetMethod("Invoke");
public string TableName { get; private set; }
public string PrimaryKey { get; private set; }
public string QueryColumns { get; private set; }
public Dictionary<string, PocoColumn> Columns { get; private set; }
Dictionary<string, object> PocoFactories = new Dictionary<string, object>();
}
// ShareableConnection represents either a shared connection used by a transaction,
// or a one-off connection if not in a transaction.
// Non-shared connections are disposed
class ShareableConnection : IDisposable
{
public ShareableConnection(Database db)
{
_db = db;
_db.OpenSharedConnection();
}
public DbConnection Connection
{
get
{
return _db._sharedConnection;
}
}
Database _db;
public void Dispose()
{
_db.CloseSharedConnection();
}
}
// Member variables
string _connectionString;
string _providerName;
DbProviderFactory _factory;
DbConnection _sharedConnection;
DbTransaction _transaction;
int _sharedConnectionDepth;
int _transactionDepth;
bool _transactionCancelled;
string _lastSql;
object[] _lastArgs;
string _paramPrefix = "@";
}
// Transaction object helps maintain transaction depth counts
public class Transaction : IDisposable
{
public Transaction(Database db)
{
_db = db;
_db.BeginTransaction();
}
public void Complete()
{
_db.CompleteTransaction();
_db = null;
}
public void Dispose()
{
if (_db != null)
_db.AbortTransaction();
}
Database _db;
}
// Simple helper class for building SQL statments
public class Sql
{
public Sql()
{
}
public Sql(string sql, params object[] args)
{
_sql = sql;
_args = args;
}
string _sql;
object[] _args;
Sql _rhs;
string _sqlFinal;
object[] _argsFinal;
void Build()
{
// already built?
if (_sqlFinal != null)
return;
// Build it
var sb = new StringBuilder();
var args = new List<object>();
Build(sb, args, null);
_sqlFinal = sb.ToString();
_argsFinal = args.ToArray();
}
public string SQL
{
get
{
Build();
return _sqlFinal;
}
}
public object[] Arguments
{
get
{
Build();
return _argsFinal;
}
}
public Sql Append(Sql sql)
{
if (_rhs != null)
_rhs.Append(sql);
else
_rhs = sql;
return this;
}
public Sql Append(string sql, params object[] args)
{
return Append(new Sql(sql, args));
}
public Sql Where(string sql, params object[] args)
{
return Append(new Sql("WHERE " + sql, args));
}
public Sql OrderBy(params object[] args)
{
return Append(new Sql("ORDER BY " + String.Join(", ", (from x in args select x.ToString()).ToArray())));
}
public Sql Select(params object[] args)
{
return Append(new Sql("SELECT " + String.Join(", ", (from x in args select x.ToString()).ToArray())));
}
public Sql From(params object[] args)
{
return Append(new Sql("FROM " + String.Join(", ", (from x in args select x.ToString()).ToArray())));
}
static bool Is(Sql sql, string sqltype)
{
return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase);
}
public void Build(StringBuilder sb, List<object> args, Sql lhs)
{
if (!String.IsNullOrEmpty(_sql))
{
// Add SQL to the string
if (sb.Length > 0)
{
sb.Append("\n");
}
var sql = Database.ProcessParams(_sql, _args, args);
if (Is(lhs, "WHERE ") && Is(this, "WHERE "))
sql = "AND " + sql.Substring(6);
if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY "))
sql = ", " + sql.Substring(9);
sb.Append(sql);
}
// Now do rhs
if (_rhs != null)
_rhs.Build(sb, args, this);
}
}
}
...@@ -35,7 +35,11 @@ ...@@ -35,7 +35,11 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="SubSonic.Core">
<HintPath>SubSonic\SubSonic.Core.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data.Entity" /> <Reference Include="System.Data.Entity" />
<Reference Include="System.Data.Linq" /> <Reference Include="System.Data.Linq" />
...@@ -61,6 +65,7 @@ ...@@ -61,6 +65,7 @@
<Compile Include="Linq2Sql\Post.cs" /> <Compile Include="Linq2Sql\Post.cs" />
<Compile Include="Massive\Massive.cs" /> <Compile Include="Massive\Massive.cs" />
<Compile Include="PerformanceTests.cs" /> <Compile Include="PerformanceTests.cs" />
<Compile Include="PetaPoco\PetaPoco.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Settings.Designer.cs"> <Compile Include="Properties\Settings.Designer.cs">
...@@ -69,6 +74,26 @@ ...@@ -69,6 +74,26 @@
<DependentUpon>Settings.settings</DependentUpon> <DependentUpon>Settings.settings</DependentUpon>
</Compile> </Compile>
<Compile Include="SqlMapper.cs" /> <Compile Include="SqlMapper.cs" />
<Compile Include="SubSonic\ActiveRecord.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ActiveRecord.tt</DependentUpon>
</Compile>
<Compile Include="SubSonic\Context.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Context.tt</DependentUpon>
</Compile>
<Compile Include="SubSonic\StoredProcedures.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>StoredProcedures.tt</DependentUpon>
</Compile>
<Compile Include="SubSonic\Structs.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Structs.tt</DependentUpon>
</Compile>
<Compile Include="Tests.cs" /> <Compile Include="Tests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
...@@ -86,15 +111,35 @@ ...@@ -86,15 +111,35 @@
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None> </None>
<None Include="SubSonic\ActiveRecord.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ActiveRecord.cs</LastGenOutput>
</None>
<None Include="SubSonic\Context.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Context.cs</LastGenOutput>
</None>
<None Include="SubSonic\Settings.ttinclude" />
<None Include="SubSonic\SQLServer.ttinclude" />
<None Include="SubSonic\StoredProcedures.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>StoredProcedures.cs</LastGenOutput>
</None>
<None Include="SubSonic\Structs.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Structs.cs</LastGenOutput>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" /> <Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" />
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Linq2Sql\DataClasses.dbml.layout"> <None Include="Linq2Sql\DataClasses.dbml.layout">
<DependentUpon>DataClasses.dbml</DependentUpon> <DependentUpon>DataClasses.dbml</DependentUpon>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using SubSonic.DataProviders;
using SubSonic.Extensions;
using System.Linq.Expressions;
using SubSonic.Schema;
using System.Collections;
using SubSonic;
using SubSonic.Repository;
using System.ComponentModel;
using System.Data.Common;
namespace SubSonic
{
/// <summary>
/// A class which represents the Posts table in the tempdb Database.
/// </summary>
public partial class Post: IActiveRecord
{
#region Built-in testing
static TestRepository<Post> _testRepo;
static void SetTestRepo(){
_testRepo = _testRepo ?? new TestRepository<Post>(new SubSonic.tempdbDB());
}
public static void ResetTestRepo(){
_testRepo = null;
SetTestRepo();
}
public static void Setup(List<Post> testlist){
SetTestRepo();
foreach (var item in testlist)
{
_testRepo._items.Add(item);
}
}
public static void Setup(Post item) {
SetTestRepo();
_testRepo._items.Add(item);
}
public static void Setup(int testItems) {
SetTestRepo();
for(int i=0;i<testItems;i++){
Post item=new Post();
_testRepo._items.Add(item);
}
}
public bool TestMode = false;
#endregion
IRepository<Post> _repo;
ITable tbl;
bool _isNew;
public bool IsNew(){
return _isNew;
}
public void SetIsLoaded(bool isLoaded){
_isLoaded=isLoaded;
if(isLoaded)
OnLoaded();
}
public void SetIsNew(bool isNew){
_isNew=isNew;
}
bool _isLoaded;
public bool IsLoaded(){
return _isLoaded;
}
List<IColumn> _dirtyColumns;
public bool IsDirty(){
return _dirtyColumns.Count>0;
}
public List<IColumn> GetDirtyColumns (){
return _dirtyColumns;
}
SubSonic.tempdbDB _db;
public Post(string connectionString, string providerName) {
_db=new SubSonic.tempdbDB(connectionString, providerName);
Init();
}
void Init(){
TestMode=this._db.DataProvider.ConnectionString.Equals("test", StringComparison.InvariantCultureIgnoreCase);
_dirtyColumns=new List<IColumn>();
if(TestMode){
Post.SetTestRepo();
_repo=_testRepo;
}else{
_repo = new SubSonicRepository<Post>(_db);
}
tbl=_repo.GetTable();
SetIsNew(true);
OnCreated();
}
public Post(){
_db=new SubSonic.tempdbDB();
Init();
}
partial void OnCreated();
partial void OnLoaded();
partial void OnSaved();
partial void OnChanged();
public IList<IColumn> Columns{
get{
return tbl.Columns;
}
}
public Post(Expression<Func<Post, bool>> expression):this() {
SetIsLoaded(_repo.Load(this,expression));
}
internal static IRepository<Post> GetRepo(string connectionString, string providerName){
SubSonic.tempdbDB db;
if(String.IsNullOrEmpty(connectionString)){
db=new SubSonic.tempdbDB();
}else{
db=new SubSonic.tempdbDB(connectionString, providerName);
}
IRepository<Post> _repo;
if(db.TestMode){
Post.SetTestRepo();
_repo=_testRepo;
}else{
_repo = new SubSonicRepository<Post>(db);
}
return _repo;
}
internal static IRepository<Post> GetRepo(){
return GetRepo("","");
}
public static Post SingleOrDefault(Expression<Func<Post, bool>> expression) {
var repo = GetRepo();
var results=repo.Find(expression);
Post single=null;
if(results.Count() > 0){
single=results.ToList()[0];
single.OnLoaded();
single.SetIsLoaded(true);
single.SetIsNew(false);
}
return single;
}
public static Post SingleOrDefault(Expression<Func<Post, bool>> expression,string connectionString, string providerName) {
var repo = GetRepo(connectionString,providerName);
var results=repo.Find(expression);
Post single=null;
if(results.Count() > 0){
single=results.ToList()[0];
}
return single;
}
public static bool Exists(Expression<Func<Post, bool>> expression,string connectionString, string providerName) {
return All(connectionString,providerName).Any(expression);
}
public static bool Exists(Expression<Func<Post, bool>> expression) {
return All().Any(expression);
}
public static IList<Post> Find(Expression<Func<Post, bool>> expression) {
var repo = GetRepo();
return repo.Find(expression).ToList();
}
public static IList<Post> Find(Expression<Func<Post, bool>> expression,string connectionString, string providerName) {
var repo = GetRepo(connectionString,providerName);
return repo.Find(expression).ToList();
}
public static IQueryable<Post> All(string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetAll();
}
public static IQueryable<Post> All() {
return GetRepo().GetAll();
}
public static PagedList<Post> GetPaged(string sortBy, int pageIndex, int pageSize,string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetPaged(sortBy, pageIndex, pageSize);
}
public static PagedList<Post> GetPaged(string sortBy, int pageIndex, int pageSize) {
return GetRepo().GetPaged(sortBy, pageIndex, pageSize);
}
public static PagedList<Post> GetPaged(int pageIndex, int pageSize,string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetPaged(pageIndex, pageSize);
}
public static PagedList<Post> GetPaged(int pageIndex, int pageSize) {
return GetRepo().GetPaged(pageIndex, pageSize);
}
public string KeyName()
{
return "Id";
}
public object KeyValue()
{
return this.Id;
}
public void SetKeyValue(object value) {
if (value != null && value!=DBNull.Value) {
var settable = value.ChangeTypeTo<int>();
this.GetType().GetProperty(this.KeyName()).SetValue(this, settable, null);
}
}
public override string ToString(){
return this.Text.ToString();
}
public override bool Equals(object obj){
if(obj.GetType()==typeof(Post)){
Post compare=(Post)obj;
return compare.KeyValue()==this.KeyValue();
}else{
return base.Equals(obj);
}
}
public override int GetHashCode() {
return this.Id;
}
public string DescriptorValue()
{
return this.Text.ToString();
}
public string DescriptorColumn() {
return "Text";
}
public static string GetKeyColumn()
{
return "Id";
}
public static string GetDescriptorColumn()
{
return "Text";
}
#region ' Foreign Keys '
#endregion
int _Id;
public int Id
{
get { return _Id; }
set
{
if(_Id!=value){
_Id=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Id");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
string _Text;
public string Text
{
get { return _Text; }
set
{
if(_Text!=value){
_Text=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Text");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
DateTime _CreationDate;
public DateTime CreationDate
{
get { return _CreationDate; }
set
{
if(_CreationDate!=value){
_CreationDate=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="CreationDate");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
DateTime _LastChangeDate;
public DateTime LastChangeDate
{
get { return _LastChangeDate; }
set
{
if(_LastChangeDate!=value){
_LastChangeDate=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="LastChangeDate");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter1;
public int? Counter1
{
get { return _Counter1; }
set
{
if(_Counter1!=value){
_Counter1=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter1");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter2;
public int? Counter2
{
get { return _Counter2; }
set
{
if(_Counter2!=value){
_Counter2=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter2");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter3;
public int? Counter3
{
get { return _Counter3; }
set
{
if(_Counter3!=value){
_Counter3=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter3");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter4;
public int? Counter4
{
get { return _Counter4; }
set
{
if(_Counter4!=value){
_Counter4=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter4");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter5;
public int? Counter5
{
get { return _Counter5; }
set
{
if(_Counter5!=value){
_Counter5=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter5");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter6;
public int? Counter6
{
get { return _Counter6; }
set
{
if(_Counter6!=value){
_Counter6=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter6");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter7;
public int? Counter7
{
get { return _Counter7; }
set
{
if(_Counter7!=value){
_Counter7=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter7");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter8;
public int? Counter8
{
get { return _Counter8; }
set
{
if(_Counter8!=value){
_Counter8=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter8");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
int? _Counter9;
public int? Counter9
{
get { return _Counter9; }
set
{
if(_Counter9!=value){
_Counter9=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="Counter9");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
public DbCommand GetUpdateCommand() {
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToUpdateQuery(_db.Provider).GetCommand().ToDbCommand();
}
public DbCommand GetInsertCommand() {
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToInsertQuery(_db.Provider).GetCommand().ToDbCommand();
}
public DbCommand GetDeleteCommand() {
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToDeleteQuery(_db.Provider).GetCommand().ToDbCommand();
}
public void Update(){
Update(_db.DataProvider);
}
public void Update(IDataProvider provider){
if(this._dirtyColumns.Count>0){
_repo.Update(this,provider);
_dirtyColumns.Clear();
}
OnSaved();
}
public void Add(){
Add(_db.DataProvider);
}
public void Add(IDataProvider provider){
var key=KeyValue();
if(key==null){
var newKey=_repo.Add(this,provider);
this.SetKeyValue(newKey);
}else{
_repo.Add(this,provider);
}
SetIsNew(false);
OnSaved();
}
public void Save() {
Save(_db.DataProvider);
}
public void Save(IDataProvider provider) {
if (_isNew) {
Add(provider);
} else {
Update(provider);
}
}
public void Delete(IDataProvider provider) {
_repo.Delete(KeyValue());
}
public void Delete() {
Delete(_db.DataProvider);
}
public static void Delete(Expression<Func<Post, bool>> expression) {
var repo = GetRepo();
repo.DeleteMany(expression);
}
public void Load(IDataReader rdr) {
Load(rdr, true);
}
public void Load(IDataReader rdr, bool closeReader) {
if (rdr.Read()) {
try {
rdr.Load(this);
SetIsNew(false);
SetIsLoaded(true);
} catch {
SetIsLoaded(false);
throw;
}
}else{
SetIsLoaded(false);
}
if (closeReader)
rdr.Dispose();
}
}
}
<#@ include file="SQLServer.ttinclude" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using SubSonic.DataProviders;
using SubSonic.Extensions;
using System.Linq.Expressions;
using SubSonic.Schema;
using System.Collections;
using SubSonic;
using SubSonic.Repository;
using System.ComponentModel;
using System.Data.Common;
namespace <#=Namespace #>
{
<#
var tables = LoadTables();
foreach(Table tbl in tables)
{
if(!ExcludeTables.Contains(tbl.Name))
{
#>
/// <summary>
/// A class which represents the <#=tbl.Name #> table in the <#=DatabaseName#> Database.
/// </summary>
public partial class <#=tbl.ClassName#>: IActiveRecord
{
#region Built-in testing
static TestRepository<<#=tbl.ClassName#>> _testRepo;
static void SetTestRepo(){
_testRepo = _testRepo ?? new TestRepository<<#=tbl.ClassName#>>(new <#=Namespace#>.<#=DatabaseName#>DB());
}
public static void ResetTestRepo(){
_testRepo = null;
SetTestRepo();
}
public static void Setup(List<<#=tbl.ClassName#>> testlist){
SetTestRepo();
foreach (var item in testlist)
{
_testRepo._items.Add(item);
}
}
public static void Setup(<#=tbl.ClassName#> item) {
SetTestRepo();
_testRepo._items.Add(item);
}
public static void Setup(int testItems) {
SetTestRepo();
for(int i=0;i<testItems;i++){
<#=tbl.ClassName#> item=new <#=tbl.ClassName#>();
_testRepo._items.Add(item);
}
}
public bool TestMode = false;
#endregion
IRepository<<#=tbl.ClassName#>> _repo;
ITable tbl;
bool _isNew;
public bool IsNew(){
return _isNew;
}
public void SetIsLoaded(bool isLoaded){
_isLoaded=isLoaded;
if(isLoaded)
OnLoaded();
}
public void SetIsNew(bool isNew){
_isNew=isNew;
}
bool _isLoaded;
public bool IsLoaded(){
return _isLoaded;
}
List<IColumn> _dirtyColumns;
public bool IsDirty(){
return _dirtyColumns.Count>0;
}
public List<IColumn> GetDirtyColumns (){
return _dirtyColumns;
}
<#=Namespace#>.<#=DatabaseName#>DB _db;
public <#=tbl.ClassName#>(string connectionString, string providerName) {
_db=new <#=Namespace#>.<#=DatabaseName#>DB(connectionString, providerName);
Init();
}
void Init(){
TestMode=this._db.DataProvider.ConnectionString.Equals("test", StringComparison.InvariantCultureIgnoreCase);
_dirtyColumns=new List<IColumn>();
if(TestMode){
<#=tbl.ClassName#>.SetTestRepo();
_repo=_testRepo;
}else{
_repo = new SubSonicRepository<<#=tbl.ClassName#>>(_db);
}
tbl=_repo.GetTable();
SetIsNew(true);
OnCreated();
}
public <#=tbl.ClassName#>(){
_db=new <#=Namespace#>.<#=DatabaseName#>DB();
Init();
}
partial void OnCreated();
partial void OnLoaded();
partial void OnSaved();
partial void OnChanged();
public IList<IColumn> Columns{
get{
return tbl.Columns;
}
}
public <#=tbl.ClassName#>(Expression<Func<<#=tbl.ClassName#>, bool>> expression):this() {
SetIsLoaded(_repo.Load(this,expression));
}
internal static IRepository<<#=tbl.ClassName#>> GetRepo(string connectionString, string providerName){
<#=Namespace#>.<#=DatabaseName#>DB db;
if(String.IsNullOrEmpty(connectionString)){
db=new <#=Namespace#>.<#=DatabaseName#>DB();
}else{
db=new <#=Namespace#>.<#=DatabaseName#>DB(connectionString, providerName);
}
IRepository<<#=tbl.ClassName#>> _repo;
if(db.TestMode){
<#=tbl.ClassName#>.SetTestRepo();
_repo=_testRepo;
}else{
_repo = new SubSonicRepository<<#=tbl.ClassName#>>(db);
}
return _repo;
}
internal static IRepository<<#=tbl.ClassName#>> GetRepo(){
return GetRepo("","");
}
public static <#=tbl.ClassName#> SingleOrDefault(Expression<Func<<#=tbl.ClassName#>, bool>> expression) {
var repo = GetRepo();
var results=repo.Find(expression);
<#=tbl.ClassName#> single=null;
if(results.Count() > 0){
single=results.ToList()[0];
single.OnLoaded();
single.SetIsLoaded(true);
single.SetIsNew(false);
}
return single;
}
public static <#=tbl.ClassName#> SingleOrDefault(Expression<Func<<#=tbl.ClassName#>, bool>> expression,string connectionString, string providerName) {
var repo = GetRepo(connectionString,providerName);
var results=repo.Find(expression);
<#=tbl.ClassName#> single=null;
if(results.Count() > 0){
single=results.ToList()[0];
}
return single;
}
public static bool Exists(Expression<Func<<#=tbl.ClassName#>, bool>> expression,string connectionString, string providerName) {
return All(connectionString,providerName).Any(expression);
}
public static bool Exists(Expression<Func<<#=tbl.ClassName#>, bool>> expression) {
return All().Any(expression);
}
public static IList<<#=tbl.ClassName#>> Find(Expression<Func<<#=tbl.ClassName#>, bool>> expression) {
var repo = GetRepo();
return repo.Find(expression).ToList();
}
public static IList<<#=tbl.ClassName#>> Find(Expression<Func<<#=tbl.ClassName#>, bool>> expression,string connectionString, string providerName) {
var repo = GetRepo(connectionString,providerName);
return repo.Find(expression).ToList();
}
public static IQueryable<<#=tbl.ClassName#>> All(string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetAll();
}
public static IQueryable<<#=tbl.ClassName#>> All() {
return GetRepo().GetAll();
}
public static PagedList<<#=tbl.ClassName#>> GetPaged(string sortBy, int pageIndex, int pageSize,string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetPaged(sortBy, pageIndex, pageSize);
}
public static PagedList<<#=tbl.ClassName#>> GetPaged(string sortBy, int pageIndex, int pageSize) {
return GetRepo().GetPaged(sortBy, pageIndex, pageSize);
}
public static PagedList<<#=tbl.ClassName#>> GetPaged(int pageIndex, int pageSize,string connectionString, string providerName) {
return GetRepo(connectionString,providerName).GetPaged(pageIndex, pageSize);
}
public static PagedList<<#=tbl.ClassName#>> GetPaged(int pageIndex, int pageSize) {
return GetRepo().GetPaged(pageIndex, pageSize);
}
public string KeyName()
{
return "<#=tbl.PK.CleanName #>";
}
public object KeyValue()
{
return this.<#=tbl.PK.CleanName#>;
}
public void SetKeyValue(object value) {
if (value != null && value!=DBNull.Value) {
var settable = value.ChangeTypeTo<<#=tbl.PK.SysType#>>();
this.GetType().GetProperty(this.KeyName()).SetValue(this, settable, null);
}
}
public override string ToString(){
<# if (tbl.ClassName == tbl.Descriptor.CleanName){ #>
return this.<#=tbl.Descriptor.CleanName+"X" #>.ToString();
<# } else { #>
return this.<#=tbl.Descriptor.CleanName #>.ToString();
<# } #>
}
public override bool Equals(object obj){
if(obj.GetType()==typeof(<#=tbl.ClassName#>)){
<#=tbl.ClassName#> compare=(<#=tbl.ClassName#>)obj;
return compare.KeyValue()==this.KeyValue();
}else{
return base.Equals(obj);
}
}
<# if(tbl.PK.SysType=="int"){#>
public override int GetHashCode() {
return this.<#=tbl.PK.CleanName #>;
}
<# }#>
public string DescriptorValue()
{
<# if (tbl.ClassName == tbl.Descriptor.CleanName){ #>
return this.<#=tbl.Descriptor.CleanName+"X" #>.ToString();
<# } else { #>
return this.<#=tbl.Descriptor.CleanName #>.ToString();
<# } #>
}
public string DescriptorColumn() {
return "<#=tbl.Descriptor.CleanName #>";
}
public static string GetKeyColumn()
{
return "<#=tbl.PK.CleanName #>";
}
public static string GetDescriptorColumn()
{
return "<#=tbl.Descriptor.CleanName #>";
}
#region ' Foreign Keys '
<#
List<string> fkCreated = new List<string>();
foreach(FKTable fk in tbl.FKTables)
{
if(!ExcludeTables.Contains(fk.OtherTable)){
string propName=fk.OtherQueryable;
if(fkCreated.Contains(propName))
{
propName=fk.OtherQueryable+fkCreated.Count.ToString();
}
fkCreated.Add(fk.OtherQueryable);
#>
public IQueryable<<#=fk.OtherClass #>> <#=propName #>
{
get
{
var repo=<#=Namespace #>.<#=fk.OtherClass#>.GetRepo();
return from items in repo.GetAll()
where items.<#=CleanUp(fk.OtherColumn)#> == _<#=CleanUp(fk.ThisColumn)#>
select items;
}
}
<#
}
}
#>
#endregion
<#
foreach(Column col in tbl.Columns)
{
if (tbl.ClassName == col.CleanName)
{
col.CleanName += "X";
}
#>
<#=col.SysType #><#=CheckNullable(col)#> _<#=col.CleanName #>;
public <#=col.SysType #><#=CheckNullable(col)#> <#=col.CleanName #>
{
get { return _<#=col.CleanName #>; }
set
{
if(_<#=col.CleanName #>!=value){
_<#=col.CleanName #>=value;
var col=tbl.Columns.SingleOrDefault(x=>x.Name=="<#=col.Name #>");
if(col!=null){
if(!_dirtyColumns.Any(x=>x.Name==col.Name) && _isLoaded){
_dirtyColumns.Add(col);
}
}
OnChanged();
}
}
}
<#
}
#>
public DbCommand GetUpdateCommand() {
<#if(tbl.Columns.Any(x=>x.Name.ToLower()=="modifiedon")){#>
if (!_dirtyColumns.Any(x => x.Name.ToLower() == "modifiedon")) {
this.<#=tbl.Columns.Single(x=>x.Name.ToLower()=="modifiedon").CleanName#>=<#=DatabaseName#>DB.DateTimeNowTruncatedDownToSecond();
}
<#}#>
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToUpdateQuery(_db.Provider).GetCommand().ToDbCommand();
}
public DbCommand GetInsertCommand() {
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToInsertQuery(_db.Provider).GetCommand().ToDbCommand();
}
public DbCommand GetDeleteCommand() {
if(TestMode)
return _db.DataProvider.CreateCommand();
else
return this.ToDeleteQuery(_db.Provider).GetCommand().ToDbCommand();
}
public void Update(){
Update(_db.DataProvider);
}
public void Update(IDataProvider provider){
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedBy")){#>
if(String.IsNullOrEmpty(this.ModifiedBy))
this.ModifiedBy=Environment.UserName;
<#}#>
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedOn")){#>
this.ModifiedOn=<#=DatabaseName#>DB.DateTimeNowTruncatedDownToSecond();
<#}#>
if(this._dirtyColumns.Count>0){
_repo.Update(this,provider);
_dirtyColumns.Clear();
}
OnSaved();
}
public void Add(){
Add(_db.DataProvider);
}
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedBy")){#>
public void Update(string username){
this.ModifiedBy=username;
Update();
}
public void Update(string username, IDataProvider provider){
this.ModifiedBy=username;
Update(provider);
}
<#}#>
public void Add(IDataProvider provider){
<#if(tbl.Columns.Any(x=>x.Name=="CreatedOn")){#>
this.CreatedOn=<#=DatabaseName#>DB.DateTimeNowTruncatedDownToSecond();
<#}#>
<#if(tbl.Columns.Any(x=>x.Name=="CreatedBy")){#>
if(String.IsNullOrEmpty(this.CreatedBy))
this.CreatedBy=Environment.UserName;
<#}#>
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedOn")){#>
this.ModifiedOn=<#=DatabaseName#>DB.DateTimeNowTruncatedDownToSecond();
<#}#>
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedBy")){#>
if(String.IsNullOrEmpty(this.ModifiedBy))
this.ModifiedBy=Environment.UserName;
<#}#>
var key=KeyValue();
if(key==null){
var newKey=_repo.Add(this,provider);
this.SetKeyValue(newKey);
}else{
_repo.Add(this,provider);
}
SetIsNew(false);
OnSaved();
}
<#if(tbl.Columns.Any(x=>x.Name=="CreatedBy")){#>
public void Add(string username){
this.CreatedBy=username;
Add();
}
public void Add(string username, IDataProvider provider){
this.CreatedBy=username;
Add(provider);
}
<#}#>
public void Save() {
Save(_db.DataProvider);
}
public void Save(IDataProvider provider) {
if (_isNew) {
Add(provider);
} else {
Update(provider);
}
}
<#if(tbl.Columns.Any(x=>x.Name=="CreatedBy" || x.Name=="ModifiedBy")){#>
public void Save(string username, IDataProvider provider) {
if (_isNew) {
<#if(tbl.Columns.Any(x=>x.Name=="CreatedBy")){#>
Add(username,provider);
<#}else{#>
Add(provider);
<#}#>
} else {
<#if(tbl.Columns.Any(x=>x.Name=="ModifiedBy")){#>
Update(username,provider);
<#}else{#>
Update(provider);
<#}#>
}
}
<#}#>
public void Delete(IDataProvider provider) {
<#if(tbl.HasLogicalDelete()){#>
this.<#=tbl.DeleteColumn.CleanName#>=true;
_repo.Update(this,provider);
<#}else{#>
_repo.Delete(KeyValue());
<#}#>
}
public void Delete() {
Delete(_db.DataProvider);
}
public static void Delete(Expression<Func<<#=tbl.ClassName#>, bool>> expression) {
var repo = GetRepo();
<#if(tbl.HasLogicalDelete()){#>
List<<#=tbl.ClassName#>> items=repo.GetAll().Where(expression).ToList();
items.ForEach(x=>x.<#=tbl.DeleteColumn.CleanName#>=true);
repo.Update(items);
<#}else{#>
repo.DeleteMany(expression);
<#}#>
}
<#if(tbl.HasLogicalDelete()){#>
public static void Destroy(Func<<#=tbl.ClassName#>, bool> expression) {
var repo = GetRepo();
repo.Delete(expression);
}
public static void Destroy(object key) {
var repo = GetRepo();
repo.Delete(key);
}
public static void Destroy(object key, IDataProvider provider) {
var repo = GetRepo();
repo.Delete(key,provider);
}
public void Destroy() {
_repo.Delete(KeyValue());
}
public void Destroy(IDataProvider provider) {
_repo.Delete(KeyValue(), provider);
}
<#}#>
public void Load(IDataReader rdr) {
Load(rdr, true);
}
public void Load(IDataReader rdr, bool closeReader) {
if (rdr.Read()) {
try {
rdr.Load(this);
SetIsNew(false);
SetIsLoaded(true);
} catch {
SetIsLoaded(false);
throw;
}
}else{
SetIsLoaded(false);
}
if (closeReader)
rdr.Dispose();
}
}
<# }
}
#>
}
using System;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using SubSonic.DataProviders;
using SubSonic.Extensions;
using SubSonic.Linq.Structure;
using SubSonic.Query;
using SubSonic.Schema;
using System.Data.Common;
using System.Collections.Generic;
namespace SubSonic
{
public partial class tempdbDB : IQuerySurface
{
public IDataProvider DataProvider;
public DbQueryProvider provider;
public static IDataProvider DefaultDataProvider { get; set; }
public bool TestMode
{
get
{
return DataProvider.ConnectionString.Equals("test", StringComparison.InvariantCultureIgnoreCase);
}
}
public tempdbDB()
{
if (DefaultDataProvider == null) {
DataProvider = ProviderFactory.GetProvider("Smackdown.Properties.Settings.tempdbConnectionString");
}
else {
DataProvider = DefaultDataProvider;
}
Init();
}
public tempdbDB(string connectionStringName)
{
DataProvider = ProviderFactory.GetProvider(connectionStringName);
Init();
}
public tempdbDB(string connectionString, string providerName)
{
DataProvider = ProviderFactory.GetProvider(connectionString,providerName);
Init();
}
public ITable FindByPrimaryKey(string pkName)
{
return DataProvider.Schema.Tables.SingleOrDefault(x => x.PrimaryKey.Name.Equals(pkName, StringComparison.InvariantCultureIgnoreCase));
}
public Query<T> GetQuery<T>()
{
return new Query<T>(provider);
}
public ITable FindTable(string tableName)
{
return DataProvider.FindTable(tableName);
}
public IDataProvider Provider
{
get { return DataProvider; }
set {DataProvider=value;}
}
public DbQueryProvider QueryProvider
{
get { return provider; }
}
BatchQuery _batch = null;
public void Queue<T>(IQueryable<T> qry)
{
if (_batch == null)
_batch = new BatchQuery(Provider, QueryProvider);
_batch.Queue(qry);
}
public void Queue(ISqlQuery qry)
{
if (_batch == null)
_batch = new BatchQuery(Provider, QueryProvider);
_batch.Queue(qry);
}
public void ExecuteTransaction(IList<DbCommand> commands)
{
if(!TestMode)
{
using(var connection = commands[0].Connection)
{
if (connection.State == ConnectionState.Closed)
connection.Open();
using (var trans = connection.BeginTransaction())
{
foreach (var cmd in commands)
{
cmd.Transaction = trans;
cmd.Connection = connection;
cmd.ExecuteNonQuery();
}
trans.Commit();
}
connection.Close();
}
}
}
public IDataReader ExecuteBatch()
{
if (_batch == null)
throw new InvalidOperationException("There's nothing in the queue");
if(!TestMode)
return _batch.ExecuteReader();
return null;
}
public Query<Post> Posts { get; set; }
#region ' Aggregates and SubSonic Queries '
public Select SelectColumns(params string[] columns)
{
return new Select(DataProvider, columns);
}
public Select Select
{
get { return new Select(this.Provider); }
}
public Insert Insert
{
get { return new Insert(this.Provider); }
}
public Update<T> Update<T>() where T:new()
{
return new Update<T>(this.Provider);
}
public SqlQuery Delete<T>(Expression<Func<T,bool>> column) where T:new()
{
LambdaExpression lamda = column;
SqlQuery result = new Delete<T>(this.Provider);
result = result.From<T>();
result.Constraints=lamda.ParseConstraints().ToList();
return result;
}
public SqlQuery Max<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = DataProvider.FindTable(objectName).Name;
return new Select(DataProvider, new Aggregate(colName, AggregateFunction.Max)).From(tableName);
}
public SqlQuery Min<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Min)).From(tableName);
}
public SqlQuery Sum<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Sum)).From(tableName);
}
public SqlQuery Avg<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Avg)).From(tableName);
}
public SqlQuery Count<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Count)).From(tableName);
}
public SqlQuery Variance<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Var)).From(tableName);
}
public SqlQuery StandardDeviation<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.StDev)).From(tableName);
}
#endregion
void Init()
{
provider = new DbQueryProvider(this.Provider);
#region ' Query Defs '
Posts = new Query<Post>(provider);
#endregion
#region ' Schemas '
if(DataProvider.Schema.Tables.Count == 0)
{
DataProvider.Schema.Tables.Add(new PostsTable(DataProvider));
}
#endregion
}
#region ' Helpers '
internal static DateTime DateTimeNowTruncatedDownToSecond() {
var now = DateTime.Now;
return now.AddTicks(-now.Ticks % TimeSpan.TicksPerSecond);
}
#endregion
}
}
\ No newline at end of file
<#@ template language="C#v3.5" debug="False" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ include file="SQLServer.ttinclude" #>
<#
var tables = LoadTables();
#>
using System;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using SubSonic.DataProviders;
using SubSonic.Extensions;
using SubSonic.Linq.Structure;
using SubSonic.Query;
using SubSonic.Schema;
using System.Data.Common;
using System.Collections.Generic;
namespace <#=Namespace#>
{
public partial class <#=DatabaseName#>DB : IQuerySurface
{
public IDataProvider DataProvider;
public DbQueryProvider provider;
public static IDataProvider DefaultDataProvider { get; set; }
public bool TestMode
{
get
{
return DataProvider.ConnectionString.Equals("test", StringComparison.InvariantCultureIgnoreCase);
}
}
public <#=DatabaseName#>DB()
{
if (DefaultDataProvider == null) {
DataProvider = ProviderFactory.GetProvider("<#=ConnectionStringName#>");
}
else {
DataProvider = DefaultDataProvider;
}
Init();
}
public <#=DatabaseName#>DB(string connectionStringName)
{
DataProvider = ProviderFactory.GetProvider(connectionStringName);
Init();
}
public <#=DatabaseName#>DB(string connectionString, string providerName)
{
DataProvider = ProviderFactory.GetProvider(connectionString,providerName);
Init();
}
public ITable FindByPrimaryKey(string pkName)
{
return DataProvider.Schema.Tables.SingleOrDefault(x => x.PrimaryKey.Name.Equals(pkName, StringComparison.InvariantCultureIgnoreCase));
}
public Query<T> GetQuery<T>()
{
return new Query<T>(provider);
}
public ITable FindTable(string tableName)
{
return DataProvider.FindTable(tableName);
}
public IDataProvider Provider
{
get { return DataProvider; }
set {DataProvider=value;}
}
public DbQueryProvider QueryProvider
{
get { return provider; }
}
BatchQuery _batch = null;
public void Queue<T>(IQueryable<T> qry)
{
if (_batch == null)
_batch = new BatchQuery(Provider, QueryProvider);
_batch.Queue(qry);
}
public void Queue(ISqlQuery qry)
{
if (_batch == null)
_batch = new BatchQuery(Provider, QueryProvider);
_batch.Queue(qry);
}
public void ExecuteTransaction(IList<DbCommand> commands)
{
if(!TestMode)
{
using(var connection = commands[0].Connection)
{
if (connection.State == ConnectionState.Closed)
connection.Open();
using (var trans = connection.BeginTransaction())
{
foreach (var cmd in commands)
{
cmd.Transaction = trans;
cmd.Connection = connection;
cmd.ExecuteNonQuery();
}
trans.Commit();
}
connection.Close();
}
}
}
public IDataReader ExecuteBatch()
{
if (_batch == null)
throw new InvalidOperationException("There's nothing in the queue");
if(!TestMode)
return _batch.ExecuteReader();
return null;
}
<# //################################################ IQueryable ####################################### #>
<# foreach(Table tbl in tables){
if(!ExcludeTables.Contains(tbl.Name))
{
#>
public Query<<#=tbl.ClassName#>> <#=tbl.QueryableName#> { get; set; }
<#
}
}
#>
<# //################################################ Aggregates and Queries ####################################### #>
#region ' Aggregates and SubSonic Queries '
public Select SelectColumns(params string[] columns)
{
return new Select(DataProvider, columns);
}
public Select Select
{
get { return new Select(this.Provider); }
}
public Insert Insert
{
get { return new Insert(this.Provider); }
}
public Update<T> Update<T>() where T:new()
{
return new Update<T>(this.Provider);
}
public SqlQuery Delete<T>(Expression<Func<T,bool>> column) where T:new()
{
LambdaExpression lamda = column;
SqlQuery result = new Delete<T>(this.Provider);
result = result.From<T>();
result.Constraints=lamda.ParseConstraints().ToList();
return result;
}
public SqlQuery Max<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = DataProvider.FindTable(objectName).Name;
return new Select(DataProvider, new Aggregate(colName, AggregateFunction.Max)).From(tableName);
}
public SqlQuery Min<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Min)).From(tableName);
}
public SqlQuery Sum<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Sum)).From(tableName);
}
public SqlQuery Avg<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Avg)).From(tableName);
}
public SqlQuery Count<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Count)).From(tableName);
}
public SqlQuery Variance<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.Var)).From(tableName);
}
public SqlQuery StandardDeviation<T>(Expression<Func<T,object>> column)
{
LambdaExpression lamda = column;
string colName = lamda.ParseObjectValue();
string objectName = typeof(T).Name;
string tableName = this.Provider.FindTable(objectName).Name;
return new Select(this.Provider, new Aggregate(colName, AggregateFunction.StDev)).From(tableName);
}
#endregion
void Init()
{
provider = new DbQueryProvider(this.Provider);
<#
//################################################ QUERIES ####################################### #>
#region ' Query Defs '
<#
foreach(Table tbl in tables)
{
if(!ExcludeTables.Contains(tbl.Name))
{
#>
<#=tbl.QueryableName#> = new Query<<#=tbl.ClassName#>>(provider);
<#
}
#>
<#
}
#>
#endregion
<#//################################################ SCHEMAS ####################################### #>
#region ' Schemas '
if(DataProvider.Schema.Tables.Count == 0)
{
<#
foreach(Table tbl in tables)
{
if(!ExcludeTables.Contains(tbl.Name))
{
#>
DataProvider.Schema.Tables.Add(new <#=tbl.CleanName#>Table(DataProvider));
<#
}
}
#>
}
#endregion
}
<#//################################################ HELPERS ####################################### #>
#region ' Helpers '
internal static DateTime DateTimeNowTruncatedDownToSecond() {
var now = DateTime.Now;
return now.AddTicks(-now.Ticks % TimeSpan.TicksPerSecond);
}
#endregion
}
}
\ No newline at end of file
<#@ include file="Settings.ttinclude" #>
<#+
IDataReader GetReader(string sql){
SqlConnection conn=new SqlConnection(ConnectionString);
SqlCommand cmd=new SqlCommand(sql,conn);
conn.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
SqlCommand GetCommand(string sql){
SqlConnection conn=new SqlConnection(ConnectionString);
SqlCommand cmd=new SqlCommand(sql,conn);
conn.Open();
return cmd;
}
const string FKSql=@"SELECT
ThisTable = FK.TABLE_NAME,
ThisColumn = CU.COLUMN_NAME,
OtherTable = PK.TABLE_NAME,
OtherColumn = PT.COLUMN_NAME,
Constraint_Name = C.CONSTRAINT_NAME,
Owner = FK.TABLE_SCHEMA
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
INNER JOIN
(
SELECT i1.TABLE_NAME, i2.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
)
PT ON PT.TABLE_NAME = PK.TABLE_NAME
WHERE FK.Table_NAME=@tableName OR PK.Table_NAME=@tableName";
const string TABLE_SQL=@"SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE='BASE TABLE'";
const string COLUMN_SQL=@"SELECT
TABLE_CATALOG AS [Database],
TABLE_SCHEMA AS Owner,
TABLE_NAME AS TableName,
COLUMN_NAME AS ColumnName,
ORDINAL_POSITION AS OrdinalPosition,
COLUMN_DEFAULT AS DefaultSetting,
IS_NULLABLE AS IsNullable, DATA_TYPE AS DataType,
CHARACTER_MAXIMUM_LENGTH AS MaxLength,
DATETIME_PRECISION AS DatePrecision,
COLUMNPROPERTY(object_id('[' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']'), COLUMN_NAME, 'IsIdentity') AS IsIdentity,
COLUMNPROPERTY(object_id('[' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']'), COLUMN_NAME, 'IsComputed') as IsComputed
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=@tableName
ORDER BY OrdinalPosition ASC";
List<SPParam> GetSPParams(string spName){
var result=new List<SPParam>();
string dbName = null;
if(!String.IsNullOrEmpty(DatabaseName))
dbName = DatabaseName;
string[] restrictions = new string[4] { dbName , null, spName, null };
using(SqlConnection conn=new SqlConnection(ConnectionString)){
conn.Open();
var sprocs=conn.GetSchema("ProcedureParameters", restrictions);
conn.Close();
foreach(DataRow row in sprocs.Select("", "ORDINAL_POSITION")){
SPParam p=new SPParam();
p.SysType=GetSysType(row["DATA_TYPE"].ToString());
p.DbType=GetDbType(row["DATA_TYPE"].ToString()).ToString();
p.Name=row["PARAMETER_NAME"].ToString().Replace("@","");
p.CleanName=CleanUp(p.Name);
result.Add(p);
}
}
return result;
}
List<SP> GetSPs(){
var result=new List<SP>();
//pull the SPs
DataTable sprocs=null;
DataTable parameters=null;
using(SqlConnection conn=new SqlConnection(ConnectionString)){
conn.Open();
sprocs=conn.GetSchema("Procedures");
conn.Close();
}
foreach(DataRow row in sprocs.Rows){
string spType=row["ROUTINE_TYPE"].ToString();
var sp=new SP();
sp.Name=row["ROUTINE_NAME"].ToString();
if(spType=="PROCEDURE" &! sp.Name.StartsWith("sp_")){
sp.CleanName=CleanUp(sp.Name);
sp.Parameters=GetSPParams(sp.Name);
result.Add(sp);
}
}
return result;
}
List<Table> LoadTables(){
var result=new List<Table>();
//pull the tables in a reader
using(IDataReader rdr=GetReader(TABLE_SQL)){
while(rdr.Read()){
Table tbl=new Table();
tbl.Name=rdr["TABLE_NAME"].ToString();
tbl.Schema=rdr["TABLE_SCHEMA"].ToString();
tbl.Columns=LoadColumns(tbl);
tbl.PrimaryKey=GetPK(tbl.Name);
tbl.CleanName=CleanUp(tbl.Name);
tbl.ClassName=Inflector.MakeSingular(tbl.CleanName);
tbl.QueryableName=Inflector.MakePlural(tbl.ClassName);
//set the PK for the columns
var pkColumn=tbl.Columns.SingleOrDefault(x=>x.Name.ToLower().Trim()==tbl.PrimaryKey.ToLower().Trim());
if(pkColumn!=null)
pkColumn.IsPK=true;
tbl.FKTables=LoadFKTables(tbl.Name);
result.Add(tbl);
}
}
foreach(Table tbl in result){
//loop the FK tables and see if there's a match for our FK columns
foreach(Column col in tbl.Columns){
col.IsForeignKey=tbl.FKTables.Any(
x=>x.ThisColumn.Equals(col.Name,StringComparison.InvariantCultureIgnoreCase)
);
}
}
return result;
}
List<Column> LoadColumns(Table tbl){
var result=new List<Column>();
var cmd=GetCommand(COLUMN_SQL);
cmd.Parameters.AddWithValue("@tableName",tbl.Name);
using(IDataReader rdr=cmd.ExecuteReader(CommandBehavior.CloseConnection)){
while(rdr.Read()){
Column col=new Column();
col.Name=rdr["ColumnName"].ToString();
col.CleanName=CleanUp(col.Name);
col.DataType=rdr["DataType"].ToString();
col.SysType=GetSysType(col.DataType);
col.DbType=GetDbType(col.DataType);
col.AutoIncrement=rdr["IsIdentity"].ToString()=="1";
col.IsNullable=rdr["IsNullable"].ToString()=="YES";
int.TryParse(rdr["MaxLength"].ToString(),out col.MaxLength);
result.Add(col);
}
}
return result;
}
List<FKTable> LoadFKTables(string tableName){
//this is a "bi-directional" scheme
//which pulls both 1-many and many-1
var result=new List<FKTable>();
var cmd=GetCommand(FKSql);
cmd.Parameters.AddWithValue("@tableName",tableName);
using(IDataReader rdr=cmd.ExecuteReader(CommandBehavior.CloseConnection)){
while(rdr.Read()){
FKTable fk=new FKTable();
string thisTable=rdr["ThisTable"].ToString();
if(tableName.ToLower()==thisTable.ToLower()){
fk.ThisTable=rdr["ThisTable"].ToString();
fk.ThisColumn=rdr["ThisColumn"].ToString();
fk.OtherTable=rdr["OtherTable"].ToString();
fk.OtherColumn=rdr["OtherColumn"].ToString();
}else{
fk.ThisTable=rdr["OtherTable"].ToString();
fk.ThisColumn=rdr["OtherColumn"].ToString();
fk.OtherTable=rdr["ThisTable"].ToString();
fk.OtherColumn=rdr["ThisColumn"].ToString();
}
fk.OtherClass=Inflector.MakeSingular(CleanUp(fk.OtherTable));
fk.OtherQueryable=Inflector.MakePlural(fk.OtherClass);
result.Add(fk);
}
}
return result;
}
string GetPK(string table){
string pk="";
DataTable pkTable=new DataTable();
string sql=@"SELECT KCU.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
ON KCU.CONSTRAINT_NAME=TC.CONSTRAINT_NAME
WHERE TC.CONSTRAINT_TYPE='PRIMARY KEY'
AND KCU.TABLE_NAME=@tableName";
var cmd=GetCommand(sql);
cmd.Parameters.AddWithValue("@tableName",table);
var result=cmd.ExecuteScalar();
cmd.Dispose();
if(result!=null)
pk=result.ToString();
return pk;
}
string GetSysType(string sqlType){
string sysType="string";
switch (sqlType) {
case "bigint":
sysType = "long";
break;
case "smallint":
sysType= "short";
break;
case "int":
sysType= "int";
break;
case "uniqueidentifier":
sysType= "Guid";
break;
case "smalldatetime":
case "datetime":
case "date":
sysType= "DateTime";
break;
case "float":
sysType="double";
break;
case "real":
case "numeric":
case "smallmoney":
case "decimal":
case "money":
sysType= "decimal";
break;
case "tinyint":
sysType = "byte";
break;
case "bit":
sysType= "bool";
break;
case "image":
case "binary":
case "varbinary":
case "timestamp":
sysType= "byte[]";
break;
}
return sysType;
}
DbType GetDbType(string sqlType){
switch(sqlType)
{
case "varchar":
return DbType.AnsiString;
case "nvarchar":
return DbType.String;
case "int":
return DbType.Int32;
case "uniqueidentifier":
return DbType.Guid;
case "datetime":
return DbType.DateTime;
case "bigint":
return DbType.Int64;
case "binary":
return DbType.Binary;
case "bit":
return DbType.Boolean;
case "char":
return DbType.AnsiStringFixedLength;
case "decimal":
return DbType.Decimal;
case "float":
return DbType.Double;
case "image":
return DbType.Binary;
case "money":
return DbType.Currency;
case "nchar":
return DbType.String;
case "ntext":
return DbType.String;
case "numeric":
return DbType.Decimal;
case "real":
return DbType.Single;
case "smalldatetime":
return DbType.DateTime;
case "smallint":
return DbType.Int16;
case "smallmoney":
return DbType.Currency;
case "sql_variant":
return DbType.String;
case "sysname":
return DbType.String;
case "text":
return DbType.AnsiString;
case "timestamp":
return DbType.Binary;
case "tinyint":
return DbType.Byte;
case "varbinary":
return DbType.Binary;
case "xml":
return DbType.Xml;
default:
return DbType.AnsiString;
}
}
#>
\ No newline at end of file
<#@ template language="C#v3.5" debug="True" hostspecific="True" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Configuration" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data.Common" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Configuration" #>
<#+
const string Namespace = "SubSonic";
const string ConnectionStringName = "Smackdown.Properties.Settings.tempdbConnectionString";
//This is the name of your database and is used in naming
//the repository. By default we set it to the connection string name
const string DatabaseName = "tempdb";
const bool TreatTinyint1AsBool = false;
//this is a list of tables you don't want generated
string[] ExcludeTables = new string[]{
"sysdiagrams",
"BuildVersion",
};
string CleanUp(string tableName){
string result=tableName;
//strip blanks
result=result.Replace(" ","");
//put your logic here...
return result;
}
string CheckNullable(Column col){
string result="";
if(col.IsNullable && col.SysType !="byte[]" && col.SysType !="string")
result="?";
return result;
}
string GetConnectionString(string connectionStringName){
var _CurrentProject = GetCurrentProject();
string result="";
ExeConfigurationFileMap configFile = new ExeConfigurationFileMap();
configFile.ExeConfigFilename = GetConfigPath();
if (string.IsNullOrEmpty(configFile.ExeConfigFilename))
throw new ArgumentNullException("The project does not contain App.config or Web.config file.");
var config = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);
var connSection=config.ConnectionStrings;
//if the connectionString is empty - which is the defauls
//look for count-1 - this is the last connection string
//and takes into account AppServices and LocalSqlServer
if(string.IsNullOrEmpty(connectionStringName)){
if(connSection.ConnectionStrings.Count>1){
result=connSection.ConnectionStrings[connSection.ConnectionStrings.Count-1].ConnectionString;
}
}else{
try{
result=connSection.ConnectionStrings[connectionStringName].ConnectionString;
}catch{
result="There is no connection string name called '"+connectionStringName+"'";
}
}
return result;
}
string _connectionString="";
public string ConnectionString{
get {
if(String.IsNullOrEmpty(_connectionString)){
_connectionString=GetConnectionString(ConnectionStringName);
}
if(_connectionString.Contains("|DataDirectory|")){
//have to replace it
string dataFilePath=GetDataDirectory();
_connectionString=_connectionString.Replace("|DataDirectory|",dataFilePath);
}
return _connectionString;
}
}
public EnvDTE.Project GetCurrentProject() {
IServiceProvider _ServiceProvider = (IServiceProvider)Host;
if (_ServiceProvider == null)
throw new Exception("Host property returned unexpected value (null)");
EnvDTE.DTE dte = (EnvDTE.DTE)_ServiceProvider.GetService(typeof(EnvDTE.DTE));
if (dte == null)
throw new Exception("Unable to retrieve EnvDTE.DTE");
Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects;
if (activeSolutionProjects == null)
throw new Exception("DTE.ActiveSolutionProjects returned null");
EnvDTE.Project dteProject = (EnvDTE.Project)activeSolutionProjects.GetValue(0);
if (dteProject == null)
throw new Exception("DTE.ActiveSolutionProjects[0] returned null");
return dteProject;
}
private string GetProjectPath()
{
EnvDTE.Project project = GetCurrentProject();
System.IO.FileInfo info = new System.IO.FileInfo(project.FullName);
return info.Directory.FullName;
}
private string GetConfigPath()
{
EnvDTE.Project project = GetCurrentProject();
foreach (EnvDTE.ProjectItem item in project.ProjectItems)
{
// if it is the app.config file, then open it up
if (item.Name.Equals("App.config",StringComparison.InvariantCultureIgnoreCase) || item.Name.Equals("Web.config",StringComparison.InvariantCultureIgnoreCase))
return GetProjectPath() + "\\" + item.Name;
}
return String.Empty;
}
public string GetDataDirectory(){
EnvDTE.Project project=GetCurrentProject();
return System.IO.Path.GetDirectoryName(project.FileName)+"\\App_Data\\";
}
public class Table{
public List<Column> Columns;
public List<FKTable> FKTables;
public string Name;
public string CleanName;
public string ClassName;
public string PrimaryKey;
public string Schema;
public string QueryableName;
public bool HasLogicalDelete(){
return this.Columns.Any(x=>x.Name.ToLower()=="deleted" || x.Name.ToLower()=="isdeleted");
}
public Column DeleteColumn{
get{
Column result=null;
if(this.Columns.Any(x=>x.Name.ToLower()=="deleted"))
result=this.Columns.Single(x=>x.Name.ToLower()=="deleted");
if(this.Columns.Any(x=>x.Name.ToLower()=="isdeleted"))
result=this.Columns.Single(x=>x.Name.ToLower()=="isdeleted");
return result;
}
}
public Column PK{
get{
return this.Columns.FirstOrDefault(x=>x.IsPK) ?? this.Columns[0];
}
}
public Column Descriptor{
get{
if(this.Columns.Count==1){
return this.Columns[0];
}else{
//get the first string column
Column result=null;
result=this.Columns.FirstOrDefault(x=>x.SysType.ToLower().Trim()=="string");
if(result==null)
result=this.Columns[1];
return result;
}
}
}
}
public class Column{
public string Name;
public string CleanName;
public string SysType;
public string DataType;
public DbType DbType;
public bool AutoIncrement;
public bool IsPK;
public int MaxLength;
public bool IsNullable;
public bool IsForeignKey;
public bool IsUnsigned;
}
public class FKTable{
public string ThisTable;
public string ThisColumn;
public string OtherTable;
public string OtherColumn;
public string OtherClass;
public string OtherQueryable;
}
public class SP{
public string Name;
public string CleanName;
public string ClassName;
public List<SPParam> Parameters;
public SP(){
Parameters=new List<SPParam>();
}
public string ArgList{
get{
StringBuilder sb=new StringBuilder();
int loopCount=1;
foreach(var par in Parameters){
sb.AppendFormat("{0} {1}", par.SysType,par.CleanName);
if(loopCount<Parameters.Count)
sb.Append(",");
loopCount++;
}
return sb.ToString();
}
}
}
public class SPParam{
public string Name;
public string CleanName;
public string SysType;
public string DbType;
}
/*
* SubSonic - http://subsonicproject.com
*
* The contents of this file are subject to the New BSD
* License (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.opensource.org/licenses/bsd-license.php
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*/
/// <summary>
/// Summary for the Inflector class
/// </summary>
public static class Inflector {
private static readonly List<InflectorRule> _plurals = new List<InflectorRule>();
private static readonly List<InflectorRule> _singulars = new List<InflectorRule>();
private static readonly List<string> _uncountables = new List<string>();
/// <summary>
/// Initializes the <see cref="Inflector"/> class.
/// </summary>
static Inflector() {
AddPluralRule("$", "s");
AddPluralRule("s$", "s");
AddPluralRule("(ax|test)is$", "$1es");
AddPluralRule("(octop|vir)us$", "$1i");
AddPluralRule("(alias|status)$", "$1es");
AddPluralRule("(bu)s$", "$1ses");
AddPluralRule("(buffal|tomat)o$", "$1oes");
AddPluralRule("([ti])um$", "$1a");
AddPluralRule("sis$", "ses");
AddPluralRule("(?:([^f])fe|([lr])f)$", "$1$2ves");
AddPluralRule("(hive)$", "$1s");
AddPluralRule("([^aeiouy]|qu)y$", "$1ies");
AddPluralRule("(x|ch|ss|sh)$", "$1es");
AddPluralRule("(matr|vert|ind)ix|ex$", "$1ices");
AddPluralRule("([m|l])ouse$", "$1ice");
AddPluralRule("^(ox)$", "$1en");
AddPluralRule("(quiz)$", "$1zes");
AddSingularRule("s$", String.Empty);
AddSingularRule("ss$", "ss");
AddSingularRule("(n)ews$", "$1ews");
AddSingularRule("([ti])a$", "$1um");
AddSingularRule("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
AddSingularRule("(^analy)ses$", "$1sis");
AddSingularRule("([^f])ves$", "$1fe");
AddSingularRule("(hive)s$", "$1");
AddSingularRule("(tive)s$", "$1");
AddSingularRule("([lr])ves$", "$1f");
AddSingularRule("([^aeiouy]|qu)ies$", "$1y");
AddSingularRule("(s)eries$", "$1eries");
AddSingularRule("(m)ovies$", "$1ovie");
AddSingularRule("(x|ch|ss|sh)es$", "$1");
AddSingularRule("([m|l])ice$", "$1ouse");
AddSingularRule("(bus)es$", "$1");
AddSingularRule("(o)es$", "$1");
AddSingularRule("(shoe)s$", "$1");
AddSingularRule("(cris|ax|test)es$", "$1is");
AddSingularRule("(octop|vir)i$", "$1us");
AddSingularRule("(alias|status)$", "$1");
AddSingularRule("(alias|status)es$", "$1");
AddSingularRule("^(ox)en", "$1");
AddSingularRule("(vert|ind)ices$", "$1ex");
AddSingularRule("(matr)ices$", "$1ix");
AddSingularRule("(quiz)zes$", "$1");
AddIrregularRule("person", "people");
AddIrregularRule("man", "men");
AddIrregularRule("child", "children");
AddIrregularRule("sex", "sexes");
AddIrregularRule("tax", "taxes");
AddIrregularRule("move", "moves");
AddUnknownCountRule("equipment");
AddUnknownCountRule("information");
AddUnknownCountRule("rice");
AddUnknownCountRule("money");
AddUnknownCountRule("species");
AddUnknownCountRule("series");
AddUnknownCountRule("fish");
AddUnknownCountRule("sheep");
}
/// <summary>
/// Adds the irregular rule.
/// </summary>
/// <param name="singular">The singular.</param>
/// <param name="plural">The plural.</param>
private static void AddIrregularRule(string singular, string plural) {
AddPluralRule(String.Concat("(", singular[0], ")", singular.Substring(1), "$"), String.Concat("$1", plural.Substring(1)));
AddSingularRule(String.Concat("(", plural[0], ")", plural.Substring(1), "$"), String.Concat("$1", singular.Substring(1)));
}
/// <summary>
/// Adds the unknown count rule.
/// </summary>
/// <param name="word">The word.</param>
private static void AddUnknownCountRule(string word) {
_uncountables.Add(word.ToLower());
}
/// <summary>
/// Adds the plural rule.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="replacement">The replacement.</param>
private static void AddPluralRule(string rule, string replacement) {
_plurals.Add(new InflectorRule(rule, replacement));
}
/// <summary>
/// Adds the singular rule.
/// </summary>
/// <param name="rule">The rule.</param>
/// <param name="replacement">The replacement.</param>
private static void AddSingularRule(string rule, string replacement) {
_singulars.Add(new InflectorRule(rule, replacement));
}
/// <summary>
/// Makes the plural.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public static string MakePlural(string word) {
return ApplyRules(_plurals, word);
}
/// <summary>
/// Makes the singular.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public static string MakeSingular(string word) {
return ApplyRules(_singulars, word);
}
/// <summary>
/// Applies the rules.
/// </summary>
/// <param name="rules">The rules.</param>
/// <param name="word">The word.</param>
/// <returns></returns>
private static string ApplyRules(IList<InflectorRule> rules, string word) {
string result = word;
if (!_uncountables.Contains(word.ToLower())) {
for (int i = rules.Count - 1; i >= 0; i--) {
string currentPass = rules[i].Apply(word);
if (currentPass != null) {
result = currentPass;
break;
}
}
}
return result;
}
/// <summary>
/// Converts the string to title case.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public static string ToTitleCase(string word) {
return Regex.Replace(ToHumanCase(AddUnderscores(word)), @"\b([a-z])",
delegate(Match match) { return match.Captures[0].Value.ToUpper(); });
}
/// <summary>
/// Converts the string to human case.
/// </summary>
/// <param name="lowercaseAndUnderscoredWord">The lowercase and underscored word.</param>
/// <returns></returns>
public static string ToHumanCase(string lowercaseAndUnderscoredWord) {
return MakeInitialCaps(Regex.Replace(lowercaseAndUnderscoredWord, @"_", " "));
}
/// <summary>
/// Adds the underscores.
/// </summary>
/// <param name="pascalCasedWord">The pascal cased word.</param>
/// <returns></returns>
public static string AddUnderscores(string pascalCasedWord) {
return Regex.Replace(Regex.Replace(Regex.Replace(pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])", "$1_$2"), @"[-\s]", "_").ToLower();
}
/// <summary>
/// Makes the initial caps.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public static string MakeInitialCaps(string word) {
return String.Concat(word.Substring(0, 1).ToUpper(), word.Substring(1).ToLower());
}
/// <summary>
/// Makes the initial lower case.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public static string MakeInitialLowerCase(string word) {
return String.Concat(word.Substring(0, 1).ToLower(), word.Substring(1));
}
/// <summary>
/// Determine whether the passed string is numeric, by attempting to parse it to a double
/// </summary>
/// <param name="str">The string to evaluated for numeric conversion</param>
/// <returns>
/// <c>true</c> if the string can be converted to a number; otherwise, <c>false</c>.
/// </returns>
public static bool IsStringNumeric(string str) {
double result;
return (double.TryParse(str, NumberStyles.Float, NumberFormatInfo.CurrentInfo, out result));
}
/// <summary>
/// Adds the ordinal suffix.
/// </summary>
/// <param name="number">The number.</param>
/// <returns></returns>
public static string AddOrdinalSuffix(string number) {
if (IsStringNumeric(number)) {
int n = int.Parse(number);
int nMod100 = n % 100;
if (nMod100 >= 11 && nMod100 <= 13)
return String.Concat(number, "th");
switch (n % 10) {
case 1:
return String.Concat(number, "st");
case 2:
return String.Concat(number, "nd");
case 3:
return String.Concat(number, "rd");
default:
return String.Concat(number, "th");
}
}
return number;
}
/// <summary>
/// Converts the underscores to dashes.
/// </summary>
/// <param name="underscoredWord">The underscored word.</param>
/// <returns></returns>
public static string ConvertUnderscoresToDashes(string underscoredWord) {
return underscoredWord.Replace('_', '-');
}
#region Nested type: InflectorRule
/// <summary>
/// Summary for the InflectorRule class
/// </summary>
private class InflectorRule {
/// <summary>
///
/// </summary>
public readonly Regex regex;
/// <summary>
///
/// </summary>
public readonly string replacement;
/// <summary>
/// Initializes a new instance of the <see cref="InflectorRule"/> class.
/// </summary>
/// <param name="regexPattern">The regex pattern.</param>
/// <param name="replacementText">The replacement text.</param>
public InflectorRule(string regexPattern, string replacementText) {
regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
replacement = replacementText;
}
/// <summary>
/// Applies the specified word.
/// </summary>
/// <param name="word">The word.</param>
/// <returns></returns>
public string Apply(string word) {
if (!regex.IsMatch(word))
return null;
string replace = regex.Replace(word, replacement);
if (word == word.ToUpper())
replace = replace.ToUpper();
return replace;
}
}
#endregion
}
#>
\ No newline at end of file

\ No newline at end of file
<#@ template language="C#v3.5" debug="False" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ include file="SQLServer.ttinclude" #>
<#
var sps = GetSPs();
if(sps.Count>0){
#>
using System;
using SubSonic;
using SubSonic.Schema;
using SubSonic.DataProviders;
using System.Data;
namespace <#=Namespace#>{
public partial class <#=DatabaseName#>DB{
<# foreach(var sp in sps){#>
public StoredProcedure <#=sp.CleanName#>(<#=sp.ArgList#>){
StoredProcedure sp=new StoredProcedure("<#=sp.Name#>",this.Provider);
<# foreach(var par in sp.Parameters){#>
sp.Command.AddParameter("<#=par.Name#>",<#=par.CleanName#>,DbType.<#=par.DbType#>);
<# }#>
return sp;
}
<# }#>
}
}
<# }#>
\ No newline at end of file
using System;
using SubSonic.Schema;
using System.Collections.Generic;
using SubSonic.DataProviders;
using System.Data;
namespace SubSonic {
/// <summary>
/// Table: Posts
/// Primary Key: Id
/// </summary>
public class PostsTable: DatabaseTable {
public PostsTable(IDataProvider provider):base("Posts",provider){
ClassName = "Post";
SchemaName = "dbo";
Columns.Add(new DatabaseColumn("Id", this)
{
IsPrimaryKey = true,
DataType = DbType.Int32,
IsNullable = false,
AutoIncrement = true,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Text", this)
{
IsPrimaryKey = false,
DataType = DbType.AnsiString,
IsNullable = false,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = -1
});
Columns.Add(new DatabaseColumn("CreationDate", this)
{
IsPrimaryKey = false,
DataType = DbType.DateTime,
IsNullable = false,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("LastChangeDate", this)
{
IsPrimaryKey = false,
DataType = DbType.DateTime,
IsNullable = false,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter1", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter2", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter3", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter4", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter5", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter6", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter7", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter8", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
Columns.Add(new DatabaseColumn("Counter9", this)
{
IsPrimaryKey = false,
DataType = DbType.Int32,
IsNullable = true,
AutoIncrement = false,
IsForeignKey = false,
MaxLength = 0
});
}
public IColumn Id{
get{
return this.GetColumn("Id");
}
}
public static string IdColumn{
get{
return "Id";
}
}
public IColumn Text{
get{
return this.GetColumn("Text");
}
}
public static string TextColumn{
get{
return "Text";
}
}
public IColumn CreationDate{
get{
return this.GetColumn("CreationDate");
}
}
public static string CreationDateColumn{
get{
return "CreationDate";
}
}
public IColumn LastChangeDate{
get{
return this.GetColumn("LastChangeDate");
}
}
public static string LastChangeDateColumn{
get{
return "LastChangeDate";
}
}
public IColumn Counter1{
get{
return this.GetColumn("Counter1");
}
}
public static string Counter1Column{
get{
return "Counter1";
}
}
public IColumn Counter2{
get{
return this.GetColumn("Counter2");
}
}
public static string Counter2Column{
get{
return "Counter2";
}
}
public IColumn Counter3{
get{
return this.GetColumn("Counter3");
}
}
public static string Counter3Column{
get{
return "Counter3";
}
}
public IColumn Counter4{
get{
return this.GetColumn("Counter4");
}
}
public static string Counter4Column{
get{
return "Counter4";
}
}
public IColumn Counter5{
get{
return this.GetColumn("Counter5");
}
}
public static string Counter5Column{
get{
return "Counter5";
}
}
public IColumn Counter6{
get{
return this.GetColumn("Counter6");
}
}
public static string Counter6Column{
get{
return "Counter6";
}
}
public IColumn Counter7{
get{
return this.GetColumn("Counter7");
}
}
public static string Counter7Column{
get{
return "Counter7";
}
}
public IColumn Counter8{
get{
return this.GetColumn("Counter8");
}
}
public static string Counter8Column{
get{
return "Counter8";
}
}
public IColumn Counter9{
get{
return this.GetColumn("Counter9");
}
}
public static string Counter9Column{
get{
return "Counter9";
}
}
}
}
\ No newline at end of file
<#@ template language="C#v3.5" debug="False" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ include file="SQLServer.ttinclude" #>
<#
var tables = LoadTables();
#>
using System;
using SubSonic.Schema;
using System.Collections.Generic;
using SubSonic.DataProviders;
using System.Data;
namespace <#=Namespace#> {
<# foreach(var tbl in tables){
if(!ExcludeTables.Contains(tbl.Name))
{
#>
/// <summary>
/// Table: <#=tbl.Name#>
/// Primary Key: <#=tbl.PrimaryKey#>
/// </summary>
public class <#=tbl.CleanName#>Table: DatabaseTable {
public <#=tbl.CleanName#>Table(IDataProvider provider):base("<#=tbl.Name#>",provider){
ClassName = "<#=tbl.ClassName#>";
SchemaName = "<#=tbl.Schema ?? ""#>";
<# foreach(var col in tbl.Columns){#>
Columns.Add(new DatabaseColumn("<#=col.Name#>", this)
{
IsPrimaryKey = <#=col.IsPK.ToString().ToLower()#>,
DataType = DbType.<#=col.DbType.ToString()#>,
IsNullable = <#=col.IsNullable.ToString().ToLower()#>,
AutoIncrement = <#=col.AutoIncrement.ToString().ToLower()#>,
IsForeignKey = <#=col.IsForeignKey.ToString().ToLower()#>,
MaxLength = <#=col.MaxLength#>
});
<# }#>
}
<# foreach(var col in tbl.Columns){#>
public IColumn <#=col.CleanName#>{
get{
return this.GetColumn("<#=col.Name#>");
}
}
public static string <#= col.CleanName #>Column{
get{
return "<#= col.Name #>";
}
}
<# }#>
}
<#
}
}
#>
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment