From 596c56b3afda4d22e8d6171bcc33b9b46075c0d2 Mon Sep 17 00:00:00 2001 From: Paul Wheeler Date: Tue, 13 Dec 2016 16:55:39 -0500 Subject: [PATCH] Convert from PetaPoco to EntityFramework --- .../App-Code/Controllers/HomeController.cs | 4 +- MyepWeb/App-Code/Models/Code.cs | 6 +- MyepWeb/App-Code/Models/Intern.cs | 5 +- MyepWeb/App-Code/Models/User.cs | 2 +- MyepWeb/App-Code/Services/Codes.cs | 30 +- MyepWeb/App-Code/Services/Interns.cs | 50 +- MyepWeb/App-Code/Services/SiteDb.cs | 114 +- MyepWeb/App-Code/Services/Users.cs | 37 +- MyepWeb/App-Code/Utility/PetaPoco.cs | 4286 ----------------- MyepWeb/App-Code/Utility/PetaSchema.cs | 459 -- MyepWeb/Bootstrapper.cs | 6 +- MyepWeb/MyepWeb.csproj | 10 +- MyepWeb/Web.config | 8 + MyepWeb/packages.config | 1 + 14 files changed, 217 insertions(+), 4801 deletions(-) delete mode 100644 MyepWeb/App-Code/Utility/PetaPoco.cs delete mode 100644 MyepWeb/App-Code/Utility/PetaSchema.cs diff --git a/MyepWeb/App-Code/Controllers/HomeController.cs b/MyepWeb/App-Code/Controllers/HomeController.cs index 78be9a5..dbe89fa 100644 --- a/MyepWeb/App-Code/Controllers/HomeController.cs +++ b/MyepWeb/App-Code/Controllers/HomeController.cs @@ -5,13 +5,11 @@ namespace Site { public class HomeController : Controller { - private readonly SiteDb _db; private readonly Interns _interns; private readonly Users _users; - public HomeController(SiteDb db, Interns interns, Users users) + public HomeController(Interns interns, Users users) { - _db = db; _interns = interns; _users = users; } diff --git a/MyepWeb/App-Code/Models/Code.cs b/MyepWeb/App-Code/Models/Code.cs index 28ee9d3..9b71ec8 100644 --- a/MyepWeb/App-Code/Models/Code.cs +++ b/MyepWeb/App-Code/Models/Code.cs @@ -4,18 +4,18 @@ namespace Site { [Table("Codes")] - public class Code : IEntity + public class Code { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] - [Index("IX_Code_TypeValue", true)] + [Index("IX_Code_TypeValue", IsUnique = true)] [StringLength(100)] public string Type { get; set; } [Required] - [Index("IX_Code_TypeValue", true)] + [Index("IX_Code_TypeValue", IsUnique = true)] [StringLength(100)] public string Value { get; set; } diff --git a/MyepWeb/App-Code/Models/Intern.cs b/MyepWeb/App-Code/Models/Intern.cs index b0b99c9..668ed20 100644 --- a/MyepWeb/App-Code/Models/Intern.cs +++ b/MyepWeb/App-Code/Models/Intern.cs @@ -5,13 +5,13 @@ namespace Site { [Table("Interns")] - public class Intern : IEntity + public class Intern { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [StringLength(10)] - [Index("IX_Intern_AccessCode", unique: true)] + [Index("IX_Intern_AccessCode", IsUnique = true)] public string AccessCode { get; set; } [StringLength(10)] @@ -79,6 +79,7 @@ public class Intern : IEntity public bool HasSummerSchool { get; set; } + [StringLength(50)] public string InternshipAvailability { get; set; } public bool ConvictedOfCrime { get; set; } diff --git a/MyepWeb/App-Code/Models/User.cs b/MyepWeb/App-Code/Models/User.cs index 3abf764..81857e5 100644 --- a/MyepWeb/App-Code/Models/User.cs +++ b/MyepWeb/App-Code/Models/User.cs @@ -4,7 +4,7 @@ namespace Site { [Table("Users")] - public class User : IEntity + public class User { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } diff --git a/MyepWeb/App-Code/Services/Codes.cs b/MyepWeb/App-Code/Services/Codes.cs index 821ef42..6525479 100644 --- a/MyepWeb/App-Code/Services/Codes.cs +++ b/MyepWeb/App-Code/Services/Codes.cs @@ -5,28 +5,44 @@ namespace Site { public class Codes { - private readonly SiteDb _db; + private readonly IDb _db; - public Codes(SiteDb db) + public Codes(IDb db) { _db = db; } public List GetTypes() { - return _db.Query("SELECT DISTINCT [Type] FROM [Codes] ORDER BY [Type]").ToList(); + return _db + .Query() + .Select(x => x.Type) + .Distinct() + .OrderBy(x => x) + .ToList(); } public List Query(string type, bool? active = null) { - var sql = "SELECT * FROM [Codes] WHERE [Type]=@0"; - if (active == true) sql += " AND Seq>=0 "; - return _db.Query(sql + " ORDER BY [Seq],[Value]", type).ToList(); + var query = _db + .Query() + .Where(x => x.Type == type); + + if (active == true) + { + query = query.Where(x => x.Seq >= 0); + } + + return query + .OrderBy(x => x.Seq) + .ThenBy(x => x.Value) + .ToList(); } public void Save(Code model) { - _db.Save("Codes", "Id", model); + _db.Save(model, model.Id == 0); + _db.SaveChanges(); } }; } diff --git a/MyepWeb/App-Code/Services/Interns.cs b/MyepWeb/App-Code/Services/Interns.cs index 2eeab69..a68252a 100644 --- a/MyepWeb/App-Code/Services/Interns.cs +++ b/MyepWeb/App-Code/Services/Interns.cs @@ -9,36 +9,59 @@ namespace Site { public class Interns { - private readonly SiteDb _db; + private readonly IDb _db; - public Interns(SiteDb db) + public Interns(IDb db) { _db = db; } public List Query(bool? inactive = false) { - var sql = "SELECT Id AS InternId, FirstName+' '+LastName AS FullName FROM Interns"; - if (inactive != true) sql += " WHERE Inactive=0 "; - return _db.Query(sql).ToList(); + var query = _db.Query(); + + if (inactive != true) + { + query = query.Where(x => x.Inactive == false); + } + + return query + .Select(x => new InternInfo + { + InternId = x.Id, + FullName = x.FirstName + " " + x.LastName, + }) + .ToList(); } public Intern Load(int? id) { - if (!id.HasValue) return null; - return _db.SingleOrDefault("SELECT * FROM Interns WHERE Id=@0", id); + if (!id.HasValue) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.Id == id); } public Intern LoadByCode(string code) { - if (!code.HasValue()) return null; - return _db.SingleOrDefault("SELECT * FROM Interns WHERE CmsStudentId=@0 OR AccessCode=@0", code); + if (!code.HasValue()) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.CmsStudentId == code || x.AccessCode == code); } public Intern LoadByCmsId(string id) { - if (!id.HasValue()) return null; - return _db.SingleOrDefault("SELECT * FROM Interns WHERE CmsStudentId=@0", id); + if (!id.HasValue()) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.CmsStudentId == id); } public Intern Create(string cmsStudentId) @@ -80,7 +103,8 @@ public void Save(Intern model) if (model.RaceOther.HasValue() && !model.Race.Or().Contains("Other")) model.Race = string.Join(",", model.Race.Or().Split(',').Union(new[] { "Other" }).Distinct()); - _db.Save("Interns", "Id", model); + _db.Save(model, model.Id == 0); + _db.SaveChanges(); } public void UploadEssay(Intern model, HttpPostedFileBase file) @@ -171,7 +195,7 @@ public int ImportSchoolFile(HttpPostedFileBase file) public Stream Export() { - var interns = _db.Query("SELECT * FROM Interns"); + var interns = _db.Query().ToList(); using (var excel = Excel.Create(interns)) { return excel.GetStream(); diff --git a/MyepWeb/App-Code/Services/SiteDb.cs b/MyepWeb/App-Code/Services/SiteDb.cs index 1204ba2..781dc05 100644 --- a/MyepWeb/App-Code/Services/SiteDb.cs +++ b/MyepWeb/App-Code/Services/SiteDb.cs @@ -1,23 +1,111 @@ -using PetaPoco; -using PetaSchema; +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; namespace Site { - public class SiteDb : Database + public interface IDb : IDisposable { - public SiteDb() : base("SiteDb") { } + IQueryable Query() where T : class; + T Add(T entity) where T : class; + T Save(T entity, bool? isNew = null) where T : class; + T Delete(T entity) where T : class; + bool IsDetached(T entity) where T : class; + int SaveChanges(); + int ExecuteSqlCommand(string sql, params object[] parameters); + List SqlQuery(string sql, params object[] parameters); + }; - public DbSchema GetSchema() + public class SiteDb : DbContext, IDb + { + public SiteDb(string cs) : base(cs) { - var schema = new DbSchema(this); - Ext.ImplementorsOf().ForEach(schema.AddTable); - return schema; + Database.SetInitializer(null); } - }; + public DbSet Codes { get; set; } + public DbSet Interns { get; set; } + public DbSet Users { get; set; } - public interface IEntity - { - //placeholder interface for database entities - } + //REF: http://msdn.microsoft.com/en-us/data/jj819164.aspx + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + modelBuilder.Configurations.AddFromAssembly(typeof(SiteDb).Assembly); + modelBuilder.HasDefaultSchema("dbo"); + } + + public virtual bool IsDetached(T entity) where T : class + { + var entry = Entry(entity); + return entry.State == EntityState.Detached; + } + + public virtual T Attach(T entity) where T : class + { + var entry = Entry(entity); + if (entry.State == EntityState.Detached) Set().Attach(entity); + return entity; + } + + public virtual T Modified(T entity) where T : class + { + var entry = Entry(entity); + if (entry.State == EntityState.Detached) entry.State = EntityState.Modified; + return entity; + } + + public virtual T Add(T entity) where T : class + { + Entry(entity).State = EntityState.Added; + return entity; + } + + public virtual T Save(T entity, bool? isNew = null) where T : class + { + var entry = Entry(entity); + if (entry.State == EntityState.Detached) + { + if (isNew == false) + { + Modified(entity); + } + else + { + Add(entity); + } + } + return entity; + } + + public virtual T Delete(T entity) where T : class + { + var entry = Entry(entity); + if (entry.State != EntityState.Detached) entry.State = EntityState.Deleted; + return entity; + } + + public void DeleteAll(IEnumerable entities) where T : class + { + entities.Each(x => Delete(x)); + } + + public IQueryable Query() where T : class + { + return Set(); + } + + public List SqlQuery(string sql, params object[] parameters) + { + return Database + .SqlQuery(sql, parameters) + .ToList(); + } + + public int ExecuteSqlCommand(string sql, params object[] parameters) + { + return Database + .ExecuteSqlCommand(sql, parameters); + } + }; } diff --git a/MyepWeb/App-Code/Services/Users.cs b/MyepWeb/App-Code/Services/Users.cs index 4945bd1..387a3f9 100644 --- a/MyepWeb/App-Code/Services/Users.cs +++ b/MyepWeb/App-Code/Services/Users.cs @@ -8,35 +8,49 @@ namespace Site { public class Users { - private readonly SiteDb _db; + private readonly IDb _db; - public Users(SiteDb db) + public Users(IDb db) { _db = db; } public List Query() { - return _db.Query("SELECT * FROM [Users]") - .ToList(); + return _db + .Query() + .OrderBy(x => x.Email) + .ToList(); } public User Load(int? id) { - if (!id.HasValue) return null; - return _db.SingleOrDefault("SELECT * FROM [Users] WHERE Id=@0", id); + if (!id.HasValue) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.Id == id); } public User Load(string email) { - if (string.IsNullOrWhiteSpace(email)) return null; - return _db.SingleOrDefault("SELECT * FROM [Users] WHERE [Email]=@0", email); + if (string.IsNullOrWhiteSpace(email)) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.Email == email); } public User LoadByCode(string resetCode) { - if (string.IsNullOrWhiteSpace(resetCode)) return null; - return _db.SingleOrDefault("SELECT * FROM [Users] WHERE [ResetCode]=@0", resetCode); + if (string.IsNullOrWhiteSpace(resetCode)) + return null; + + return _db + .Query() + .SingleOrDefault(x => x.ResetCode == resetCode); } public User Create() @@ -49,7 +63,8 @@ public User Create() public void Save(User model) { - _db.Save("Users", "Id", model); + _db.Save(model, model.Id == 0); + _db.SaveChanges(); } public bool Login(string email, string password, bool remember) diff --git a/MyepWeb/App-Code/Utility/PetaPoco.cs b/MyepWeb/App-Code/Utility/PetaPoco.cs deleted file mode 100644 index d3c9de9..0000000 --- a/MyepWeb/App-Code/Utility/PetaPoco.cs +++ /dev/null @@ -1,4286 +0,0 @@ -/* PetaPoco - A Tiny ORMish thing for your POCO's. - * Copyright © 2011-2012 Topten Software. All Rights Reserved. - * - * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice - * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support - */ - -// Define PETAPOCO_NO_DYNAMIC in your project settings on .NET 3.5 - - - -// This file was built by merging separate C# source files into one. -// DO NOT EDIT THIS FILE - go back to the originals - -using PetaPoco.DatabaseTypes; -using PetaPoco.Internal; -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; - -namespace PetaPoco -{ - /// - /// The main PetaPoco Database class. You can either use this class directly, or derive from it. - /// - public class Database : IDisposable - { - #region Constructors - /// - /// Construct a database using a supplied IDbConnection - /// - /// The IDbConnection to use - /// - /// The supplied IDbConnection will not be closed/disposed by PetaPoco - that remains - /// the responsibility of the caller. - /// - public Database(IDbConnection connection) - { - _sharedConnection = connection; - _connectionString = connection.ConnectionString; - _sharedConnectionDepth = 2; // Prevent closing external connection - CommonConstruct(); - } - - /// - /// Construct a database using a supplied connections string and optionally a provider name - /// - /// The DB connection string - /// The name of the DB provider to use - /// - /// PetaPoco will automatically close and dispose any connections it creates. - /// - public Database(string connectionString, string providerName) - { - _connectionString = connectionString; - _providerName = providerName; - CommonConstruct(); - } - - /// - /// Construct a Database using a supplied connection string and a DbProviderFactory - /// - /// The connection string to use - /// The DbProviderFactory to use for instantiating IDbConnection's - public Database(string connectionString, DbProviderFactory provider) - { - _connectionString = connectionString; - _factory = provider; - CommonConstruct(); - } - - /// - /// Construct a Database using a supplied connectionString Name. The actual connection string and provider will be - /// read from app/web.config. - /// - /// The name of the connection - 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(); - } - - /// - /// Provides common initialization for the various constructors - /// - private void CommonConstruct() - { - // Reset - _transactionDepth = 0; - EnableAutoSelect = true; - EnableNamedParams = true; - - // If a provider name was supplied, get the IDbProviderFactory for it - if (_providerName != null) - _factory = DbProviderFactories.GetFactory(_providerName); - - // Resolve the DB Type - string DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; - _dbType = DatabaseType.Resolve(DBTypeName, _providerName); - - // What character is used for delimiting parameters in SQL - _paramPrefix = _dbType.GetParameterPrefix(_connectionString); - } - - #endregion - - #region IDisposable - /// - /// Automatically close one open shared connection - /// - public virtual void Dispose() - { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); - } - #endregion - - #region Connection Management - /// - /// When set to true the first opened connection is kept alive until this object is disposed - /// - public bool KeepConnectionAlive - { - get; - set; - } - - /// - /// Open a connection that will be used for all subsequent queries. - /// - /// - /// Calls to Open/CloseSharedConnection are reference counted and should be balanced - /// - public void OpenSharedConnection() - { - if (_sharedConnectionDepth == 0) - { - _sharedConnection = _factory.CreateConnection(); - _sharedConnection.ConnectionString = _connectionString; - - if (_sharedConnection.State == ConnectionState.Broken) - _sharedConnection.Close(); - - if (_sharedConnection.State == ConnectionState.Closed) - _sharedConnection.Open(); - - _sharedConnection = OnConnectionOpened(_sharedConnection); - - if (KeepConnectionAlive) - _sharedConnectionDepth++; // Make sure you call Dispose - } - _sharedConnectionDepth++; - } - - /// - /// Releases the shared connection - /// - public void CloseSharedConnection() - { - if (_sharedConnectionDepth > 0) - { - _sharedConnectionDepth--; - if (_sharedConnectionDepth == 0) - { - OnConnectionClosing(_sharedConnection); - _sharedConnection.Dispose(); - _sharedConnection = null; - } - } - } - - /// - /// Provides access to the currently open shared connection (or null if none) - /// - public IDbConnection Connection - { - get { return _sharedConnection; } - } - - #endregion - - #region Transaction Management - // Helper to create a transaction scope - - /// - /// Starts or continues a transaction. - /// - /// An ITransaction reference that must be Completed or disposed - /// - /// This method makes management of calls to Begin/End/CompleteTransaction easier. - /// - /// The usage pattern for this should be: - /// - /// using (var tx = db.GetTransaction()) - /// { - /// // Do stuff - /// db.Update(...); - /// - /// // Mark the transaction as complete - /// tx.Complete(); - /// } - /// - /// Transactions can be nested but they must all be completed otherwise the entire - /// transaction is aborted. - /// - public ITransaction GetTransaction() - { - return new Transaction(this); - } - - /// - /// Called when a transaction starts. Overridden by the T4 template generated database - /// classes to ensure the same DB instance is used throughout the transaction. - /// - public virtual void OnBeginTransaction() - { - } - - /// - /// Called when a transaction ends. - /// - public virtual void OnEndTransaction() - { - } - - /// - /// Starts a transaction scope, see GetTransaction() for recommended usage - /// - public void BeginTransaction() - { - _transactionDepth++; - - if (_transactionDepth == 1) - { - OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(); - _transactionCancelled = false; - OnBeginTransaction(); - } - - } - - /// - /// Internal helper to cleanup transaction - /// - void CleanupTransaction() - { - OnEndTransaction(); - - if (_transactionCancelled) - _transaction.Rollback(); - else - _transaction.Commit(); - - _transaction.Dispose(); - _transaction = null; - - CloseSharedConnection(); - } - - /// - /// Aborts the entire outer most transaction scope - /// - /// - /// Called automatically by Transaction.Dispose() - /// if the transaction wasn't completed. - /// - public void AbortTransaction() - { - _transactionCancelled = true; - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - /// - /// Marks the current transaction scope as complete. - /// - public void CompleteTransaction() - { - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - #endregion - - #region Command Management - /// - /// Add a parameter to a DB command - /// - /// A reference to the IDbCommand to which the parameter is to be added - /// The value to assign to the parameter - /// Optional, a reference to the property info of the POCO property from which the value is coming. - void AddParam(IDbCommand cmd, object value, PropertyInfo pi) - { - // Convert value to from poco type to db type - if (pi != null) - { - var mapper = Mappers.GetMapper(pi.DeclaringType); - var fn = mapper.GetToDbConverter(pi); - if (fn != null) - value = fn(value); - } - - // Support passed in parameters - var idbParam = value as IDbDataParameter; - if (idbParam != null) - { - idbParam.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count); - cmd.Parameters.Add(idbParam); - return; - } - - // Create the parameter - var p = cmd.CreateParameter(); - p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count); - - // Assign the parmeter value - if (value == null) - { - p.Value = DBNull.Value; - } - else - { - // Give the database type first crack at converting to DB required type - value = _dbType.MapParameterValue(value); - - var t = value.GetType(); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int - { - p.Value = (int)value; - } - else if (t == typeof(Guid)) - { - p.Value = value.ToString(); - p.DbType = DbType.String; - p.Size = 40; - } - else if (t == typeof(string)) - { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 - if ((value as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - - p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size - p.Value = value; - } - else if (t == typeof(AnsiString)) - { - // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar - p.Size = Math.Max((value as AnsiString).Value.Length + 1, 4000); - p.Value = (value as AnsiString).Value; - p.DbType = DbType.AnsiString; - } - else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - - else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - else - { - p.Value = value; - } - } - - // Add to the collection - cmd.Parameters.Add(p); - } - - // Create a command - static Regex rxParamsPrefix = new Regex(@"(?(); - sql = ParametersHelper.ProcessParams(sql, args, new_args); - args = new_args.ToArray(); - } - - // Perform parameter prefix replacements - if (_paramPrefix != "@") - sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1)); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ - - // Create the command and add parameters - IDbCommand cmd = connection.CreateCommand(); - cmd.Connection = connection; - cmd.CommandText = sql; - cmd.Transaction = _transaction; - foreach (var item in args) - { - AddParam(cmd, item, null); - } - - // Notify the DB type - _dbType.PreExecute(cmd); - - // Call logging - if (!String.IsNullOrEmpty(sql)) - DoPreExecute(cmd); - - return cmd; - } - #endregion - - #region Exception Reporting and Logging - - /// - /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. - /// - /// The exception instance - /// True to re-throw the exception, false to suppress it - public virtual bool OnException(Exception x) - { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); - return true; - } - - /// - /// Called when DB connection opened - /// - /// The newly opened IDbConnection - /// The same or a replacement IDbConnection - /// - /// Override this method to provide custom logging of opening connection, or - /// to provide a proxy IDbConnection. - /// - public virtual IDbConnection OnConnectionOpened(IDbConnection conn) - { - return conn; - } - - /// - /// Called when DB connection closed - /// - /// The soon to be closed IDBConnection - public virtual void OnConnectionClosing(IDbConnection conn) - { - } - - /// - /// Called just before an DB command is executed - /// - /// The command to be executed - /// - /// Override this method to provide custom logging of commands and/or - /// modification of the IDbCommand before it's executed - /// - public virtual void OnExecutingCommand(IDbCommand cmd) - { - } - - /// - /// Called on completion of command execution - /// - /// The IDbCommand that finished executing - public virtual void OnExecutedCommand(IDbCommand cmd) - { - } - - #endregion - - #region operation: Execute - /// - /// Executes a non-query command - /// - /// The SQL statement to execute - /// Arguments to any embedded parameters in the SQL - /// The number of rows affected - public int Execute(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - var retv = cmd.ExecuteNonQuery(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return -1; - } - } - - /// - /// Executes a non-query command - /// - /// An SQL builder object representing the query and it's arguments - /// The number of rows affected - public int Execute(Sql sql) - { - return Execute(sql.SQL, sql.Arguments); - } - - #endregion - - #region operation: ExecuteScalar - - /// - /// Executes a query and return the first column of the first row in the result set. - /// - /// The type that the result value should be cast to - /// The SQL query to execute - /// Arguments to any embedded parameters in the SQL - /// The scalar value cast to T - public T ExecuteScalar(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - object val = cmd.ExecuteScalar(); - OnExecutedCommand(cmd); - - // Handle nullable types - Type u = Nullable.GetUnderlyingType(typeof(T)); - if (u != null && val == null) - return default(T); - - return (T)Convert.ChangeType(val, u == null ? typeof(T) : u); - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return default(T); - } - } - - /// - /// Executes a query and return the first column of the first row in the result set. - /// - /// The type that the result value should be cast to - /// An SQL builder object representing the query and it's arguments - /// The scalar value cast to T - public T ExecuteScalar(Sql sql) - { - return ExecuteScalar(sql.SQL, sql.Arguments); - } - - #endregion - - #region operation: Fetch - - /// - /// Runs a query and returns the result set as a typed list - /// - /// The Type representing a row in the result set - /// The SQL query to execute - /// Arguments to any embedded parameters in the SQL - /// A List holding the results of the query - public List Fetch(string sql, params object[] args) - { - return Query(sql, args).ToList(); - } - - /// - /// Runs a query and returns the result set as a typed list - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and it's arguments - /// A List holding the results of the query - public List Fetch(Sql sql) - { - return Fetch(sql.SQL, sql.Arguments); - } - - #endregion - - #region operation: Page - - /// - /// Starting with a regular SELECT statement, derives the SQL statements required to query a - /// DB for a page of records and the total number of records - /// - /// The Type representing a row in the result set - /// The number of rows to skip before the start of the page - /// The number of rows in the page - /// The original SQL select statement - /// Arguments to any embedded parameters in the SQL - /// Outputs the SQL statement to query for the total number of matching rows - /// Outputs the SQL statement to retrieve a single page of matching rows - void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) - { - // Add auto select clause - if (EnableAutoSelect) - sql = AutoSelectHelper.AddSelectClause(_dbType, sql); - - // Split the SQL - PagingHelper.SQLParts parts; - if (!PagingHelper.SplitSQL(sql, out parts)) - throw new Exception("Unable to parse SQL statement for paged query"); - - sqlPage = _dbType.BuildPageQuery(skip, take, parts, ref args); - sqlCount = parts.sqlCount; - } - - /// - /// Retrieves a page of records and the total number of available records - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// The SQL to retrieve the total number of records - /// Arguments to any embedded parameters in the sqlCount statement - /// The SQL To retrieve a single page of results - /// Arguments to any embedded parameters in the sqlPage statement - /// A Page of results - /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. - /// - public Page Page(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs) - { - // Save the one-time command time out and use it for both queries - var saveTimeout = OneTimeCommandTimeout; - - // Setup the paged result - var result = new Page - { - CurrentPage = page, - ItemsPerPage = itemsPerPage, - TotalItems = ExecuteScalar(sqlCount, countArgs) - }; - result.TotalPages = result.TotalItems / itemsPerPage; - - if ((result.TotalItems % itemsPerPage) != 0) - result.TotalPages++; - - OneTimeCommandTimeout = saveTimeout; - - // Get the records - result.Items = Fetch(sqlPage, pageArgs); - - // Done - return result; - } - - - /// - /// Retrieves a page of records and the total number of available records - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A Page of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. - /// - public Page Page(long page, long itemsPerPage, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); - return Page(page, itemsPerPage, sqlCount, args, sqlPage, args); - } - - /// - /// Retrieves a page of records and the total number of available records - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the base SQL query and it's arguments - /// A Page of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. - /// - public Page Page(long page, long itemsPerPage, Sql sql) - { - return Page(page, itemsPerPage, sql.SQL, sql.Arguments); - } - - /// - /// Retrieves a page of records and the total number of available records - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the SQL to retrieve the total number of records - /// An SQL builder object representing the SQL to retrieve a single page of results - /// A Page of results - /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. - /// - public Page Page(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) - { - return Page(page, itemsPerPage, sqlCount.SQL, sqlCount.Arguments, sqlPage.SQL, sqlPage.Arguments); - } - - #endregion - - #region operation: Fetch (page) - - /// - /// Retrieves a page of records (without the total count) - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A List of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. - /// - public List Fetch(long page, long itemsPerPage, string sql, params object[] args) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, args); - } - - /// - /// Retrieves a page of records (without the total count) - /// - /// The Type representing a row in the result set - /// The 1 based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the base SQL query and it's arguments - /// A List of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. - /// - public List Fetch(long page, long itemsPerPage, Sql sql) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); - } - - #endregion - - #region operation: SkipTake - - /// - /// Retrieves a range of records from result set - /// - /// The Type representing a row in the result set - /// The number of rows at the start of the result set to skip over - /// The number of rows to retrieve - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A List of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. - /// - public List SkipTake(long skip, long take, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); - return Fetch(sqlPage, args); - } - - /// - /// Retrieves a range of records from result set - /// - /// The Type representing a row in the result set - /// The number of rows at the start of the result set to skip over - /// The number of rows to retrieve - /// An SQL builder object representing the base SQL query and it's arguments - /// A List of results - /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. - /// - public List SkipTake(long skip, long take, Sql sql) - { - return SkipTake(skip, take, sql.SQL, sql.Arguments); - } - #endregion - - #region operation: Query - - /// - /// Runs an SQL query, returning the results as an IEnumerable collection - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// An enumerable collection of result records - /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List rather than an IEnumerable. - /// - public IEnumerable Query(string sql, params object[] args) - { - if (EnableAutoSelect) - sql = AutoSelectHelper.AddSelectClause(_dbType, sql); - - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - var pd = PocoData.ForType(typeof(T)); - try - { - r = cmd.ExecuteReader(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func; - using (r) - { - while (true) - { - T poco; - try - { - if (!r.Read()) - yield break; - poco = factory(r); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } - - yield return poco; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - /// - /// Runs an SQL query, returning the results as an IEnumerable collection - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the base SQL query and it's arguments - /// An enumerable collection of result records - /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List rather than an IEnumerable. - /// - public IEnumerable Query(Sql sql) - { - return Query(sql.SQL, sql.Arguments); - } - - #endregion - - #region operation: Exists - - /// - /// Checks for the existance of a row matching the specified condition - /// - /// The Type representing the table being queried - /// The SQL expression to be tested for (ie: the WHERE expression) - /// Arguments to any embedded parameters in the SQL statement - /// True if a record matching the condition is found. - public bool Exists(string sqlCondition, params object[] args) - { - var poco = PocoData.ForType(typeof(T)).TableInfo; - - return ExecuteScalar(string.Format(_dbType.GetExistsSql(), poco.TableName, sqlCondition), args) != 0; - } - - /// - /// Checks for the existance of a row with the specified primary key value. - /// - /// The Type representing the table being queried - /// The primary key value to look for - /// True if a record with the specified primary key value exists. - public bool Exists(object primaryKey) - { - return Exists(string.Format("{0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - - #endregion - - #region operation: linq style (Exists, Single, SingleOrDefault etc...) - - /// - /// Returns the record with the specified primary key value - /// - /// The Type representing a row in the result set - /// The primary key value of the record to fetch - /// The single record matching the specified primary key value - /// - /// Throws an exception if there are zero or more than one record with the specified primary key value. - /// - public T Single(object primaryKey) - { - return Single(string.Format("WHERE {0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - - /// - /// Returns the record with the specified primary key value, or the default value if not found - /// - /// The Type representing a row in the result set - /// The primary key value of the record to fetch - /// The single record matching the specified primary key value - /// - /// If there are no records with the specified primary key value, default(T) (typically null) is returned. - /// - public T SingleOrDefault(object primaryKey) - { - return SingleOrDefault(string.Format("WHERE {0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - - /// - /// Runs a query that should always return a single row. - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The single record matching the specified primary key value - /// - /// Throws an exception if there are zero or more than one matching record - /// - public T Single(string sql, params object[] args) - { - return Query(sql, args).Single(); - } - - /// - /// Runs a query that should always return either a single row, or no rows - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The single record matching the specified primary key value, or default(T) if no matching rows - public T SingleOrDefault(string sql, params object[] args) - { - return Query(sql, args).SingleOrDefault(); - } - - /// - /// Runs a query that should always return at least one return - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The first record in the result set - public T First(string sql, params object[] args) - { - return Query(sql, args).First(); - } - - /// - /// Runs a query and returns the first record, or the default value if no matching records - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The first record in the result set, or default(T) if no matching rows - public T FirstOrDefault(string sql, params object[] args) - { - return Query(sql, args).FirstOrDefault(); - } - - - /// - /// Runs a query that should always return a single row. - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and it's arguments - /// The single record matching the specified primary key value - /// - /// Throws an exception if there are zero or more than one matching record - /// - public T Single(Sql sql) - { - return Query(sql).Single(); - } - - /// - /// Runs a query that should always return either a single row, or no rows - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and it's arguments - /// The single record matching the specified primary key value, or default(T) if no matching rows - public T SingleOrDefault(Sql sql) - { - return Query(sql).SingleOrDefault(); - } - - /// - /// Runs a query that should always return at least one return - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and it's arguments - /// The first record in the result set - public T First(Sql sql) - { - return Query(sql).First(); - } - - /// - /// Runs a query and returns the first record, or the default value if no matching records - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and it's arguments - /// The first record in the result set, or default(T) if no matching rows - public T FirstOrDefault(Sql sql) - { - return Query(sql).FirstOrDefault(); - } - #endregion - - #region operation: Insert - - /// - /// Performs an SQL Insert - /// - /// The name of the table to insert into - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be inserted - /// The auto allocated primary key of the new record - public object Insert(string tableName, string primaryKeyName, object poco) - { - return Insert(tableName, primaryKeyName, true, poco); - } - - - - /// - /// Performs an SQL Insert - /// - /// The name of the table to insert into - /// The name of the primary key column of the table - /// True if the primary key is automatically allocated by the DB - /// The POCO object that specifies the column values to be inserted - /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// Inserts 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, bool autoIncrement, object poco) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - var names = new List(); - var values = new List(); - var index = 0; - foreach (var i in pd.Columns) - { - // Don't insert result columns - if (i.Value.ResultColumn) - continue; - - // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true) == 0) - { - // Setup auto increment expression - string autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo); - if (autoIncExpression != null) - { - names.Add(i.Key); - values.Add(autoIncExpression); - } - continue; - } - - names.Add(_dbType.EscapeSqlIdentifier(i.Key)); - values.Add(string.Format("{0}{1}", _paramPrefix, index++)); - AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo); - } - - string outputClause = String.Empty; - if (autoIncrement) - { - outputClause = _dbType.GetInsertOutputClause(primaryKeyName); - } - - - cmd.CommandText = string.Format("INSERT INTO {0} ({1}){2} VALUES ({3})", - _dbType.EscapeTableName(tableName), - string.Join(",", names.ToArray()), - outputClause, - string.Join(",", values.ToArray()) - ); - - if (!autoIncrement) - { - DoPreExecute(cmd); - cmd.ExecuteNonQuery(); - OnExecutedCommand(cmd); - - PocoColumn pkColumn; - if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out pkColumn)) - return pkColumn.GetValue(poco); - else - return null; - } - - - object id = _dbType.ExecuteInsert(this, cmd, primaryKeyName); - - - // Assign the ID back to the primary key property - if (primaryKeyName != null) - { - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pc.SetValue(poco, pc.ChangeType(id)); - } - } - - return id; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return null; - } - } - - /// - /// Performs an SQL Insert - /// - /// The POCO object that specifies the column values to be inserted - /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved - /// from the POCO's attributes - public object Insert(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); - } - - #endregion - - #region operation: Update - - /// - /// Performs an SQL update - /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The number of affected records - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - { - return Update(tableName, primaryKeyName, poco, primaryKeyValue, null); - } - - /// - /// Performs an SQL update - /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var sb = new StringBuilder(); - var index = 0; - var pd = PocoData.ForObject(poco, primaryKeyName); - if (columns == null) - { - foreach (var i in pd.Columns) - { - // Don't update the primary key, but grab the value if we don't have it - if (string.Compare(i.Key, primaryKeyName, true) == 0) - { - if (primaryKeyValue == null) - primaryKeyValue = i.Value.GetValue(poco); - continue; - } - - // Dont update result only columns - if (i.Value.ResultColumn) - continue; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", _dbType.EscapeSqlIdentifier(i.Key), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo); - } - } - else - { - foreach (var colname in columns) - { - var pc = pd.Columns[colname]; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", _dbType.EscapeSqlIdentifier(colname), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, pc.GetValue(poco), pc.PropertyInfo); - } - - // Grab primary key value - if (primaryKeyValue == null) - { - var pc = pd.Columns[primaryKeyName]; - primaryKeyValue = pc.GetValue(poco); - } - - } - - // Find the property info for the primary key - PropertyInfo pkpi = null; - if (primaryKeyName != null) - { - pkpi = pd.Columns[primaryKeyName].PropertyInfo; - } - - cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}", - _dbType.EscapeTableName(tableName), sb.ToString(), _dbType.EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++); - AddParam(cmd, primaryKeyValue, pkpi); - - DoPreExecute(cmd); - - // Do it - var retv = cmd.ExecuteNonQuery(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return -1; - } - } - - /// - /// Performs an SQL update - /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The number of affected rows - public int Update(string tableName, string primaryKeyName, object poco) - { - return Update(tableName, primaryKeyName, poco, null); - } - - /// - /// Performs an SQL update - /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) - { - return Update(tableName, primaryKeyName, poco, null, columns); - } - - /// - /// Performs an SQL update - /// - /// The POCO object that specifies the column values to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - public int Update(object poco, IEnumerable columns) - { - return Update(poco, null, columns); - } - - /// - /// Performs an SQL update - /// - /// The POCO object that specifies the column values to be updated - /// The number of affected rows - public int Update(object poco) - { - return Update(poco, null, null); - } - - /// - /// Performs an SQL update - /// - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The number of affected rows - public int Update(object poco, object primaryKeyValue) - { - return Update(poco, primaryKeyValue, null); - } - - /// - /// Performs an SQL update - /// - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - public int Update(object poco, object primaryKeyValue, IEnumerable columns) - { - var pd = PocoData.ForType(poco.GetType()); - return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); - } - - /// - /// Performs an SQL update - /// - /// The POCO class who's attributes specify the name of the table to update - /// The SQL update and condition clause (ie: everything after "UPDATE tablename" - /// Arguments to any embedded parameters in the SQL - /// The number of affected rows - public int Update(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("UPDATE {0} {1}", _dbType.EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - /// - /// Performs an SQL update - /// - /// The POCO class who's attributes specify the name of the table to update - /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" - /// The number of affected rows - public int Update(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("UPDATE {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - #endregion - - #region operation: Delete - - /// - /// Performs and SQL Delete - /// - /// The name of the table to delete from - /// The name of the primary key column - /// The POCO object whose primary key value will be used to delete the row - /// The number of rows affected - public int Delete(string tableName, string primaryKeyName, object poco) - { - return Delete(tableName, primaryKeyName, poco, null); - } - - /// - /// Performs and SQL Delete - /// - /// The name of the table to delete from - /// The name of the primary key column - /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) - /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) - /// The number of rows affected - 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.ForObject(poco, primaryKeyName); - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - primaryKeyValue = pc.GetValue(poco); - } - } - - // Do it - var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", _dbType.EscapeTableName(tableName), _dbType.EscapeSqlIdentifier(primaryKeyName)); - return Execute(sql, primaryKeyValue); - } - - /// - /// Performs an SQL Delete - /// - /// The POCO object specifying the table name and primary key value of the row to be deleted - /// The number of rows affected - public int Delete(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - /// - /// Performs an SQL Delete - /// - /// The POCO class whose attributes identify the table and primary key to be used in the delete - /// The value of the primary key of the row to delete - /// - public int Delete(object pocoOrPrimaryKey) - { - if (pocoOrPrimaryKey.GetType() == typeof(T)) - return Delete(pocoOrPrimaryKey); - var pd = PocoData.ForType(typeof(T)); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); - } - - /// - /// Performs an SQL Delete - /// - /// The POCO class who's attributes specify the name of the table to delete from - /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" - /// Arguments to any embedded parameters in the SQL - /// The number of affected rows - public int Delete(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("DELETE FROM {0} {1}", _dbType.EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - /// - /// Performs an SQL Delete - /// - /// The POCO class who's attributes specify the name of the table to delete from - /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" - /// The number of affected rows - public int Delete(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - #endregion - - #region operation: IsNew - - /// - /// Check if a poco represents a new row - /// - /// The name of the primary key column - /// The object instance whose "newness" is to be tested - /// True if the POCO represents a record already in the database - /// This method simply tests if the POCO's primary key column property has been set to something non-zero. - public bool IsNew(string primaryKeyName, object poco) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - object pk; - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pk = pc.GetValue(poco); - } -#if !PETAPOCO_NO_DYNAMIC - else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) - { - return true; - } -#endif - else - { - var pi = poco.GetType().GetProperty(primaryKeyName); - if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); - 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 == default(long); - else if (type == typeof(ulong)) - return (ulong)pk == default(ulong); - else if (type == typeof(int)) - return (int)pk == default(int); - else if (type == typeof(uint)) - return (uint)pk == default(uint); - else if (type == typeof(Guid)) - return (Guid)pk == default(Guid); - - // Create a default instance and compare - return pk == Activator.CreateInstance(pk.GetType()); - } - else - { - return pk == null; - } - } - - /// - /// Check if a poco represents a new row - /// - /// The object instance whose "newness" is to be tested - /// True if the POCO represents a record already in the database - /// This method simply tests if the POCO's primary key column property has been set to something non-zero. - public bool IsNew(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - if (!pd.TableInfo.AutoIncrement) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); - return IsNew(pd.TableInfo.PrimaryKey, poco); - } - #endregion - - #region operation: Save - /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update - /// - /// The name of the table to be updated - /// The name of the primary key column - /// The POCO object to be saved - public void Save(string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - { - Insert(tableName, primaryKeyName, true, poco); - } - else - { - Update(tableName, primaryKeyName, poco); - } - } - - /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update - /// - /// The POCO object to be saved - public void Save(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - #endregion - - #region operation: Multi-Poco Query/Fetch - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as a List - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as a List - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); } - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// An SQL builder object representing the query and it's arguments - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } - - /// - /// Performs a multi-poco query - /// - /// The type of objects in the returned IEnumerable - /// An array of Types representing the POCO types of the returned result set. - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCO's as an IEnumerable - public IEnumerable Query(Type[] types, object cb, string sql, params object[] args) - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - try - { - r = cmd.ExecuteReader(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } - var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r); - if (cb == null) - cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); - bool bNeedTerminator = false; - using (r) - { - while (true) - { - TRet poco; - try - { - if (!r.Read()) - break; - poco = factory(r, cb); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } - - if (poco != null) - yield return poco; - else - bNeedTerminator = true; - } - if (bNeedTerminator) - { - var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); - if (poco != null) - yield return poco; - else - yield break; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - #endregion - - #region Last Command - - /// - /// Retrieves the SQL of the last executed statement - /// - public string LastSQL { get { return _lastSql; } } - - /// - /// Retrieves the arguments to the last execute statement - /// - public object[] LastArgs { get { return _lastArgs; } } - - - /// - /// Returns a formatted string describing the last executed SQL statement and it's argument values - /// - public string LastCommand - { - get { return FormatCommand(_lastSql, _lastArgs); } - } - #endregion - - #region FormatCommand - - /// - /// Formats the contents of a DB command for display - /// - /// - /// - public string FormatCommand(IDbCommand cmd) - { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); - } - - /// - /// Formats an SQL query and it's arguments for display - /// - /// - /// - /// - public string FormatCommand(string sql, object[] args) - { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) - { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) - { - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); - } - sb.Remove(sb.Length - 1, 1); - } - return sb.ToString(); - } - #endregion - - #region Public Properties - - /* - public static IMapper Mapper - { - get; - set; - } */ - - /// - /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it - /// - public bool EnableAutoSelect - { - get; - set; - } - - /// - /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. - /// - public bool EnableNamedParams - { - get; - set; - } - - /// - /// Sets the timeout value for all SQL statements. - /// - public int CommandTimeout - { - get; - set; - } - - /// - /// Sets the timeout value for the next (and only next) SQL statement - /// - public int OneTimeCommandTimeout - { - get; - set; - } - #endregion - - #region Member Fields - // Member variables - internal DatabaseType _dbType; - string _connectionString; - string _providerName; - DbProviderFactory _factory; - IDbConnection _sharedConnection; - IDbTransaction _transaction; - int _sharedConnectionDepth; - int _transactionDepth; - bool _transactionCancelled; - string _lastSql; - object[] _lastArgs; - string _paramPrefix; - #endregion - - #region Internal operations - internal void ExecuteNonQueryHelper(IDbCommand cmd) - { - DoPreExecute(cmd); - cmd.ExecuteNonQuery(); - OnExecutedCommand(cmd); - } - - internal object ExecuteScalarHelper(IDbCommand cmd) - { - DoPreExecute(cmd); - object r = cmd.ExecuteScalar(); - OnExecutedCommand(cmd); - return r; - } - - internal void DoPreExecute(IDbCommand cmd) - { - // Setup command timeout - if (OneTimeCommandTimeout != 0) - { - cmd.CommandTimeout = OneTimeCommandTimeout; - OneTimeCommandTimeout = 0; - } - else if (CommandTimeout != 0) - { - cmd.CommandTimeout = CommandTimeout; - } - - // Call hook - OnExecutingCommand(cmd); - - // Save it - _lastSql = cmd.CommandText; - _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); - } - - #endregion - } - - - /* - Thanks to Adam Schroder (@schotime) for this. - - This extra file provides an implementation of DbProviderFactory for early versions of the Oracle - drivers that don't include include it. For later versions of Oracle, the standard OracleProviderFactory - class should work fine - - Uses reflection to load Oracle.DataAccess assembly and in-turn create connections and commands - - Currently untested. - - Usage: - - new PetaPoco.Database("", new PetaPoco.OracleProvider()) - - Or in your app/web config (be sure to change ASSEMBLYNAME to the name of your - assembly containing OracleProvider.cs) - - - - - - - - - - - - */ - - public class OracleProvider : DbProviderFactory - { - private const string _assemblyName = "Oracle.DataAccess"; - private const string _connectionTypeName = "Oracle.DataAccess.Client.OracleConnection"; - private const string _commandTypeName = "Oracle.DataAccess.Client.OracleCommand"; - private static Type _connectionType; - private static Type _commandType; - - // Required for DbProviderFactories.GetFactory() to work. - public static OracleProvider Instance = new OracleProvider(); - - public OracleProvider() - { - _connectionType = TypeFromAssembly(_connectionTypeName, _assemblyName); - _commandType = TypeFromAssembly(_commandTypeName, _assemblyName); - if (_connectionType == null) - throw new InvalidOperationException("Can't find Connection type: " + _connectionTypeName); - } - - public override DbConnection CreateConnection() - { - return (DbConnection)Activator.CreateInstance(_connectionType); - } - - public override DbCommand CreateCommand() - { - DbCommand command = (DbCommand)Activator.CreateInstance(_commandType); - - var oracleCommandBindByName = _commandType.GetProperty("BindByName"); - oracleCommandBindByName.SetValue(command, true, null); - - return command; - } - - - public static Type TypeFromAssembly(string typeName, string assemblyName) - { - try - { - // Try to get the type from an already loaded assembly - Type type = Type.GetType(typeName); - - if (type != null) - { - return type; - } - - if (assemblyName == null) - { - // No assembly was specified for the type, so just fail - string message = "Could not load type " + typeName + ". Possible cause: no assembly name specified."; - throw new TypeLoadException(message); - } - - Assembly assembly = Assembly.Load(assemblyName); - - if (assembly == null) - { - throw new InvalidOperationException("Can't find assembly: " + assemblyName); - } - - type = assembly.GetType(typeName); - - if (type == null) - { - return null; - } - - return type; - } - catch (Exception) - { - return null; - } - } - } - - /// - /// For explicit poco properties, marks the property as a column and optionally - /// supplies the DB column name. - /// - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public ColumnAttribute() - { - ForceToUtc = false; - } - - public ColumnAttribute(string Name) - { - this.Name = Name; - ForceToUtc = false; - } - - public string Name - { - get; - set; - } - - public bool ForceToUtc - { - get; - set; - } - } - - - /// - /// Poco classes marked with the Explicit attribute require all column properties to - /// be marked with the Column attribute - /// - [AttributeUsage(AttributeTargets.Class)] - public class ExplicitColumnsAttribute : Attribute - { - } - - /// - /// Use the Ignore attribute on POCO class properties that shouldn't be mapped - /// by PetaPoco. - /// - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - - /// - /// Specifies the primary key column of a poco class, whether the column is auto incrementing - /// and the sequence name for Oracle sequence columns. - /// - [AttributeUsage(AttributeTargets.Class)] - public class PrimaryKeyAttribute : Attribute - { - public PrimaryKeyAttribute(string primaryKey) - { - Value = primaryKey; - autoIncrement = true; - } - - public string Value - { - get; - private set; - } - - public string sequenceName - { - get; - set; - } - - public bool autoIncrement - { - get; - set; - } - } - - - - /// - /// Marks a poco property as a result only column that is populated in queries - /// but not used for updates or inserts. - /// - [AttributeUsage(AttributeTargets.Property)] - public class ResultColumnAttribute : ColumnAttribute - { - public ResultColumnAttribute() - { - } - - public ResultColumnAttribute(string name) - : base(name) - { - } - } - - - /// - /// Sets the DB table name to be used for a Poco class. - /// - [AttributeUsage(AttributeTargets.Class)] - public class TableNameAttribute : Attribute - { - public TableNameAttribute(string tableName) - { - Value = tableName; - } - - public string Value - { - get; - private set; - } - } - - - /// - /// Wrap strings in an instance of this class to force use of DBType.AnsiString - /// - public class AnsiString - { - /// - /// Constructs an AnsiString - /// - /// The C# string to be converted to ANSI before being passed to the DB - public AnsiString(string str) - { - Value = str; - } - - /// - /// The string value - /// - public string Value - { - get; - private set; - } - } - - - /// - /// Hold information about a column in the database. - /// - /// - /// Typically ColumnInfo is automatically populated from the attributes on a POCO object and it's properties. It can - /// however also be returned from the IMapper interface to provide your owning bindings between the DB and your POCOs. - /// - public class ColumnInfo - { - /// - /// The SQL name of the column - /// - public string ColumnName - { - get; - set; - } - - /// - /// True if this column returns a calculated value from the database and shouldn't be used in Insert and Update operations. - /// - public bool ResultColumn - { - get; - set; - } - - /// - /// True if time and date values returned through this column should be forced to UTC DateTimeKind. (no conversion is applied - the Kind of the DateTime property - /// is simply set to DateTimeKind.Utc instead of DateTimeKind.Unknown. - /// - public bool ForceToUtc - { - get; - set; - } - - /// - /// Creates and populates a ColumnInfo from the attributes of a POCO property. - /// - /// The property whose column info is required - /// A ColumnInfo instance - public static ColumnInfo FromProperty(PropertyInfo pi) - { - // Check if declaring poco has [Explicit] attribute - bool ExplicitColumns = pi.DeclaringType.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0; - - // Check for [Column]/[Ignore] Attributes - var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); - if (ExplicitColumns) - { - if (ColAttrs.Length == 0) - return null; - } - else - { - if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0) - return null; - } - - ColumnInfo ci = new ColumnInfo(); - - // Read attribute - if (ColAttrs.Length > 0) - { - var colattr = (ColumnAttribute)ColAttrs[0]; - - ci.ColumnName = colattr.Name == null ? pi.Name : colattr.Name; - ci.ForceToUtc = colattr.ForceToUtc; - if ((colattr as ResultColumnAttribute) != null) - ci.ResultColumn = true; - - } - else - { - ci.ColumnName = pi.Name; - ci.ForceToUtc = false; - ci.ResultColumn = false; - } - - return ci; - - - - } - } - - /// - /// IMapper provides a way to hook into PetaPoco's Database to POCO mapping mechanism to either - /// customize or completely replace it. - /// - /// - /// To use this functionality, instantiate a class that implements IMapper and then pass it to - /// PetaPoco through the static method Mappers.Register() - /// - public interface IMapper - { - /// - /// Get information about the table associated with a POCO class - /// - /// - /// A TableInfo instance - /// - /// This method must return a valid TableInfo. - /// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco - /// - TableInfo GetTableInfo(Type pocoType); - - /// - /// Get information about the column associated with a property of a POCO - /// - /// The PropertyInfo of the property being queried - /// A reference to a ColumnInfo instance, or null to ignore this property - /// - /// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty - /// - ColumnInfo GetColumnInfo(PropertyInfo pocoProperty); - - /// - /// Supply a function to convert a database value to the correct property value - /// - /// The target property - /// The type of data returned by the DB - /// A Func that can do the conversion, or null for no conversion - Func GetFromDbConverter(PropertyInfo TargetProperty, Type SourceType); - - /// - /// Supply a function to convert a property value into a database value - /// - /// The property to be converted - /// A Func that can do the conversion - /// - /// This conversion is only used for converting values from POCO's that are - /// being Inserted or Updated. - /// Conversion is not available for parameter values passed directly to queries. - /// - Func GetToDbConverter(PropertyInfo SourceProperty); - } - - - /// - /// This static manages registation of IMapper instances with PetaPoco - /// - public static class Mappers - { - /// - /// Registers a mapper for all types in a specific assembly - /// - /// The assembly whose types are to be managed by this mapper - /// The IMapper implementation - public static void Register(Assembly assembly, IMapper mapper) - { - RegisterInternal(assembly, mapper); - } - - /// - /// Registers a mapper for a single POCO type - /// - /// The type to be managed by this mapper - /// The IMapper implementation - public static void Register(Type type, IMapper mapper) - { - RegisterInternal(type, mapper); - } - - /// - /// Remove all mappers for all types in a specific assembly - /// - /// The assembly whose mappers are to be revoked - public static void Revoke(Assembly assembly) - { - RevokeInternal(assembly); - } - - /// - /// Remove the mapper for a specific type - /// - /// The type whose mapper is to be removed - public static void Revoke(Type type) - { - RevokeInternal(type); - } - - /// - /// Revoke an instance of a mapper - /// - /// The IMapper to be revkoed - public static void Revoke(IMapper mapper) - { - _lock.EnterWriteLock(); - try - { - foreach (var i in _mappers.Where(kvp => kvp.Value == mapper).ToList()) - _mappers.Remove(i.Key); - } - finally - { - _lock.ExitWriteLock(); - FlushCaches(); - } - } - - /// - /// Retrieve the IMapper implementation to be used for a specified POCO type - /// - /// - /// - public static IMapper GetMapper(Type t) - { - _lock.EnterReadLock(); - try - { - IMapper val; - if (_mappers.TryGetValue(t, out val)) - return val; - if (_mappers.TryGetValue(t.Assembly, out val)) - return val; - - return Singleton.Instance; - } - finally - { - _lock.ExitReadLock(); - } - } - - - static void RegisterInternal(object typeOrAssembly, IMapper mapper) - { - _lock.EnterWriteLock(); - try - { - _mappers.Add(typeOrAssembly, mapper); - } - finally - { - _lock.ExitWriteLock(); - FlushCaches(); - } - } - - static void RevokeInternal(object typeOrAssembly) - { - _lock.EnterWriteLock(); - try - { - _mappers.Remove(typeOrAssembly); - } - finally - { - _lock.ExitWriteLock(); - FlushCaches(); - } - } - - static void FlushCaches() - { - // Whenever a mapper is registered or revoked, we have to assume any generated code is no longer valid. - // Since this should be a rare occurance, the simplest approach is to simply dump everything and start over. - MultiPocoFactory.FlushCaches(); - PocoData.FlushCaches(); - } - - static Dictionary _mappers = new Dictionary(); - static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - } - - /// - /// Holds the results of a paged request. - /// - /// The type of Poco in the returned result set - public class Page - { - /// - /// The current page number contained in this page of result set - /// - public long CurrentPage - { - get; - set; - } - - /// - /// The total number of pages in the full result set - /// - public long TotalPages - { - get; - set; - } - - /// - /// The total number of records in the full result set - /// - public long TotalItems - { - get; - set; - } - - /// - /// The number of items per page - /// - public long ItemsPerPage - { - get; - set; - } - - /// - /// The actual records on this page - /// - public List Items - { - get; - set; - } - - /// - /// User property to hold anything. - /// - public object Context - { - get; - set; - } - } - - /// - /// A simple helper class for build SQL statements - /// - public class Sql - { - /// - /// Default, empty constructor - /// - public Sql() - { - } - - /// - /// Construct an SQL statement with the supplied SQL and arguments - /// - /// The SQL statement or fragment - /// Arguments to any parameters embedded in the SQL - public Sql(string sql, params object[] args) - { - _sql = sql; - _args = args; - } - - /// - /// Instantiate a new SQL Builder object. Weirdly implemented as a property but makes - /// for more elegantly readble fluent style construction of SQL Statements - /// eg: db.Query(Sql.Builder.Append(....)) - /// - public static Sql Builder - { - get { return new Sql(); } - } - - string _sql; - object[] _args; - Sql _rhs; - string _sqlFinal; - object[] _argsFinal; - - private void Build() - { - // already built? - if (_sqlFinal != null) - return; - - // Build it - var sb = new StringBuilder(); - var args = new List(); - Build(sb, args, null); - _sqlFinal = sb.ToString(); - _argsFinal = args.ToArray(); - } - - /// - /// Returns the final SQL statement represented by this builder - /// - public string SQL - { - get - { - Build(); - return _sqlFinal; - } - } - - /// - /// Gets the complete, final set of arguments collected by this builder. - /// - public object[] Arguments - { - get - { - Build(); - return _argsFinal; - } - } - - /// - /// Append another SQL builder instance to the right-hand-side of this SQL builder - /// - /// A reference to another SQL builder instance - /// A reference to this builder, allowing for fluent style concatenation - public Sql Append(Sql sql) - { - if (_rhs != null) - _rhs.Append(sql); - else - _rhs = sql; - - return this; - } - - /// - /// Append an SQL fragement to the right-hand-side of this SQL builder - /// - /// The SQL statement or fragment - /// Arguments to any parameters embedded in the SQL - /// A reference to this builder, allowing for fluent style concatenation - public Sql Append(string sql, params object[] args) - { - return Append(new Sql(sql, args)); - } - - static bool Is(Sql sql, string sqltype) - { - return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); - } - - private void Build(StringBuilder sb, List args, Sql lhs) - { - if (!String.IsNullOrEmpty(_sql)) - { - // Add SQL to the string - if (sb.Length > 0) - { - sb.Append("\n"); - } - - var sql = ParametersHelper.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); - } - - /// - /// Appends an SQL WHERE clause to this SQL builder - /// - /// The condition of the WHERE clause - /// Arguments to any parameters embedded in the supplied SQL - /// A reference to this builder, allowing for fluent style concatenation - public Sql Where(string sql, params object[] args) - { - return Append(new Sql("WHERE (" + sql + ")", args)); - } - - /// - /// Appends an SQL ORDER BY clause to this SQL builder - /// - /// A collection of SQL column names to order by - /// A reference to this builder, allowing for fluent style concatenation - public Sql OrderBy(params object[] columns) - { - return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL SELECT clause to this SQL builder - /// - /// A collection of SQL column names to select - /// A reference to this builder, allowing for fluent style concatenation - public Sql Select(params object[] columns) - { - return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL FROM clause to this SQL builder - /// - /// A collection of table names to be used in the FROM clause - /// A reference to this builder, allowing for fluent style concatenation - public Sql From(params object[] tables) - { - return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL GROUP BY clause to this SQL builder - /// - /// A collection of column names to be grouped by - /// A reference to this builder, allowing for fluent style concatenation - public Sql GroupBy(params object[] columns) - { - return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - private SqlJoinClause Join(string JoinType, string table) - { - return new SqlJoinClause(Append(new Sql(JoinType + table))); - } - - /// - /// Appends an SQL INNER JOIN clause to this SQL builder - /// - /// The name of the table to join - /// A reference an SqlJoinClause through which the join condition can be specified - public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); } - - /// - /// Appends an SQL LEFT JOIN clause to this SQL builder - /// - /// The name of the table to join - /// A reference an SqlJoinClause through which the join condition can be specified - public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); } - - /// - /// The SqlJoinClause is a simple helper class used in the construction of SQL JOIN statements with the SQL builder - /// - public class SqlJoinClause - { - private readonly Sql _sql; - - public SqlJoinClause(Sql sql) - { - _sql = sql; - } - - /// - /// Appends a SQL ON clause after a JOIN statement - /// - /// The ON clause to be appended - /// Arguments to any parameters embedded in the supplied SQL - /// A reference to the parent SQL builder, allowing for fluent style concatenation - public Sql On(string onClause, params object[] args) - { - return _sql.Append("ON " + onClause, args); - } - } - } - - - /// - /// StandardMapper is the default implementation of IMapper used by PetaPoco - /// - public class StandardMapper : IMapper - { - /// - /// Constructs a TableInfo for a POCO by reading its attribute data - /// - /// The POCO Type - /// - public TableInfo GetTableInfo(Type pocoType) - { - return TableInfo.FromPoco(pocoType); - } - - /// - /// Constructs a ColumnInfo for a POCO property by reading its attribute data - /// - /// - /// - public ColumnInfo GetColumnInfo(PropertyInfo pocoProperty) - { - return ColumnInfo.FromProperty(pocoProperty); - } - - public Func GetFromDbConverter(PropertyInfo TargetProperty, Type SourceType) - { - return null; - } - - public Func GetToDbConverter(PropertyInfo SourceProperty) - { - return null; - } - } - - /// - /// Use by IMapper to override table bindings for an object - /// - public class TableInfo - { - /// - /// The database table name - /// - public string TableName - { - get; - set; - } - - /// - /// The name of the primary key column of the table - /// - public string PrimaryKey - { - get; - set; - } - - /// - /// True if the primary key column is an auto-incrementing - /// - public bool AutoIncrement - { - get; - set; - } - - /// - /// The name of the sequence used for auto-incrementing Oracle primary key fields - /// - public string SequenceName - { - get; - set; - } - - - /// - /// Creates and populates a TableInfo from the attributes of a POCO - /// - /// The POCO type - /// A TableInfo instance - public static TableInfo FromPoco(Type t) - { - TableInfo ti = new TableInfo(); - - // Get the table name - var a = t.GetCustomAttributes(typeof(TableNameAttribute), true); - ti.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value; - - // Get the primary key - a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); - ti.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value; - ti.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName; - ti.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement; - - return ti; - } - } - - - public interface ITransaction : IDisposable - { - void Complete(); - } - - /// - /// Transaction object helps maintain transaction depth counts - /// - public class Transaction : ITransaction - { - 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; - } - - namespace Internal - { - /// - /// Base class for DatabaseType handlers - provides default/common handling for different database engines - /// - abstract class DatabaseType - { - /// - /// Returns the prefix used to delimit parameters in SQL query strings. - /// - /// - /// - public virtual string GetParameterPrefix(string ConnectionString) - { - return "@"; - } - - /// - /// Converts a supplied C# object value into a value suitable for passing to the database - /// - /// The value to convert - /// The converted value - public virtual object MapParameterValue(object value) - { - // Cast bools to integer - if (value.GetType() == typeof(bool)) - { - return ((bool)value) ? 1 : 0; - } - - // Leave it - return value; - } - - /// - /// Called immediately before a command is executed, allowing for modification of the IDbCommand before it's passed to the database provider - /// - /// - public virtual void PreExecute(IDbCommand cmd) - { - } - - /// - /// Builds an SQL query suitable for performing page based queries to the database - /// - /// The number of rows that should be skipped by the query - /// The number of rows that should be retruend by the query - /// The original SQL query after being parsed into it's component parts - /// Arguments to any embedded parameters in the SQL query - /// The final SQL query that should be executed. - public virtual string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) - { - var sql = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", parts.sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - return sql; - } - - /// - /// Returns an SQL Statement that can check for the existance of a row in the database. - /// - /// - public virtual string GetExistsSql() - { - return "SELECT COUNT(*) FROM {0} WHERE {1}"; - } - - /// - /// Escape a tablename into a suitable format for the associated database provider. - /// - /// The name of the table (as specified by the client program, or as attributes on the associated POCO class. - /// The escaped table name - public virtual string EscapeTableName(string tableName) - { - // Assume table names with "dot" are already escaped - return tableName.IndexOf('.') >= 0 ? tableName : EscapeSqlIdentifier(tableName); - } - - /// - /// Escape and arbitary SQL identifier into a format suitable for the associated database provider - /// - /// The SQL identifier to be escaped - /// The escaped identifier - public virtual string EscapeSqlIdentifier(string str) - { - return string.Format("[{0}]", str); - } - - /// - /// Return an SQL expression that can be used to populate the primary key column of an auto-increment column. - /// - /// Table info describing the table - /// An SQL expressions - /// See the Oracle database type for an example of how this method is used. - public virtual string GetAutoIncrementExpression(TableInfo ti) - { - return null; - } - - /// - /// Returns an SQL expression that can be used to specify the return value of auto incremented columns. - /// - /// The primary key of the row being inserted. - /// An expression describing how to return the new primary key value - /// See the SQLServer database provider for an example of how this method is used. - public virtual string GetInsertOutputClause(string primaryKeyName) - { - return string.Empty; - } - - /// - /// Performs an Insert operation - /// - /// The calling Database object - /// The insert command to be executed - /// The primary key of the table being inserted into - /// The ID of the newly inserted record - public virtual object ExecuteInsert(Database db, IDbCommand cmd, string PrimaryKeyName) - { - cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - return db.ExecuteScalarHelper(cmd); - } - - - /// - /// Look at the type and provider name being used and instantiate a suitable DatabaseType instance. - /// - /// - /// - /// - public static DatabaseType Resolve(string TypeName, string ProviderName) - { - // Try using type name first (more reliable) - if (TypeName.StartsWith("MySql")) - return Singleton.Instance; - if (TypeName.StartsWith("SqlCe")) - return Singleton.Instance; - if (TypeName.StartsWith("Npgsql") || TypeName.StartsWith("PgSql")) - return Singleton.Instance; - if (TypeName.StartsWith("Oracle")) - return Singleton.Instance; - if (TypeName.StartsWith("SQLite")) - return Singleton.Instance; - if (TypeName.StartsWith("System.Data.SqlClient.")) - return Singleton.Instance; - if (TypeName.StartsWith("Firebird")) - return Singleton.Instance; - - // Try again with provider name - if (ProviderName.IndexOf("MySql", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - if (ProviderName.IndexOf("SqlServerCe", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - if (ProviderName.IndexOf("pgsql", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - if (ProviderName.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - if (ProviderName.IndexOf("SQLite", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - if (ProviderName.IndexOf("Firebird", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; - - // Assume SQL Server - return Singleton.Instance; - } - - } - - internal class ExpandoColumn : PocoColumn - { - public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName] = val; } - public override object GetValue(object target) - { - object val = null; - (target as IDictionary).TryGetValue(ColumnName, out val); - return val; - } - public override object ChangeType(object val) { return val; } - } - - - class MultiPocoFactory - { - // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call - List _delegates; - public Delegate GetItem(int index) { return _delegates[index]; } - - // Automagically guess the property relationships between various POCOs and create a delegate that will set them up - public static object GetAutoMapper(Type[] types) - { - // Build a key - var key = new ArrayKey(types); - - return AutoMappers.Get(key, () => - { - // Create a method - var m = new DynamicMethod("petapoco_automapper", types[0], types, true); - var il = m.GetILGenerator(); - - for (int i = 1; i < types.Length; i++) - { - bool handled = false; - for (int j = i - 1; j >= 0; j--) - { - // Find the property - var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p; - if (candidates.Count() == 0) - continue; - if (candidates.Count() > 1) - throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j])); - - // Generate code - il.Emit(OpCodes.Ldarg_S, j); - il.Emit(OpCodes.Ldarg_S, i); - il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true)); - handled = true; - } - - if (!handled) - throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); - } - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ret); - - // Cache it - return m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray())); - } - ); - } - - // Find the split point in a result set for two different pocos and return the poco factory for the first - static Delegate FindSplitPoint(Type typeThis, Type typeNext, string ConnectionString, string sql, IDataReader r, ref int pos) - { - // Last? - if (typeNext == null) - return PocoData.ForType(typeThis).GetFactory(sql, ConnectionString, pos, r.FieldCount - pos, r); - - // Get PocoData for the two types - PocoData pdThis = PocoData.ForType(typeThis); - PocoData pdNext = PocoData.ForType(typeNext); - - // Find split point - int firstColumn = pos; - var usedColumns = new Dictionary(); - for (; pos < r.FieldCount; pos++) - { - // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next - string fieldName = r.GetName(pos); - if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) - { - return pdThis.GetFactory(sql, ConnectionString, firstColumn, pos - firstColumn, r); - } - usedColumns.Add(fieldName, true); - } - - throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); - } - - // Create a multi-poco factory - static Func CreateMultiPocoFactory(Type[] types, string ConnectionString, string sql, IDataReader r) - { - var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory)); - var il = m.GetILGenerator(); - - // Load the callback - il.Emit(OpCodes.Ldarg_2); - - // Call each delegate - var dels = new List(); - int pos = 0; - for (int i = 0; i < types.Length; i++) - { - // Add to list of delegates to call - var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, ConnectionString, sql, r, ref pos); - dels.Add(del); - - // Get the delegate - il.Emit(OpCodes.Ldarg_0); // callback,this - il.Emit(OpCodes.Ldc_I4, i); // callback,this,Index - il.Emit(OpCodes.Callvirt, typeof(MultiPocoFactory).GetMethod("GetItem")); // callback,Delegate - il.Emit(OpCodes.Ldarg_1); // callback,delegate, datareader - - // Call Invoke - var tDelInvoke = del.GetType().GetMethod("Invoke"); - il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack - } - - // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done - il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new Type[] { typeof(TRet) }).ToArray()).GetMethod("Invoke")); - il.Emit(OpCodes.Ret); - - // Finish up - return (Func)m.CreateDelegate(typeof(Func), new MultiPocoFactory() { _delegates = dels }); - } - - // Various cached stuff - static Cache, string, string>, object> MultiPocoFactories = new Cache, string, string>, object>(); - static Cache, object> AutoMappers = new Cache, object>(); - - internal static void FlushCaches() - { - MultiPocoFactories.Flush(); - AutoMappers.Flush(); - } - - // Get (or create) the multi-poco factory for a query - public static Func GetFactory(Type[] types, string ConnectionString, string sql, IDataReader r) - { - var key = Tuple.Create, string, string>(typeof(TRet), new ArrayKey(types), ConnectionString, sql); - - return (Func)MultiPocoFactories.Get(key, () => - { - return CreateMultiPocoFactory(types, ConnectionString, sql, r); - } - ); - } - - } - - - internal class PocoColumn - { - public string ColumnName; - public PropertyInfo PropertyInfo; - public bool ResultColumn; - public bool ForceToUtc; - public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); } - public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); } - public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); } - } - - class PocoData - { - public static PocoData ForObject(object o, string primaryKeyName) - { - var t = o.GetType(); -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - { - var pd = new PocoData(); - pd.TableInfo = new TableInfo(); - pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); - pd.TableInfo.PrimaryKey = primaryKeyName; - pd.TableInfo.AutoIncrement = true; - foreach (var col in (o as IDictionary).Keys) - { - if (col != primaryKeyName) - pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); - } - return pd; - } - else -#endif - return ForType(t); - } - - public static PocoData ForType(Type t) - { -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - throw new InvalidOperationException("Can't use dynamic types with this method"); -#endif - - return _pocoDatas.Get(t, () => new PocoData(t)); - } - - public PocoData() - { - } - - public PocoData(Type t) - { - type = t; - - // Get the mapper for this type - var mapper = Mappers.GetMapper(t); - - // Get the table info - TableInfo = mapper.GetTableInfo(t); - - // Work out bound properties - Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var pi in t.GetProperties()) - { - ColumnInfo ci = mapper.GetColumnInfo(pi); - if (ci == null) - continue; - - var pc = new PocoColumn(); - pc.PropertyInfo = pi; - pc.ColumnName = ci.ColumnName; - pc.ResultColumn = ci.ResultColumn; - pc.ForceToUtc = ci.ForceToUtc; - - // Store it - Columns.Add(pc.ColumnName, pc); - } - - // Build column list for automatic select - QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray(); - - } - - static bool IsIntegralType(Type t) - { - var tc = Type.GetTypeCode(t); - return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; - } - - // Create factory function that can convert a IDataReader record into a POCO - public Delegate GetFactory(string sql, string connString, int firstColumn, int countColumns, IDataReader r) - { - // Check cache - var key = Tuple.Create(sql, connString, firstColumn, countColumns); - - return PocoFactories.Get(key, () => - { - // Create the method - var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true); - var il = m.GetILGenerator(); - var mapper = Mappers.GetMapper(type); - -#if !PETAPOCO_NO_DYNAMIC - if (type == typeof(object)) - { - // var poco=new T() - il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj - - MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - var srcType = r.GetFieldType(i); - - il.Emit(OpCodes.Dup); // obj, obj - il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname - - // Get the converter - Func converter = mapper.GetFromDbConverter((PropertyInfo)null, srcType); - - /* - if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - */ - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - // r[i] - il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr - il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value - - // Convert DBNull to null - il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value - il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) - var lblNotNull = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value - il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? - if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, - il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null - if (converter != null) - { - var lblReady = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblReady); - il.MarkLabel(lblNotNull); - il.Emit(OpCodes.Callvirt, fnInvoke); - il.MarkLabel(lblReady); - } - else - { - il.MarkLabel(lblNotNull); - } - - il.Emit(OpCodes.Callvirt, fnAdd); - } - } - else -#endif - if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) - { - // Do we need to install a converter? - var srcType = r.GetFieldType(0); - var converter = GetConverter(mapper, null, srcType, type); - - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool - var lblCont = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblCont); - il.Emit(OpCodes.Ldnull); // null - var lblFin = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblFin); - - il.MarkLabel(lblCont); - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnGetValue); // value - - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); - - il.MarkLabel(lblFin); - il.Emit(OpCodes.Unbox_Any, type); // value converted - } - else - { - // var poco=new T() - il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; 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? - var converter = GetConverter(mapper, pc, srcType, dstType); - - // 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 - - // Convert to Nullable - if (Nullable.GetUnderlyingType(dstType) != null) - { - il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); - } - - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - Handled = true; - } - } - - // Not so fast - if (!Handled) - { - // Setup stack for call to converter - AddConverterToStack(il, 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(true)); // poco - } - - il.MarkLabel(lblNext); - } - - var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - if (fnOnLoaded != null) - { - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Callvirt, fnOnLoaded); - } - } - - il.Emit(OpCodes.Ret); - - // Cache it, return it - return m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - } - ); - } - - private static void AddConverterToStack(ILGenerator il, Func converter) - { - if (converter != null) - { - // Add the converter - int converterIndex = _converters.Count; - _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 - } - } - - private static Func GetConverter(IMapper mapper, PocoColumn pc, Type srcType, Type dstType) - { - Func converter = null; - - // Get converter from the mapper - if (pc != null) - { - converter = mapper.GetFromDbConverter(pc.PropertyInfo, srcType); - if (converter != null) - return converter; - } - - // Standard DateTime->Utc mapper - if (pc != null && pc.ForceToUtc && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) - { - return delegate (object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - } - - // Forced type conversion including integral types -> enum - if (dstType.IsEnum && IsIntegralType(srcType)) - { - if (srcType != typeof(int)) - { - return delegate (object src) { return Convert.ChangeType(src, typeof(int), null); }; - } - } - else if (!dstType.IsAssignableFrom(srcType)) - { - if (dstType.IsEnum && srcType == typeof(string)) - { - return delegate (object src) { return EnumMapper.EnumFromString(dstType, (string)src); }; - } - else - { - return delegate (object src) { return Convert.ChangeType(src, dstType, null); }; - } - } - - return null; - } - - - static T RecurseInheritedTypes(Type t, Func cb) - { - while (t != null) - { - T info = cb(t); - if (info != null) - return info; - t = t.BaseType; - } - return default(T); - } - - - internal static void FlushCaches() - { - _pocoDatas.Flush(); - } - - static Cache _pocoDatas = new Cache(); - static List> _converters = new List>(); - static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); - static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); - static FieldInfo fldConverters = typeof(PocoData).GetField("_converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); - static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); - static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); - public Type type; - public string[] QueryColumns { get; private set; } - public TableInfo TableInfo { get; private set; } - public Dictionary Columns { get; private set; } - Cache, Delegate> PocoFactories = new Cache, Delegate>(); - } - - - class ArrayKey - { - public ArrayKey(T[] keys) - { - // Store the keys - _keys = keys; - - // Calculate the hashcode - _hashCode = 17; - foreach (var k in keys) - { - _hashCode = _hashCode * 23 + (k == null ? 0 : k.GetHashCode()); - } - } - - T[] _keys; - int _hashCode; - - bool Equals(ArrayKey other) - { - if (other == null) - return false; - - if (other._hashCode != _hashCode) - return false; - - if (other._keys.Length != _keys.Length) - return false; - - for (int i = 0; i < _keys.Length; i++) - { - if (!object.Equals(_keys[i], other._keys[i])) - return false; - } - - return true; - } - - public override bool Equals(object obj) - { - return Equals(obj as ArrayKey); - } - - public override int GetHashCode() - { - return _hashCode; - } - - } - - static class AutoSelectHelper - { - public static string AddSelectClause(DatabaseType DatabaseType, string sql) - { - if (sql.StartsWith(";")) - return sql.Substring(1); - - if (!rxSelect.IsMatch(sql)) - { - var pd = PocoData.ForType(typeof(T)); - var tableName = DatabaseType.EscapeTableName(pd.TableInfo.TableName); - string cols = pd.Columns.Count != 0 ? string.Join(", ", (from c in pd.QueryColumns select tableName + "." + DatabaseType.EscapeSqlIdentifier(c)).ToArray()) : "NULL"; - if (!rxFrom.IsMatch(sql)) - sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql); - else - sql = string.Format("SELECT {0} {1}", cols, sql); - } - return sql; - } - - static Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - static Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - } - - class Cache - { - Dictionary _map = new Dictionary(); - ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - - public int Count - { - get - { - return _map.Count; - } - } - - public TValue Get(TKey key, Func factory) - { - // Check cache - _lock.EnterReadLock(); - TValue val; - try - { - if (_map.TryGetValue(key, out val)) - return val; - } - finally - { - _lock.ExitReadLock(); - } - - - // Cache it - _lock.EnterWriteLock(); - try - { - // Check again - if (_map.TryGetValue(key, out val)) - return val; - - // Create it - val = factory(); - - // Store it - _map.Add(key, val); - - // Done - return val; - } - finally - { - _lock.ExitWriteLock(); - } - } - - public void Flush() - { - // Cache it - _lock.EnterWriteLock(); - try - { - _map.Clear(); - } - finally - { - _lock.ExitWriteLock(); - } - - } - } - - internal static class EnumMapper - { - public static object EnumFromString(Type enumType, string value) - { - Dictionary map = _types.Get(enumType, () => - { - var values = Enum.GetValues(enumType); - - var newmap = new Dictionary(values.Length, StringComparer.InvariantCultureIgnoreCase); - - foreach (var v in values) - { - newmap.Add(v.ToString(), v); - } - - return newmap; - }); - - - return map[value]; - } - - static Cache> _types = new Cache>(); - } - - internal static class PagingHelper - { - public struct SQLParts - { - public string sql; - public string sqlCount; - public string sqlSelectRemoved; - public string sqlOrderBy; - } - - public static bool SplitSQL(string sql, out SQLParts parts) - { - parts.sql = sql; - parts.sqlSelectRemoved = null; - parts.sqlCount = null; - parts.sqlOrderBy = null; - - // Extract the columns from "SELECT FROM" - var m = rxColumns.Match(sql); - if (!m.Success) - return false; - - // Save column list and replace with COUNT(*) - Group g = m.Groups[1]; - parts.sqlSelectRemoved = sql.Substring(g.Index); - - if (rxDistinct.IsMatch(parts.sqlSelectRemoved)) - parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); - else - parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); - - - // Look for the last "ORDER BY " clause not part of a ROW_NUMBER expression - m = rxOrderBy.Match(parts.sqlCount); - if (!m.Success) - { - parts.sqlOrderBy = null; - } - else - { - g = m.Groups[0]; - parts.sqlOrderBy = g.ToString(); - parts.sqlCount = parts.sqlCount.Substring(0, g.Index) + parts.sqlCount.Substring(g.Index + g.Length); - } - - return true; - } - - public static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - } - - internal static class ParametersHelper - { - // Helper to handle named parameters from object properties - public static string ProcessParams(string sql, object[] args_src, List args_dest) - { - return rxParams.Replace(sql, m => - { - string param = m.Value.Substring(1); - - object arg_val; - - 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)); - arg_val = args_src[paramIndex]; - } - else - { - // Look for a property on one of the arguments with this name - bool found = false; - arg_val = null; - foreach (var o in args_src) - { - var pi = o.GetType().GetProperty(param); - if (pi != null) - { - arg_val = 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)); - } - - // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && - (arg_val as byte[]) == null) - { - var sb = new StringBuilder(); - foreach (var i in arg_val as System.Collections.IEnumerable) - { - sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString()); - args_dest.Add(i); - } - return sb.ToString(); - } - else - { - args_dest.Add(arg_val); - return "@" + (args_dest.Count - 1).ToString(); - } - } - ); - } - - static Regex rxParams = new Regex(@"(? where T : new() - { - public static T Instance = new T(); - } - - } - - namespace DatabaseTypes - { - class FirebirdDatabaseType : DatabaseType - { - public override string GetParameterPrefix(string ConnectionString) - { - return "@"; - } - - public override string EscapeSqlIdentifier(string str) - { - return string.Format("\"{0}\"", str); - } - - public override string GetExistsSql() - { - return "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; - } - } - - class MySqlDatabaseType : DatabaseType - { - public override string GetParameterPrefix(string ConnectionString) - { - if (ConnectionString != null && ConnectionString.IndexOf("Allow User Variables=true") >= 0) - return "?"; - else - return "@"; - } - - public override string EscapeSqlIdentifier(string str) - { - return string.Format("`{0}`", str); - } - - public override string GetExistsSql() - { - return "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; - } - } - - class OracleDatabaseType : DatabaseType - { - public override string GetParameterPrefix(string ConnectionString) - { - return ":"; - } - - public override void PreExecute(IDbCommand cmd) - { - cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null); - } - - public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) - { - if (parts.sqlSelectRemoved.StartsWith("*")) - throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - // Same deal as SQL Server - return Singleton.Instance.BuildPageQuery(skip, take, parts, ref args); - } - - public override string EscapeSqlIdentifier(string str) - { - return string.Format("\"{0}\"", str.ToUpperInvariant()); - } - - public override string GetAutoIncrementExpression(TableInfo ti) - { - if (!string.IsNullOrEmpty(ti.SequenceName)) - return string.Format("{0}.nextval", ti.SequenceName); - - return null; - } - - public override object ExecuteInsert(Database db, IDbCommand cmd, string PrimaryKeyName) - { - if (PrimaryKeyName != null) - { - cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(PrimaryKeyName)); - var param = cmd.CreateParameter(); - param.ParameterName = ":newid"; - param.Value = DBNull.Value; - param.Direction = ParameterDirection.ReturnValue; - param.DbType = DbType.Int64; - cmd.Parameters.Add(param); - db.ExecuteNonQueryHelper(cmd); - return param.Value; - } - else - { - db.ExecuteNonQueryHelper(cmd); - return -1; - } - } - - } - - class PostgreSQLDatabaseType : DatabaseType - { - public override object MapParameterValue(object value) - { - // Don't map bools to ints in PostgreSQL - if (value.GetType() == typeof(bool)) - return value; - - return base.MapParameterValue(value); - } - - public override string EscapeSqlIdentifier(string str) - { - return string.Format("\"{0}\"", str); - } - - public override object ExecuteInsert(Database db, System.Data.IDbCommand cmd, string PrimaryKeyName) - { - if (PrimaryKeyName != null) - { - cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(PrimaryKeyName)); - return db.ExecuteScalarHelper(cmd); - } - else - { - db.ExecuteNonQueryHelper(cmd); - return -1; - } - } - } - - class SQLiteDatabaseType : DatabaseType - { - public override object MapParameterValue(object value) - { - if (value.GetType() == typeof(uint)) - return (long)((uint)value); - - return base.MapParameterValue(value); - } - - public override object ExecuteInsert(Database db, System.Data.IDbCommand cmd, string PrimaryKeyName) - { - if (PrimaryKeyName != null) - { - cmd.CommandText += ";\nSELECT last_insert_rowid();"; - return db.ExecuteScalarHelper(cmd); - } - else - { - db.ExecuteNonQueryHelper(cmd); - return -1; - } - } - - public override string GetExistsSql() - { - return "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; - } - - } - - class SqlServerCEDatabaseType : DatabaseType - { - public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) - { - var sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", parts.sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - return sqlPage; - } - - public override object ExecuteInsert(Database db, System.Data.IDbCommand cmd, string PrimaryKeyName) - { - db.ExecuteNonQueryHelper(cmd); - return db.ExecuteScalar("SELECT @@@IDENTITY AS NewID;"); - } - - } - - class SqlServerDatabaseType : DatabaseType - { - public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) - { - parts.sqlSelectRemoved = PagingHelper.rxOrderBy.Replace(parts.sqlSelectRemoved, "", 1); - if (PagingHelper.rxDistinct.IsMatch(parts.sqlSelectRemoved)) - { - parts.sqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.sqlSelectRemoved + ") peta_inner"; - } - var sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - parts.sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : parts.sqlOrderBy, parts.sqlSelectRemoved, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip + take }).ToArray(); - - return sqlPage; - } - - public override object ExecuteInsert(Database db, System.Data.IDbCommand cmd, string PrimaryKeyName) - { - return db.ExecuteScalarHelper(cmd); - } - - public override string GetExistsSql() - { - return "IF EXISTS (SELECT 1 FROM {0} WHERE {1}) SELECT 1 ELSE SELECT 0"; - } - - public override string GetInsertOutputClause(string primaryKeyName) - { - return String.Format(" OUTPUT INSERTED.[{0}]", primaryKeyName); - } - } - - } -} diff --git a/MyepWeb/App-Code/Utility/PetaSchema.cs b/MyepWeb/App-Code/Utility/PetaSchema.cs deleted file mode 100644 index 291631b..0000000 --- a/MyepWeb/App-Code/Utility/PetaSchema.cs +++ /dev/null @@ -1,459 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Reflection; -using System.Text; -using PetaPoco; - -namespace PetaSchema -{ - public class DbSchema - { - private readonly Database _db; - - public DbSchema(Database db) - { - _db = db; - DefaultSchema = "dbo"; - DefaultStringType = "varchar"; - DefaultStringLength = 50; - Tables = new List(); - } - - public string ConnectionString { get; set; } - public string DefaultSchema { get; set; } - public string DefaultStringType { get; set; } - public int DefaultStringLength { get; set; } - public List Tables { get; set; } - - public void AddTable(Type type) - { - if (type == null) - return; - - var table = new DbTable(); - - //Name, Schema - var ta = GetAttribute(type); - if (ta != null) - { - table.Schema = ta.Schema; - table.Name = ta.Name; - } - if (string.IsNullOrWhiteSpace(table.Name)) - { - table.Name = type.Name; - } - if (string.IsNullOrWhiteSpace(table.Schema)) - { - table.Schema = DefaultSchema; - } - - //Columns - foreach (var prop in type.GetProperties()) - { - AddColumn(table, prop); - AddIndex(table, prop); - } - - //Key - if (!table.Columns.Any(x => x.IsKey)) - throw new ApplicationException("Table must have a key: " + table.Name); - - Tables.Add(table); - } - - private void AddColumn(DbTable table, PropertyInfo prop) - { - //remove existing column - var existing = table.Columns.FirstOrDefault(x => x.Name == prop.Name); - if (existing != null) - { - table.Columns.Remove(existing); - } - - var column = new DbColumn(); - - //Name - column.Name = prop.Name; - column.Type = prop.PropertyType; - column.DbType = GetDbType(column.Type.ToString()); - - //Nullable - if (prop.PropertyType != typeof(string) && !prop.PropertyType.Name.Contains("Nullable`1")) - { - column.IsNullable = false; - } - - var ra = GetAttribute(prop); - if (ra != null) - { - column.IsNullable = false; - } - - //Length - var sl = GetAttribute(prop); - if (sl != null) - { - column.Length = sl.MaximumLength; - } - if (!column.Length.HasValue && column.Type == typeof(string)) - { - column.Length = DefaultStringLength; - } - - //Key - var ka = GetAttribute(prop); - if (ka != null) - { - column.IsKey = true; - column.IsNullable = false; - } - - var ga = GetAttribute(prop); - if (ga != null) - { - if (ga.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity) - column.IsIdentity = true; - } - - table.Columns.Add(column); - } - - private void AddIndex(DbTable table, PropertyInfo prop) - { - var indexAttr = GetAttribute(prop); - if (indexAttr == null) - return; - - var index = table.Indexes.SingleOrDefault(x => x.Name == indexAttr.Name); - if (index == null) - { - index = new DbIndex - { - Name = indexAttr.Name, - Schema = table.Schema, - Table = table.Name, - IsUnique = indexAttr.IsUnique, - }; - table.Indexes.Add(index); - } - - index.Columns.Add(prop.Name); - } - - public string GenerateSql() - { - var sb = new StringBuilder(); - sb.AppendFormat("-- Migration Generated: {0:M/d/yyyy h:mm:ss tt} --\r\n", DateTime.Now); - - var dbtables = GetDatabaseTables(); - var dbindexes = GetDatabaseIndexes(); - - foreach (var table in Tables) - { - var tableExists = dbtables.Any(x => x.Schema == table.Schema && x.Name == table.Name); - - sb.AppendFormat("-- Table: {0} --\r\n", table.Name); - AppendTableSql(sb, table, tableExists); - AppendIndexSql(sb, table, dbindexes); - } - - return sb.ToString(); - } - - public void Execute() - { - _db.Execute(GenerateSql()); - } - - private List GetDatabaseTables() - { - return _db.Query(@" - SELECT TABLE_SCHEMA AS [Schema], TABLE_NAME AS [Name] - FROM information_schema.tables - WHERE TABLE_TYPE = 'BASE TABLE' - ").ToList(); - } - - private List GetDatabaseIndexes() - { - //REF: http://stackoverflow.com/questions/765867/list-of-all-index-index-columns-in-sql-server-db - var indexes = _db.Query(@" - SELECT - schema_name(schema_id) as SchemaName, OBJECT_NAME(si.object_id) as TableName, si.name as IndexName, - (CASE is_primary_key WHEN 1 THEN 'PK' ELSE '' END) as PK, - (CASE is_unique WHEN 1 THEN '1' ELSE '0' END)+' '+ - (CASE si.type WHEN 1 THEN 'C' WHEN 3 THEN 'X' ELSE 'B' END)+' '+ -- B=basic, C=Clustered, X=XML - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,1,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,2,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,3,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,4,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,5,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - (CASE INDEXKEY_PROPERTY(si.object_id,index_id,6,'IsDescending') WHEN 0 THEN 'A' WHEN 1 THEN 'D' ELSE '' END)+ - '' as 'Type', - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,1) as Key1, - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,2) as Key2, - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,3) as Key3, - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,4) as Key4, - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,5) as Key5, - INDEX_COL(schema_name(schema_id)+'.'+OBJECT_NAME(si.object_id),index_id,6) as Key6 - FROM sys.indexes as si - LEFT JOIN sys.objects as so on so.object_id=si.object_id - WHERE index_id>0 -- omit the default heap - and OBJECTPROPERTY(si.object_id,'IsMsShipped')=0 -- omit system tables - and not (schema_name(schema_id)='dbo' and OBJECT_NAME(si.object_id)='sysdiagrams') -- omit sysdiagrams - ORDER BY SchemaName,TableName,IndexName - "); - - var list = new List(); - foreach (var x in indexes) - { - var index = new DbIndex - { - Name = x.IndexName, - Schema = x.SchemaName, - Table = x.TableName, - }; - - if (x.Key1 != null) index.Columns.Add(x.Key1); - if (x.Key2 != null) index.Columns.Add(x.Key2); - if (x.Key3 != null) index.Columns.Add(x.Key3); - if (x.Key4 != null) index.Columns.Add(x.Key4); - if (x.Key5 != null) index.Columns.Add(x.Key5); - if (x.Key6 != null) index.Columns.Add(x.Key6); - - list.Add(index); - } - return list; - } - - private List GetDatabaseColumns(DbTable table) - { - var columns = _db.Query(@" - SELECT c.COLUMN_NAME As [Name], c.CHARACTER_MAXIMUM_LENGTH AS [Length], c.DATA_TYPE AS [DbType], - (CASE WHEN c.IS_NULLABLE='YES' THEN 1 ELSE 0 END) AS [IsNullable], - (CASE WHEN pk.CONSTRAINT_TYPE='Primary Key' THEN 1 ELSE 0 END) AS IsKey, - COLUMNPROPERTY(object_id(c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') AS IsIdentity - FROM INFORMATION_SCHEMA.COLUMNS c - LEFT JOIN ( - SELECT tc.TABLE_SCHEMA, tc.TABLE_NAME, ccu.COLUMN_NAME, tc.CONSTRAINT_TYPE - FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc - LEFT JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.Constraint_name - WHERE tc.CONSTRAINT_TYPE = 'Primary Key' - ) pk ON (pk.TABLE_SCHEMA=c.TABLE_SCHEMA AND pk.TABLE_NAME=c.TABLE_NAME AND pk.COLUMN_NAME=c.COLUMN_NAME) - WHERE c.TABLE_SCHEMA=@0 AND c.TABLE_NAME=@1 - ", table.Schema, table.Name).ToList(); - - columns.ForEach(x => - { - if (x.Length == -1) x.Length = int.MaxValue; - }); - - return columns; - } - - private void AppendTableSql(StringBuilder sb, DbTable table, bool exists) - { - if (!exists) - { - sb.AppendFormat("CREATE TABLE [{0}].[{1}] (\r\n", table.Schema, table.Name); - - for (var i = 0; i < table.Columns.Count; i++) - { - var column = table.Columns[i]; - var lastcol = i == table.Columns.Count - 1; - sb.AppendFormat("\t{0}{1}\r\n", GetColumnSql(column, false), lastcol ? "" : ","); - } - - sb.AppendFormat(");\r\n"); - } - else - { - var dbcolumns = GetDatabaseColumns(table); - foreach (var col in table.Columns) - { - var existing = dbcolumns.SingleOrDefault(x => x.Name == col.Name); - if (existing == null) - { - //add column - sb.AppendFormat("ALTER TABLE [{0}].[{1}] ADD {2};\r\n", - table.Schema, table.Name, GetColumnSql(col, true)); - } - else if (existing.DbType == col.DbType && existing.Length == col.Length - && existing.IsIdentity == col.IsIdentity && existing.IsKey == col.IsKey - && existing.IsNullable == col.IsNullable) - { - //ignore - continue; - } - else - { - //modify column - sb.AppendFormat("ALTER TABLE {0}.[{1}] ALTER COLUMN {2};\r\n", - table.Schema, table.Name, GetColumnSql(col, true)); - } - } - } - } - - private string GetColumnSql(DbColumn column, bool alter) - { - var def = GetColumnDefault(column, alter); - - return string.Format("[{0}] {1} {2} {3} {4} {5}", - column.Name, - GetColumnType(column), - column.IsIdentity ? "IDENTITY" : "", - column.IsNullable ? "NULL" : "NOT NULL", - column.IsKey ? "PRIMARY KEY" : "", - def - ); - } - - private void AppendIndexSql(StringBuilder sb, DbTable table, List dbindexes) - { - foreach (var index in table.Indexes) - { - if (dbindexes.Any(x => x.Schema == index.Schema && x.Table == index.Table && x.Name == index.Name)) - continue; - - sb.AppendFormat("CREATE {0} INDEX [{1}] ON [{2}].[{3}] ([{4}]);\r\n", - index.IsUnique ? "UNIQUE" : "", - index.Name, - index.Schema, - index.Table, - string.Join("],[", index.Columns) - ); - } - } - - private string GetColumnType(DbColumn column) - { - var dbtype = GetDbType(column.Type.ToString()); - switch (column.Type.ToString()) - { - case "System.String": - if (column.Length == int.MaxValue) - return dbtype + "(MAX)"; - else - return dbtype + "(" + column.Length + ")"; - default: - return dbtype; - } - } - - private string GetColumnDefault(DbColumn column, bool alter) - { - if (column.Default != null) - { - return "DEFAULT " + column.Default; - } - else if (!alter || column.IsNullable) - { - return null; //ignore - } - else if (column.Type == typeof(int)) - { - return "DEFAULT " + default(int); - } - else if (column.Type == typeof(bool)) - { - return "DEFAULT 0"; - } - return null; - } - - private string GetDbType(string type) - { - switch (type) - { - case "System.String": - return DefaultStringType; - case "System.Int32": - case "System.Nullable`1[System.Int32]": - return "int"; - case "System.Boolean": - case "System.Nullable`1[System.Boolean]": - return "bit"; - case "System.DateTime": - case "System.Nullable`1[System.DateTime]": - return "datetime"; - default: - throw new ApplicationException("Column type unknown: " + type); - } - } - - private static T GetAttribute(Type type, bool inherit = false) where T : Attribute - { - return (T)type.GetCustomAttributes(typeof(T), inherit).FirstOrDefault(); - } - - private static T GetAttribute(MemberInfo member, bool inherit = false) where T : Attribute - { - return (T)member.GetCustomAttributes(typeof(T), inherit).FirstOrDefault(); - } - }; - - public class DbTable - { - public DbTable() - { - Columns = new List(); - Indexes = new List(); - } - public string Schema { get; set; } - public string Name { get; set; } - public List Columns { get; set; } - public List Indexes { get; set; } - }; - - public class DbColumn - { - public DbColumn() - { - IsNullable = true; - } - public string Name { get; set; } - public int? Length { get; set; } - public bool IsKey { get; set; } - public bool IsIdentity { get; set; } - public bool IsNullable { get; set; } - public Type Type { get; set; } - public string DbType { get; set; } - public string Default { get; set; } - }; - - public class DbIndex - { - public DbIndex() - { - Columns = new List(); - } - public string Name { get; set; } - public string Schema { get; set; } - public string Table { get; set; } - public bool IsUnique { get; set; } - public List Columns { get; set; } - } -} - -namespace System.ComponentModel.DataAnnotations.Schema -{ - //REF: http://blogs.southworks.net/dschenkelman/2012/08/18/creating-indexes-via-data-annotations-with-entity-framework-5-0/ - [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] - public class IndexAttribute : Attribute - { - public IndexAttribute(string name, bool unique = false) - { - Name = name; - IsUnique = unique; - } - - public string Name { get; set; } - - public bool IsUnique { get; set; } - }; -} diff --git a/MyepWeb/Bootstrapper.cs b/MyepWeb/Bootstrapper.cs index 35ef347..9df3494 100644 --- a/MyepWeb/Bootstrapper.cs +++ b/MyepWeb/Bootstrapper.cs @@ -16,6 +16,8 @@ It is typically not necessary to register your controllers with Unity. http://devtrends.co.uk/blog/introducing-the-unity.mvc3-nuget-package-to-reconcile-mvc3-unity-and-idisposable */ + +using System.Configuration; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; @@ -25,6 +27,8 @@ namespace Site { public static class Bootstrapper { + public static readonly string ConnectionString = ConfigurationManager.ConnectionStrings["SiteDb"].ConnectionString; + public static void Initialize() { ConfigureContainer(new UnityContainer()); @@ -36,7 +40,7 @@ public static void Initialize() public static void ConfigureContainer(IUnityContainer container) { - container.RegisterType(new HierarchicalLifetimeManager()); + container.RegisterType(typeof(IDb), typeof(SiteDb), new HierarchicalLifetimeManager(), new InjectionFactory(x => new SiteDb(ConnectionString))); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); } diff --git a/MyepWeb/MyepWeb.csproj b/MyepWeb/MyepWeb.csproj index 3ae2f87..dd5eaec 100644 --- a/MyepWeb/MyepWeb.csproj +++ b/MyepWeb/MyepWeb.csproj @@ -49,6 +49,14 @@ True ..\packages\DocumentFormat.OpenXml.2.5\lib\DocumentFormat.OpenXml.dll + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + True + + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + True + ..\packages\EPPlus.4.1.0\lib\net40\EPPlus.dll True @@ -146,7 +154,6 @@ - @@ -249,7 +256,6 @@ Global.asax - diff --git a/MyepWeb/Web.config b/MyepWeb/Web.config index dcd80be..858c4e7 100644 --- a/MyepWeb/Web.config +++ b/MyepWeb/Web.config @@ -5,6 +5,8 @@
+ +
@@ -76,4 +78,10 @@ + + + + + + \ No newline at end of file diff --git a/MyepWeb/packages.config b/MyepWeb/packages.config index 52827d3..fa03660 100644 --- a/MyepWeb/packages.config +++ b/MyepWeb/packages.config @@ -3,6 +3,7 @@ +