diff --git a/.gitignore b/.gitignore index 257b555e..3c1f23a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ obj bin deploy *.csproj.user +*.DotSettings.user *.suo *.cache *.nupkg @@ -21,6 +22,22 @@ NDependOut *.dotCover *_mm_cache.bin Simple.Data.sln.DotSettings.user +Simple.Data.sln.DotSettings Simple.Data/Simple.Data.idc -.DS_Store -mono-release-* +.DS_Store +mono-release-* +*ncrunch* +MediumTrustApp +nohup.out +packages/EntityFramework.5.0.0/ +packages/Microsoft.AspNet.Mvc.3.0.20105.1/ +packages/Microsoft.AspNet.Providers.Core.1.1/ +packages/Microsoft.AspNet.Providers.LocalDB.1.1/ +packages/Microsoft.AspNet.Razor.1.0.20105.408/ +packages/Microsoft.AspNet.WebPages.1.0.20105.408/ +packages/Microsoft.Web.Infrastructure.1.0.0.0/ +packages/Modernizr.2.5.3/ +packages/jQuery.1.7.1.1/ +packages/jQuery.UI.Combined.1.8.20.1/ +packages/jQuery.Validation.1.9.0.1/ +Simple.Data.sln.ide diff --git a/Assemblies/Microsoft.SqlServer.Types.dll b/Assemblies/Microsoft.SqlServer.Types.dll new file mode 100644 index 00000000..a4d5a777 Binary files /dev/null and b/Assemblies/Microsoft.SqlServer.Types.dll differ diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index a1e7751d..3be7ef1c 100644 --- a/CommonAssemblyInfo.cs +++ b/CommonAssemblyInfo.cs @@ -19,6 +19,6 @@ // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.0.0.1")] -[assembly: AssemblyFileVersion("1.0.0.1")] +[assembly: AssemblyVersion("0.18.3.1")] +[assembly: AssemblyFileVersion("0.18.3.1")] diff --git a/PerformanceTestConsole/Program.cs b/PerformanceTestConsole/Program.cs index 654cbc75..c7fad05d 100644 --- a/PerformanceTestConsole/Program.cs +++ b/PerformanceTestConsole/Program.cs @@ -171,7 +171,7 @@ public void Run(int iterations) var tests = new Tests(); var simpleDb = Simple.Data.Database.OpenConnection(Program.ConnectionString); SqlConnection connection = Program.GetOpenConnection(); - ((AdoAdapter) simpleDb.GetAdapter()).UseSharedConnection(connection); + simpleDb.UseSharedConnection(connection); simpleDb.Posts.FindById(1); tests.Add(id => simpleDb.Posts.FindById(id), "Dynamic Simple.Data Query"); diff --git a/PerformanceTestConsole/app.config b/PerformanceTestConsole/app.config new file mode 100644 index 00000000..5b242640 --- /dev/null +++ b/PerformanceTestConsole/app.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProfilingApp/CastTask.cs b/ProfilingApp/CastTask.cs new file mode 100644 index 00000000..c24ce65d --- /dev/null +++ b/ProfilingApp/CastTask.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Simple.Data; + +namespace ProfilingApp +{ + public class CastTask : IProfileTask + { + private readonly dynamic _db = Database.OpenConnection(Properties.Settings.Default.ConnectionString); + + public void Run() + { + var watch = Stopwatch.StartNew(); + + List posts = _db.Posts.All().ToList(); + Console.WriteLine(posts.Count); + watch.Stop(); + Console.WriteLine(watch.Elapsed); + } + } + + public class Post + { + public int ID { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Created { get; set; } + } +} \ No newline at end of file diff --git a/ProfilingApp/ProfilingApp.csproj b/ProfilingApp/ProfilingApp.csproj index dea6f333..9c233236 100644 --- a/ProfilingApp/ProfilingApp.csproj +++ b/ProfilingApp/ProfilingApp.csproj @@ -43,6 +43,7 @@ + diff --git a/ProfilingApp/Program.cs b/ProfilingApp/Program.cs index 94b0b205..2ff45c74 100644 --- a/ProfilingApp/Program.cs +++ b/ProfilingApp/Program.cs @@ -12,9 +12,9 @@ class Program { static void Main(string[] args) { - ResetDatabase(); + //ResetDatabase(); - new FindByTask().Run(); + new CastTask().Run(); } private static void ResetDatabase() diff --git a/ProfilingApp/Resources/DatabaseResetSql.txt b/ProfilingApp/Resources/DatabaseResetSql.txt index 9242624e..892b6a0a 100644 --- a/ProfilingApp/Resources/DatabaseResetSql.txt +++ b/ProfilingApp/Resources/DatabaseResetSql.txt @@ -31,7 +31,7 @@ DECLARE @PostId AS INT DECLARE @PostIdStr AS NVARCHAR(3) DECLARE @Loop AS INT SET @PostId = 1 -WHILE @PostId <= 100 +WHILE @PostId <= 2500 BEGIN SET @PostIdStr = CAST(@PostId AS NVARCHAR(3)) INSERT INTO [dbo].[Post] ([Id], [Title], [Content], [Created]) VALUES (@PostId, 'Post ' + @PostIdStr, 'This is post number ' + @PostIdStr, GETDATE()) diff --git a/README.md b/README.md index d88f7e0f..ddaac9b5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ why not just write public User FindUserByEmail(string email) { - return Database.Open().Users.FindByEmail(email); + return Database.Open().Users.FindAllByEmail(email).FirstOrDefault(); } and take the rest of the morning off? @@ -63,7 +63,7 @@ If you'd like to create an adapter or provider and need some help to get started ## Resources * Simple.Data can be installed from [NuGet](http://nuget.org/) * Find more information in [the wiki](https://github.com/markrendle/Simple.Data/wiki) -* [Documentation!](http://simplefx.org/simpledata/docs/) +* [Documentation!](http://simplefx.org/simpledata/docs/) (and here is the [SimpleFx Github Project](https://github.com/simplefx/simplefx.github.com) if you want to help us improve the documentation) * Ask questions or report issues on [the mailing list](http://groups.google.com/group/simpledata) * Follow [@markrendle on Twitter](http://twitter.com/markrendle) for updates * Check out [my blog](http://blog.markrendle.net/) for the latest news diff --git a/Simple.Data.Ado.Test/ConnectionModifierTest.cs b/Simple.Data.Ado.Test/ConnectionModifierTest.cs new file mode 100644 index 00000000..540f8e46 --- /dev/null +++ b/Simple.Data.Ado.Test/ConnectionModifierTest.cs @@ -0,0 +1,152 @@ +using System.Data; +using NUnit.Framework; + +namespace Simple.Data.Ado.Test +{ + using System; + + [TestFixture] + public class ConnectionModifierTest + { + [Test] + public void ModifiesConnection() + { + var adapter = new AdoAdapter(new StubConnectionProvider()); + adapter.SetConnectionModifier(c => new FooConnection(c)); + Assert.IsInstanceOf(adapter.CreateConnection()); + } + + [Test] + public void ClearsConnection() + { + var adapter = new AdoAdapter(new StubConnectionProvider()); + adapter.SetConnectionModifier(c => new FooConnection(c)); + Assert.IsInstanceOf(adapter.CreateConnection()); + adapter.ClearConnectionModifier(); + Assert.IsNotInstanceOf(adapter.CreateConnection()); + } + + [Test] + public void ConnectionCreatedEventFires() + { + bool fired = false; + EventHandler handler = (o, e) => { fired = true; }; + AdoAdapter.ConnectionCreated += handler; + var adapter = new AdoAdapter(new StubConnectionProvider()); + var connection = adapter.CreateConnection(); + AdoAdapter.ConnectionCreated -= handler; + Assert.True(fired); + } + + [Test] + public void ConnectionCreatedCanOverrideConnection() + { + EventHandler handler = (o, e) => e.OverrideConnection(new BarConnection(e.Connection)); + AdoAdapter.ConnectionCreated += handler; + var adapter = new AdoAdapter(new StubConnectionProvider()); + var connection = adapter.CreateConnection(); + Assert.IsInstanceOf(connection); + AdoAdapter.ConnectionCreated -= handler; + } + + private class FooConnection : IDbConnection + { + private readonly IDbConnection _wrapped; + + public FooConnection(IDbConnection wrapped) + { + _wrapped = wrapped; + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public IDbTransaction BeginTransaction() + { + throw new System.NotImplementedException(); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + throw new System.NotImplementedException(); + } + + public void Close() + { + throw new System.NotImplementedException(); + } + + public void ChangeDatabase(string databaseName) + { + throw new System.NotImplementedException(); + } + + public IDbCommand CreateCommand() + { + throw new System.NotImplementedException(); + } + + public void Open() + { + throw new System.NotImplementedException(); + } + + public string ConnectionString { get; set; } + public int ConnectionTimeout { get; private set; } + public string Database { get; private set; } + public ConnectionState State { get; private set; } + } + + private class BarConnection : IDbConnection + { + private readonly IDbConnection _wrapped; + + public BarConnection(IDbConnection wrapped) + { + _wrapped = wrapped; + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public IDbTransaction BeginTransaction() + { + throw new System.NotImplementedException(); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + throw new System.NotImplementedException(); + } + + public void Close() + { + throw new System.NotImplementedException(); + } + + public void ChangeDatabase(string databaseName) + { + throw new System.NotImplementedException(); + } + + public IDbCommand CreateCommand() + { + throw new System.NotImplementedException(); + } + + public void Open() + { + throw new System.NotImplementedException(); + } + + public string ConnectionString { get; set; } + public int ConnectionTimeout { get; private set; } + public string Database { get; private set; } + public ConnectionState State { get; private set; } + } + } +} \ No newline at end of file diff --git a/Simple.Data.Ado.Test/Properties/AssemblyInfo.cs b/Simple.Data.Ado.Test/Properties/AssemblyInfo.cs index 9b49bbae..e8f0789d 100644 --- a/Simple.Data.Ado.Test/Properties/AssemblyInfo.cs +++ b/Simple.Data.Ado.Test/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Simple.Data.Ado.Test; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -34,3 +35,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: TestProviderAssembly] \ No newline at end of file diff --git a/Simple.Data.Ado.Test/ProviderHelperTest.cs b/Simple.Data.Ado.Test/ProviderHelperTest.cs index 7c905a1f..a5af8079 100644 --- a/Simple.Data.Ado.Test/ProviderHelperTest.cs +++ b/Simple.Data.Ado.Test/ProviderHelperTest.cs @@ -1,109 +1,118 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NUnit.Framework; -using System.Data; -using Simple.Data.Ado.Schema; -using System.ComponentModel.Composition; - -namespace Simple.Data.Ado.Test -{ - [TestFixture] - public class ProviderHelperTest - { - [Test] - public void ShouldNotRequestExportableTypeFromServiceProvider() - { - var helper = new ProviderHelper(); - var connectionProvider = new StubConnectionAndServiceProvider(); - var actual = helper.GetCustomProvider(connectionProvider); - Assert.IsNull(connectionProvider.RequestedServiceType); - } - - [Test] - public void ShouldRequestNonExportedTypeFromServiceProvider() - { - var helper = new ProviderHelper(); - var connectionProvider = new StubConnectionAndServiceProvider(); - var actual = helper.GetCustomProvider(connectionProvider); - Assert.AreEqual(typeof(IQueryPager), connectionProvider.RequestedServiceType); - } - - [Test] - public void ShouldReturnNonExportedTypeFromServiceProvider() - { - var helper = new ProviderHelper(); - var connectionProvider = new StubConnectionAndServiceProvider(); - var actual = helper.GetCustomProvider(connectionProvider); - Assert.IsInstanceOf(typeof(IQueryPager), actual); - } - - public class StubConnectionAndServiceProvider : IConnectionProvider, IServiceProvider - { - public void SetConnectionString(string connectionString) - { - throw new NotImplementedException(); - } - - public IDbConnection CreateConnection() - { - throw new NotImplementedException(); - } - - public ISchemaProvider GetSchemaProvider() - { - throw new NotImplementedException(); - } - - public string ConnectionString - { - get { throw new NotImplementedException(); } - } - - public bool SupportsCompoundStatements - { - get { throw new NotImplementedException(); } - } - - public string GetIdentityFunction() - { - throw new NotImplementedException(); - } - - public bool SupportsStoredProcedures - { - get { throw new NotImplementedException(); } - } - - public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName procedureName) - { - throw new NotImplementedException(); - } - - public Type RequestedServiceType { get; private set; } - public Object GetService(Type serviceType) - { - this.RequestedServiceType = serviceType; - return new StubQueryPager(); - } - } - - public class StubQueryPager : IQueryPager +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using System.Data; +using Simple.Data.Ado.Schema; +using System.ComponentModel.Composition; + +namespace Simple.Data.Ado.Test +{ + [TestFixture] + public class ProviderHelperTest + { + [Test] + public void ShouldNotRequestExportableTypeFromServiceProvider() + { + var helper = new ProviderHelper(); + var connectionProvider = new StubConnectionAndServiceProvider(); + var actual = helper.GetCustomProvider(connectionProvider); + Assert.IsNull(connectionProvider.RequestedServiceType); + } + + [Test] + public void ShouldRequestNonExportedTypeFromServiceProvider() + { + var helper = new ProviderHelper(); + var connectionProvider = new StubConnectionAndServiceProvider(); + var actual = helper.GetCustomProvider(connectionProvider); + Assert.AreEqual(typeof(IQueryPager), connectionProvider.RequestedServiceType); + } + + [Test] + public void ShouldReturnNonExportedTypeFromServiceProvider() + { + var helper = new ProviderHelper(); + var connectionProvider = new StubConnectionAndServiceProvider(); + var actual = helper.GetCustomProvider(connectionProvider); + Assert.IsInstanceOf(typeof(IQueryPager), actual); + } + + [Test] + public void ShouldFindProviderUsingAssemblyAttribute() + { + IConnectionProvider provider; + Assert.True(ProviderHelper.TryLoadAssemblyUsingAttribute("Test", null, out provider)); + Assert.IsNotNull(provider); + Assert.IsInstanceOf(provider); + } + + public class StubConnectionAndServiceProvider : IConnectionProvider, IServiceProvider + { + public void SetConnectionString(string connectionString) + { + throw new NotImplementedException(); + } + + public IDbConnection CreateConnection() + { + throw new NotImplementedException(); + } + + public ISchemaProvider GetSchemaProvider() + { + throw new NotImplementedException(); + } + + public string ConnectionString + { + get { throw new NotImplementedException(); } + } + + public bool SupportsCompoundStatements + { + get { throw new NotImplementedException(); } + } + + public string GetIdentityFunction() + { + throw new NotImplementedException(); + } + + public bool SupportsStoredProcedures + { + get { throw new NotImplementedException(); } + } + + public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName procedureName) + { + throw new NotImplementedException(); + } + + public Type RequestedServiceType { get; private set; } + public Object GetService(Type serviceType) + { + this.RequestedServiceType = serviceType; + return new StubQueryPager(); + } + } + + public class StubQueryPager : IQueryPager { public IEnumerable ApplyLimit(string sql, int take) { throw new NotImplementedException(); } - public IEnumerable ApplyPaging(string sql, int skip, int take) - { - throw new NotImplementedException(); - } - } - - public interface ITestInterface { } - [Export(typeof(ITestInterface))] - public class TestClass : ITestInterface { } - } -} + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) + { + throw new NotImplementedException(); + } + } + + public interface ITestInterface { } + [Export(typeof(ITestInterface))] + public class TestClass : ITestInterface { } + } +} diff --git a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj index e62f20bc..cdebfa7d 100644 --- a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj +++ b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj @@ -1,4 +1,4 @@ - + Debug @@ -58,11 +58,13 @@ + + diff --git a/Simple.Data.Ado.Test/TestCustomInserter.cs b/Simple.Data.Ado.Test/TestCustomInserter.cs index 598d27be..5d8afd1e 100644 --- a/Simple.Data.Ado.Test/TestCustomInserter.cs +++ b/Simple.Data.Ado.Test/TestCustomInserter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Data; +using System.Data.OleDb; using System.Linq; using System.Text; using NUnit.Framework; @@ -31,17 +32,17 @@ public void SetConnectionString(string connectionString) public IDbConnection CreateConnection() { - throw new NotImplementedException(); + return new OleDbConnection(); } public ISchemaProvider GetSchemaProvider() { - throw new NotImplementedException(); + return new StubSchemaProvider(); } public string ConnectionString { - get { throw new NotImplementedException(); } + get { return "stub"; } } public bool SupportsCompoundStatements @@ -65,10 +66,58 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr } } + public class StubSchemaProvider : ISchemaProvider + { + public IEnumerable GetTables() + { + throw new NotImplementedException(); + } + + public IEnumerable GetColumns(Table table) + { + throw new NotImplementedException(); + } + + public IEnumerable GetStoredProcedures() + { + throw new NotImplementedException(); + } + + public IEnumerable GetParameters(Procedure storedProcedure) + { + throw new NotImplementedException(); + } + + public Key GetPrimaryKey(Table table) + { + throw new NotImplementedException(); + } + + public IEnumerable GetForeignKeys(Table table) + { + throw new NotImplementedException(); + } + + public string QuoteObjectName(string unquotedName) + { + throw new NotImplementedException(); + } + + public string NameParameter(string baseName) + { + throw new NotImplementedException(); + } + + public string GetDefaultSchema() + { + throw new NotImplementedException(); + } + } + [Export(typeof(ICustomInserter))] public class StubCustomInserter : ICustomInserter { - public IDictionary Insert(AdoAdapter adapter, string tableName, IDictionary data, IDbTransaction transaction = null) + public IDictionary Insert(AdoAdapter adapter, string tableName, IDictionary data, IDbTransaction transaction = null, bool resultRequired = false) { throw new NotImplementedException(); } diff --git a/Simple.Data.Ado.Test/TestProviderAssemblyAttribute.cs b/Simple.Data.Ado.Test/TestProviderAssemblyAttribute.cs new file mode 100644 index 00000000..cbe7bd1a --- /dev/null +++ b/Simple.Data.Ado.Test/TestProviderAssemblyAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Simple.Data.Ado.Test +{ + public class TestProviderAssemblyAttribute : ProviderAssemblyAttributeBase + { + public TestProviderAssemblyAttribute() + : base("Test") + { + } + + public override bool TryGetProvider(string connectionString, out IConnectionProvider provider, out Exception exception) + { + if (connectionString.Equals("Test")) + { + provider = new StubConnectionProvider(); + exception = null; + return true; + } + provider = null; + exception = null; + return false; + } + } +} \ No newline at end of file diff --git a/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs index 1ee2299d..f4bca895 100644 --- a/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs +++ b/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs @@ -13,7 +13,7 @@ public partial class AdoAdapter : IAdapterWithFunctions public bool IsValidFunction(string functionName) { - return _connectionProvider.SupportsStoredProcedures && _schema.FindProcedure(functionName) != null; + return _connectionProvider.SupportsStoredProcedures && _schema.IsProcedure(functionName); } public IEnumerable Execute(string functionName, IDictionary parameters) @@ -25,7 +25,7 @@ public IEnumerable Execute(string functionName, IDictionary Execute(string functionName, IDictionary parameters, IAdapterTransaction transaction) { var executor = _executors.GetOrAdd(functionName, f => _connectionProvider.GetProcedureExecutor(this, _schema.BuildObjectName(f))); - return executor.Execute(parameters, ((AdoAdapterTransaction)transaction).Transaction); + return executor.Execute(parameters, ((AdoAdapterTransaction)transaction).DbTransaction); } } } diff --git a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs index 42b599e9..915beb12 100644 --- a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs +++ b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs @@ -10,7 +10,7 @@ public partial class AdoAdapter : IAdapterWithTransactions { - public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel) + public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) { IDbConnection connection = CreateConnection(); connection.OpenIfClosed(); @@ -18,7 +18,7 @@ public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel) return new AdoAdapterTransaction(transaction, _sharedConnection != null); } - public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel, string name) + public IAdapterTransaction BeginTransaction(string name, IsolationLevel isolationLevel = IsolationLevel.Unspecified) { IDbConnection connection = CreateConnection(); connection.OpenIfClosed(); @@ -35,7 +35,7 @@ public IEnumerable> InsertMany(string tableName, IAdapterTransaction transaction, Func, Exception, bool> onError, bool resultRequired) { - return new AdoAdapterInserter(this, ((AdoAdapterTransaction)transaction).Transaction).InsertMany( + return new AdoAdapterInserter(this, ((AdoAdapterTransaction)transaction).DbTransaction).InsertMany( tableName, data, onError, resultRequired); } @@ -44,7 +44,7 @@ public int UpdateMany(string tableName, IEnumerable> { IBulkUpdater bulkUpdater = ProviderHelper.GetCustomProvider(ConnectionProvider) ?? new BulkUpdater(); - return bulkUpdater.Update(this, tableName, data.ToList(), ((AdoAdapterTransaction)transaction).Transaction); + return bulkUpdater.Update(this, tableName, data.ToList(), ((AdoAdapterTransaction)transaction).DbTransaction); } public int UpdateMany(string tableName, IEnumerable> data, @@ -52,13 +52,13 @@ public int UpdateMany(string tableName, IEnumerable> { IBulkUpdater bulkUpdater = ProviderHelper.GetCustomProvider(ConnectionProvider) ?? new BulkUpdater(); - return bulkUpdater.Update(this, tableName, data.ToList(), ((AdoAdapterTransaction)transaction).Transaction); + return bulkUpdater.Update(this, tableName, data.ToList(), ((AdoAdapterTransaction)transaction).DbTransaction); } public int Update(string tableName, IDictionary data, IAdapterTransaction adapterTransaction) { string[] keyFieldNames = GetKeyNames(tableName).ToArray(); - if (keyFieldNames.Length == 0) throw new AdoAdapterException("No Primary Key found for implicit update"); + if (keyFieldNames.Length == 0) throw new AdoAdapterException(string.Format("No primary key found for implicit update of table '{0}'.", tableName)); return Update(tableName, data, GetCriteria(tableName, keyFieldNames, data), adapterTransaction); } @@ -68,45 +68,51 @@ public int UpdateMany(string tableName, IList> dataL IBulkUpdater bulkUpdater = ProviderHelper.GetCustomProvider(ConnectionProvider) ?? new BulkUpdater(); return bulkUpdater.Update(this, tableName, dataList, criteriaFieldNames, - ((AdoAdapterTransaction)adapterTransaction).Transaction); + ((AdoAdapterTransaction)adapterTransaction).DbTransaction); } - public IAdapterTransaction BeginTransaction() - { - IDbConnection connection = CreateConnection(); - connection.OpenIfClosed(); - IDbTransaction transaction = connection.BeginTransaction(); - return new AdoAdapterTransaction(transaction, _sharedConnection != null); - } + //public IAdapterTransaction BeginTransaction() + //{ + // IDbConnection connection = CreateConnection(); + // connection.OpenIfClosed(); + // IDbTransaction transaction = connection.BeginTransaction(); + // return new AdoAdapterTransaction(transaction, _sharedConnection != null); + //} - public IAdapterTransaction BeginTransaction(string name) - { - IDbConnection connection = CreateConnection(); - connection.OpenIfClosed(); - var sqlConnection = connection as SqlConnection; - IDbTransaction transaction = sqlConnection != null - ? sqlConnection.BeginTransaction(name) - : connection.BeginTransaction(); + //public IAdapterTransaction BeginTransaction(string name) + //{ + // IDbConnection connection = CreateConnection(); + // connection.OpenIfClosed(); + // var sqlConnection = connection as SqlConnection; + // IDbTransaction transaction = sqlConnection != null + // ? sqlConnection.BeginTransaction(name) + // : connection.BeginTransaction(); - return new AdoAdapterTransaction(transaction, name, _sharedConnection != null); - } + // return new AdoAdapterTransaction(transaction, name, _sharedConnection != null); + //} public IDictionary Get(string tableName, IAdapterTransaction transaction, params object[] parameterValues) { - return new AdoAdapterGetter(this, ((AdoAdapterTransaction) transaction).Transaction).Get(tableName, + return new AdoAdapterGetter(this, ((AdoAdapterTransaction) transaction).DbTransaction).Get(tableName, parameterValues); } + + public IEnumerable> RunQuery(SimpleQuery query, IAdapterTransaction transaction, out IEnumerable unhandledClauses) + { + return new AdoAdapterQueryRunner(this, (AdoAdapterTransaction)transaction).RunQuery(query, out unhandledClauses); + } + public IEnumerable> Find(string tableName, SimpleExpression criteria, IAdapterTransaction transaction) { - return new AdoAdapterFinder(this, ((AdoAdapterTransaction)transaction).Transaction).Find(tableName, + return new AdoAdapterFinder(this, ((AdoAdapterTransaction)transaction).DbTransaction).Find(tableName, criteria); } public IDictionary Insert(string tableName, IDictionary data, IAdapterTransaction transaction, bool resultRequired) { - return new AdoAdapterInserter(this, ((AdoAdapterTransaction)transaction).Transaction).Insert(tableName, + return new AdoAdapterInserter(this, ((AdoAdapterTransaction)transaction).DbTransaction).Insert(tableName, data, resultRequired); } @@ -125,19 +131,19 @@ public int Delete(string tableName, SimpleExpression criteria, IAdapterTransacti public override IDictionary Upsert(string tableName, IDictionary data, SimpleExpression criteria, bool resultRequired, IAdapterTransaction adapterTransaction) { - var transaction = ((AdoAdapterTransaction) adapterTransaction).Transaction; + var transaction = ((AdoAdapterTransaction) adapterTransaction).DbTransaction; return new AdoAdapterUpserter(this, transaction).Upsert(tableName, data, criteria, resultRequired); } public override IEnumerable> UpsertMany(string tableName, IList> list, IAdapterTransaction adapterTransaction, bool isResultRequired, Func, Exception, bool> errorCallback) { - var transaction = ((AdoAdapterTransaction) adapterTransaction).Transaction; + var transaction = ((AdoAdapterTransaction) adapterTransaction).DbTransaction; return new AdoAdapterUpserter(this, transaction).UpsertMany(tableName, list, isResultRequired, errorCallback); } public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, IAdapterTransaction adapterTransaction, bool isResultRequired, Func, Exception, bool> errorCallback) { - var transaction = ((AdoAdapterTransaction) adapterTransaction).Transaction; + var transaction = ((AdoAdapterTransaction) adapterTransaction).DbTransaction; return new AdoAdapterUpserter(this, transaction).UpsertMany(tableName, list, keyFieldNames.ToArray(), isResultRequired, errorCallback); } } diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index 23da6808..bd713a32 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Data; -using System.Data.Common; -using System.Data.SqlClient; using System.Linq; using Simple.Data.Ado.Schema; @@ -21,6 +19,7 @@ public partial class AdoAdapter : Adapter, ICloneable private Lazy _relatedFinder; private DatabaseSchema _schema; private IDbConnection _sharedConnection; + private Func _connectionModifier = connection => connection; public AdoAdapter() { @@ -46,6 +45,11 @@ private AdoAdapter(IConnectionProvider connectionProvider, AdoAdapterFinder find _schema = schema; } + public AdoOptions AdoOptions + { + get { return Options as AdoOptions; } + } + public CommandOptimizer CommandOptimizer { get { return _commandOptimizer; } @@ -79,15 +83,17 @@ public ISchemaProvider SchemaProvider public override IDictionary GetKey(string tableName, IDictionary record) { var homogenizedRecord = new Dictionary(record, HomogenizedEqualityComparer.DefaultInstance); - return GetKeyNames(tableName).ToDictionary(key => key, - key => homogenizedRecord.ContainsKey(key) ? homogenizedRecord[key] : null); + return GetKeyNames(tableName) + .Select(k => k.Homogenize()) + .Where(homogenizedRecord.ContainsKey) + .ToDictionary(key => key, key => homogenizedRecord[key]); } #region ICloneable Members public object Clone() { - return new AdoAdapter(_connectionProvider); + return new AdoAdapter(_connectionProvider) {_connectionModifier = _connectionModifier}; } #endregion @@ -99,8 +105,17 @@ protected override void OnSetup() { if (settingsKeys.Contains("ProviderName")) { - _connectionProvider = ProviderHelper.GetProviderByConnectionString(Settings.ConnectionString, - Settings.ProviderName); + if(settingsKeys.Contains("SchemaName")) + { + _connectionProvider = ProviderHelper.GetProviderByConnectionString(Settings.ConnectionString + , Settings.ProviderName + , Settings.SchemaName); + } + else + { + _connectionProvider = ProviderHelper.GetProviderByConnectionString(Settings.ConnectionString, + Settings.ProviderName); + } } else { @@ -113,7 +128,14 @@ protected override void OnSetup() } else if (settingsKeys.Contains("ConnectionName")) { - _connectionProvider = ProviderHelper.GetProviderByConnectionName(Settings.ConnectionName); + if (settingsKeys.Contains("SchemaName")) + { + _connectionProvider = ProviderHelper.GetProviderByConnectionName(Settings.ConnectionName, Settings.SchemaName); + } + else + { + _connectionProvider = ProviderHelper.GetProviderByConnectionName(Settings.ConnectionName); + } } _schema = DatabaseSchema.Get(_connectionProvider, _providerHelper); _relatedFinder = new Lazy(CreateRelatedFinder); @@ -168,10 +190,15 @@ public override IEnumerable>> RunQueries public override bool IsExpressionFunction(string functionName, params object[] args) { - return (functionName.Equals("like", StringComparison.OrdinalIgnoreCase) || - functionName.Equals("notlike", StringComparison.OrdinalIgnoreCase)) - && args.Length == 1 - && args[0] is string; + return FunctionIsLikeOrNotLike(functionName, args); + } + + private static bool FunctionIsLikeOrNotLike(string functionName, object[] args) + { + return ((functionName.Equals("like", StringComparison.OrdinalIgnoreCase) + || functionName.Equals("notlike", StringComparison.OrdinalIgnoreCase)) + && args.Length == 1 + && args[0] is string); } public override IObservable> RunQueryAsObservable(SimpleQuery query, @@ -238,56 +265,53 @@ public override IList GetKeyNames(string tableName) return _schema.FindTable(tableName).PrimaryKey.AsEnumerable().ToList(); } + public void SetConnectionModifier(Func connectionModifer) + { + _connectionModifier = connectionModifer; + } + + public void ClearConnectionModifier() + { + _connectionModifier = connection => connection; + } + private int Execute(ICommandBuilder commandBuilder) { IDbConnection connection = CreateConnection(); using (connection.MaybeDisposable()) { - using (IDbCommand command = commandBuilder.GetCommand(connection)) + using (IDbCommand command = commandBuilder.GetCommand(connection, AdoOptions)) { connection.OpenIfClosed(); - return TryExecute(command); + return command.TryExecuteNonQuery(); } } } - internal static int Execute(ICommandBuilder commandBuilder, IDbConnection connection) + internal int Execute(ICommandBuilder commandBuilder, IDbConnection connection) { using (connection.MaybeDisposable()) { - using (IDbCommand command = commandBuilder.GetCommand(connection)) + using (IDbCommand command = commandBuilder.GetCommand(connection, AdoOptions)) { connection.OpenIfClosed(); - return TryExecute(command); + return command.TryExecuteNonQuery(); } } } - internal static int Execute(ICommandBuilder commandBuilder, IAdapterTransaction transaction) + internal int Execute(ICommandBuilder commandBuilder, IAdapterTransaction transaction) { - IDbTransaction dbTransaction = ((AdoAdapterTransaction) transaction).Transaction; + IDbTransaction dbTransaction = ((AdoAdapterTransaction) transaction).DbTransaction; return Execute(commandBuilder, dbTransaction); } - internal static int Execute(ICommandBuilder commandBuilder, IDbTransaction dbTransaction) + internal int Execute(ICommandBuilder commandBuilder, IDbTransaction dbTransaction) { - using (IDbCommand command = commandBuilder.GetCommand(dbTransaction.Connection)) + using (IDbCommand command = commandBuilder.GetCommand(dbTransaction.Connection, AdoOptions)) { command.Transaction = dbTransaction; - return TryExecute(command); - } - } - - private static int TryExecute(IDbCommand command) - { - command.WriteTrace(); - try - { - return command.ExecuteNonQuery(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); + return command.TryExecuteNonQuery(); } } @@ -303,7 +327,14 @@ public void StopUsingSharedConnection() public IDbConnection CreateConnection() { - return _sharedConnection ?? _connectionProvider.CreateConnection(); + if (_sharedConnection != null) return _sharedConnection; + var connection = _connectionModifier(_connectionProvider.CreateConnection()); + var args = ConnectionCreated.Raise(this, () => new ConnectionCreatedEventArgs(connection)); + if (args != null && args.OverriddenConnection != null) + { + return args.OverriddenConnection; + } + return connection; } public DatabaseSchema GetSchema() @@ -318,7 +349,8 @@ public override IDictionary Upsert(string tableName, IDictionary public override IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, Func, Exception, bool> errorCallback) { - return new AdoAdapterUpserter(this).UpsertMany(tableName, list, isResultRequired, errorCallback); + var upserter = new AdoAdapterUpserter(this); + return upserter.UpsertMany(tableName, list, isResultRequired, errorCallback); } public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, Func, Exception, bool> errorCallback) @@ -336,5 +368,29 @@ protected override void OnReset() DatabaseSchema.ClearCache(); _schema = DatabaseSchema.Get(_connectionProvider, _providerHelper); } + + public static event EventHandler ConnectionCreated; + } + + public class ConnectionCreatedEventArgs : EventArgs + { + private readonly IDbConnection _connection; + + public ConnectionCreatedEventArgs(IDbConnection connection) + { + _connection = connection; + } + + public IDbConnection Connection + { + get { return _connection; } + } + + internal IDbConnection OverriddenConnection { get; private set; } + + public void OverrideConnection(IDbConnection overridingConnection) + { + OverriddenConnection = overridingConnection; + } } -} \ No newline at end of file +} diff --git a/Simple.Data.Ado/AdoAdapterException.cs b/Simple.Data.Ado/AdoAdapterException.cs index 4ee15798..ff25e6da 100644 --- a/Simple.Data.Ado/AdoAdapterException.cs +++ b/Simple.Data.Ado/AdoAdapterException.cs @@ -3,77 +3,75 @@ using System.Data; using System.Linq; using System.Runtime.Serialization; -using System.Text; using Simple.Data.Extensions; namespace Simple.Data.Ado { - using System.Security; - [Serializable] public class AdoAdapterException : AdapterException { public AdoAdapterException() : base(typeof(AdoAdapter)) + {} + + public AdoAdapterException(string message) + : base(message, typeof(AdoAdapter)) + {} + + public AdoAdapterException(string message, Exception inner) + : base(message, inner, typeof(AdoAdapter)) + {} + + public AdoAdapterException(string message, IDbCommand command) + : base(message, typeof(AdoAdapter)) { + CommandText = command.CommandText; + Parameters = command.Parameters.Cast() + .ToDictionary(p => p.ParameterName, p => p.Value); } - public AdoAdapterException(string message, IDbCommand command) : base(message, typeof(AdoAdapter)) + public AdoAdapterException(string message, IDbCommand command, Exception inner) + : base(message, inner, typeof(AdoAdapter)) { CommandText = command.CommandText; Parameters = command.Parameters.Cast() .ToDictionary(p => p.ParameterName, p => p.Value); } - public AdoAdapterException(string commandText, IEnumerable> parameters) - :base(typeof(AdoAdapter)) + public AdoAdapterException(string commandText, IEnumerable> parameters) + : base(typeof(AdoAdapter)) // never used outside tests? { CommandText = commandText; Parameters = parameters.ToDictionary(); } - - public AdoAdapterException(string message) : base(message, typeof(AdoAdapter)) - { - } - - public AdoAdapterException(string message, string commandText, IEnumerable> parameters) - :base(message, typeof(AdoAdapter)) + public AdoAdapterException(string message, string commandText, IEnumerable> parameters) + : base(message, typeof(AdoAdapter)) { CommandText = commandText; Parameters = parameters.ToDictionary(); } - public AdoAdapterException(string message, Exception inner) : base(message, inner, typeof(AdoAdapter)) + public AdoAdapterException(string message, string commandText, IEnumerable> parameters, Exception inner) + : base(message, inner, typeof(AdoAdapter)) { + CommandText = commandText; + Parameters = parameters.ToDictionary(); } protected AdoAdapterException(SerializationInfo info, StreamingContext context) : base(info, context) - { - //CommandText = info.GetString("CommandText"); - //try - //{ - // var array = info.GetValue("Parameters", typeof(KeyValuePair[])); - // if (array != null) - // { - // Parameters = ((KeyValuePair[])array); - // } - //} - //catch (SerializationException) - //{ - //} - } + {} public IDictionary Parameters { - get { return Data.Contains("Parameters") ? ((KeyValuePair[])Data["Parameters"]).ToDictionary() : null; } + get { return Data.Contains("Parameters") ? ((KeyValuePair[])Data["Parameters"]).ToDictionary() : null; } private set { Data["Parameters"] = value.ToArray(); } } public string CommandText { - get { return Data.Contains("CommandText") ? Data["CommandText"].ToString() : null; } + get { return Data.Contains("CommandText") ? Data["CommandText"].ToString() : null; } private set { Data["CommandText"] = value; } } } -} +} \ No newline at end of file diff --git a/Simple.Data.Ado/AdoAdapterFinder.cs b/Simple.Data.Ado/AdoAdapterFinder.cs index 4f89fd89..5441b4f5 100644 --- a/Simple.Data.Ado/AdoAdapterFinder.cs +++ b/Simple.Data.Ado/AdoAdapterFinder.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; - -namespace Simple.Data.Ado +namespace Simple.Data.Ado { - using System.Data.SqlClient; using System.Dynamic; + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Data; + using System.Data.Common; + using System.Linq; class AdoAdapterFinder { @@ -57,7 +54,7 @@ public Func> CreateFindOneDelegate(string ta var commandBuilder = new FindHelper(_adapter.GetSchema()) .GetFindByCommand(_adapter.GetSchema().BuildObjectName(tableName), criteria); - var command = commandBuilder.GetCommand(_adapter.CreateConnection()); + var command = commandBuilder.GetCommand(_adapter.CreateConnection(), _adapter.AdoOptions); command = _adapter.CommandOptimizer.OptimizeFindOne(command); var commandTemplate = @@ -113,7 +110,7 @@ private IEnumerable> FindAll(ObjectName tableName) private IEnumerable> ExecuteQuery(CommandTemplate commandTemplate, IEnumerable parameterValues) { var connection = _connection ?? _adapter.CreateConnection(); - var command = commandTemplate.GetDbCommand(connection, parameterValues); + var command = commandTemplate.GetDbCommand(_adapter, connection, parameterValues); command.Transaction = _transaction; return TryExecuteQuery(connection, command, commandTemplate.Index); } @@ -121,7 +118,7 @@ private IEnumerable> ExecuteQuery(CommandTemplate co private IDictionary ExecuteSingletonQuery(CommandTemplate commandTemplate, IEnumerable parameterValues) { var connection = _connection ?? _adapter.CreateConnection(); - var command = commandTemplate.GetDbCommand(connection, parameterValues); + var command = commandTemplate.GetDbCommand(_adapter, connection, parameterValues); command.Transaction = _transaction; return TryExecuteSingletonQuery(connection, command, commandTemplate.Index); } @@ -142,7 +139,7 @@ private IEnumerable> TryExecuteQuery(IDbConnection c } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command); + throw new AdoAdapterException(ex.Message, command, ex); } } @@ -154,7 +151,7 @@ private IEnumerable> TryExecuteQuery(IDbConnection c } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command); + throw new AdoAdapterException(ex.Message, command, ex); } } @@ -172,39 +169,21 @@ private Func ConnectionCreator private static IDictionary TryExecuteSingletonQuery(IDbConnection connection, IDbCommand command, IDictionary index) { - command.WriteTrace(); using (connection.MaybeDisposable()) using (command) { - try + connection.OpenIfClosed(); + using (var reader = command.TryExecuteReader()) { - connection.OpenIfClosed(); - using (var reader = command.ExecuteReader()) + if (reader.Read()) { - if (reader.Read()) - { - return reader.ToDictionary(index); - } + return reader.ToDictionary(index); } } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } } return null; } - private static IDisposable DisposeWrap(IDbConnection connection) - { - if (connection.State == ConnectionState.Open) - { - return ActionDisposable.NoOp; - } - - return new ActionDisposable(connection.Dispose); - } - private static object FixObjectType(object value) { if (value == null) return DBNull.Value; diff --git a/Simple.Data.Ado/AdoAdapterGetter.cs b/Simple.Data.Ado/AdoAdapterGetter.cs index 93c7dc2a..ac8feb87 100644 --- a/Simple.Data.Ado/AdoAdapterGetter.cs +++ b/Simple.Data.Ado/AdoAdapterGetter.cs @@ -35,13 +35,13 @@ public AdoAdapterGetter(AdoAdapter adapter, IDbTransaction transaction) public Func> CreateGetDelegate(string tableName, params object[] keyValues) { var primaryKey = _adapter.GetSchema().FindTable(tableName).PrimaryKey; - if (primaryKey == null) throw new InvalidOperationException("Table has no primary key."); + if (primaryKey == null) throw new InvalidOperationException(string.Format("Table '{0}' has no primary key.", tableName)); if (primaryKey.Length != keyValues.Length) throw new ArgumentException("Incorrect number of values for key."); var commandBuilder = new GetHelper(_adapter.GetSchema()).GetCommand(_adapter.GetSchema().FindTable(tableName), keyValues); - var command = commandBuilder.GetCommand(_adapter.CreateConnection()); + var command = commandBuilder.GetCommand(_adapter.CreateConnection(), _adapter.AdoOptions); command = _adapter.CommandOptimizer.OptimizeFindOne(command); var commandTemplate = @@ -70,32 +70,24 @@ private IDictionary ExecuteSingletonQuery(IDbCommand command, ob private IDictionary ExecuteSingletonQuery(CommandTemplate commandTemplate, IEnumerable parameterValues) { var connection = _connection ?? _adapter.CreateConnection(); - var command = commandTemplate.GetDbCommand(connection, parameterValues); + var command = commandTemplate.GetDbCommand(_adapter, connection, parameterValues); command.Transaction = _transaction; return TryExecuteSingletonQuery(connection, command, commandTemplate.Index); } private static IDictionary TryExecuteSingletonQuery(IDbConnection connection, IDbCommand command, IDictionary index) { - command.WriteTrace(); using (connection.MaybeDisposable()) using (command) { - try + connection.OpenIfClosed(); + using (var reader = command.TryExecuteReader()) { - connection.OpenIfClosed(); - using (var reader = command.ExecuteReader()) + if (reader.Read()) { - if (reader.Read()) - { - return reader.ToDictionary(index); - } + return reader.ToDictionary(index); } } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } } return null; } @@ -115,13 +107,13 @@ private static object FixObjectType(object value) public IDictionary Get(string tableName, object[] parameterValues) { var primaryKey = _adapter.GetSchema().FindTable(tableName).PrimaryKey; - if (primaryKey == null) throw new InvalidOperationException("Table has no primary key."); + if (primaryKey == null) throw new InvalidOperationException(string.Format("Table '{0}' has no primary key.", tableName)); if (primaryKey.Length != parameterValues.Length) throw new ArgumentException("Incorrect number of values for key."); var commandBuilder = new GetHelper(_adapter.GetSchema()).GetCommand(_adapter.GetSchema().FindTable(tableName), parameterValues); - var command = commandBuilder.GetCommand(_adapter.CreateConnection()); + var command = commandBuilder.GetCommand(_adapter.CreateConnection(), _adapter.AdoOptions); command = _adapter.CommandOptimizer.OptimizeFindOne(command); var commandTemplate = diff --git a/Simple.Data.Ado/AdoAdapterInserter.cs b/Simple.Data.Ado/AdoAdapterInserter.cs index ad0f8658..46afd760 100644 --- a/Simple.Data.Ado/AdoAdapterInserter.cs +++ b/Simple.Data.Ado/AdoAdapterInserter.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Linq; using System.Text; using Simple.Data.Extensions; @@ -50,16 +49,16 @@ public IEnumerable> InsertMany(string tableName, IEn public IDictionary Insert(string tableName, IEnumerable> data, bool resultRequired) { var table = _adapter.GetSchema().FindTable(tableName); - - CheckInsertablePropertiesAreAvailable(table, data); + var dataArray = data.ToArray(); + CheckInsertablePropertiesAreAvailable(table, dataArray); var customInserter = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); if (customInserter != null) { - return customInserter.Insert(_adapter, tableName, data.ToDictionary(), _transaction); + return customInserter.Insert(_adapter, tableName, dataArray.ToDictionary(), _transaction, resultRequired); } - var dataDictionary = data.Where(kvp => table.HasColumn(kvp.Key) && !table.FindColumn(kvp.Key).IsIdentity) + var dataDictionary = dataArray.Where(kvp => table.HasColumn(kvp.Key) && table.FindColumn(kvp.Key).IsWriteable) .ToDictionary(kvp => table.FindColumn(kvp.Key), kvp => kvp.Value); string columnList = dataDictionary.Keys.Select(c => c.QuotedName).Aggregate((agg, next) => agg + "," + next); @@ -83,11 +82,8 @@ public IDictionary Insert(string tableName, IEnumerable ExecuteSingletonQuery(string insertSql, str { var command = new CommandHelper(_adapter).CreateInsert(_transaction.Connection, insertSql, columns, values.ToArray()); command.Transaction = _transaction; - TryExecute(command); + command.TryExecuteNonQuery(); command.CommandText = selectSql; command.Parameters.Clear(); return TryExecuteSingletonQuery(command); @@ -144,7 +140,7 @@ internal IDictionary ExecuteSingletonQuery(string insertSql, str using (var command = new CommandHelper(_adapter).CreateInsert(connection, insertSql, columns, values.ToArray())) { connection.OpenIfClosed(); - TryExecute(command); + command.TryExecuteNonQuery(); command.CommandText = selectSql; command.Parameters.Clear(); return TryExecuteSingletonQuery(command); @@ -154,21 +150,13 @@ internal IDictionary ExecuteSingletonQuery(string insertSql, str private static IDictionary TryExecuteSingletonQuery(IDbCommand command) { - command.WriteTrace(); - try + using (var reader = command.TryExecuteReader()) { - using (var reader = command.ExecuteReader()) + if (reader.Read()) { - if (reader.Read()) - { - return reader.ToDictionary(); - } + return reader.ToDictionary(); } } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } return null; } @@ -179,7 +167,7 @@ internal int Execute(string sql, IEnumerable columns, IEnumerable columns, IEnumerable> RunQuery(SimpleQuery query, @@ -22,19 +29,29 @@ out IEnumerable if (query.Clauses.OfType().Any()) return RunQueryWithCount(query, out unhandledClauses); - ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(query, out unhandledClauses); + ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(ref query, out unhandledClauses); IDbConnection connection = _adapter.CreateConnection(); if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) { - result = - CommandBuilder.CreateCommand( + var command = + new CommandBuilder(_adapter.GetSchema()).CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders, - connection).ToEnumerable(_adapter.CreateConnection); + connection, _adapter.AdoOptions); + + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + result = command.ToEnumerable(_transaction.DbTransaction); + } + else + { + result = command.ToEnumerable(_adapter.CreateConnection); + } } else { - result = commandBuilders.SelectMany(cb => cb.GetCommand(connection).ToEnumerable(_adapter.CreateConnection)); + result = commandBuilders.SelectMany(cb => cb.GetCommand(connection, _adapter.AdoOptions).ToEnumerable(_adapter.CreateConnection)); } if (query.Clauses.OfType().Any()) @@ -53,7 +70,7 @@ public IObservable> RunQueryAsObservable(SimpleQuery { IDbConnection connection = _adapter.CreateConnection(); return new QueryBuilder(_adapter).Build(query, out unhandledClauses) - .GetCommand(connection) + .GetCommand(connection, _adapter.AdoOptions) .ToObservable(connection, _adapter); } @@ -66,10 +83,10 @@ out IEnumerable { withCountClause = query.Clauses.OfType().First(); } - catch (InvalidOperationException) + catch (InvalidOperationException e) { // Rethrow with meaning. - throw new InvalidOperationException("No WithCountClause specified."); + throw new InvalidOperationException("No WithCountClause specified.", e); } query = query.ClearWithTotalCount(); @@ -77,6 +94,7 @@ out IEnumerable .ClearTake() .ClearOrderBy() .ClearWith() + .ClearForUpdate() .ReplaceSelect(new CountSpecialReference()); var unhandledClausesList = new List> { @@ -92,7 +110,9 @@ out IEnumerable throw new InvalidOperationException(); } IDictionary countRow = enumerator.Current.Single(); - withCountClause.SetCount((int) countRow.First().Value); + var value = countRow.First().Value; + int count = value is int ? (int) value : Convert.ToInt32(value); + withCountClause.SetCount(count); if (!enumerator.MoveNext()) { throw new InvalidOperationException(); @@ -101,13 +121,13 @@ out IEnumerable } } - private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, out IEnumerable unhandledClauses) { - return GetPagedQueryCommandBuilders(query, -1, out unhandledClauses); + return GetPagedQueryCommandBuilders(ref query, -1, out unhandledClauses); } - private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 bulkIndex, + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, out IEnumerable unhandledClauses) { var commandBuilders = new List(); @@ -120,7 +140,6 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 unhandledClausesForPagedQuery); unhandledClausesList.AddRange(unhandledClausesForPagedQuery); - SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); @@ -129,41 +148,52 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); if (queryPager == null) { - DeferPaging(query, mainCommandBuilder, commandBuilders, unhandledClausesList); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Warning, SimpleDataTraceSources.PerformanceWarningMessageId, + "There is no database-specific query paging in your current Simple.Data Provider. Paging will be done in memory."); + DeferPaging(ref query, mainCommandBuilder, commandBuilders, unhandledClausesList); } else { - ApplyPaging(commandBuilders, mainCommandBuilder, skipClause, takeClause, queryPager); + ApplyPaging(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); } } return commandBuilders.ToArray(); } - private void DeferPaging(SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, + private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, List unhandledClausesList) { - unhandledClausesList.AddRange(query.OfType()); - unhandledClausesList.AddRange(query.OfType()); + unhandledClausesList.AddRange(query.Clauses.OfType()); + unhandledClausesList.AddRange(query.Clauses.OfType()); + query = query.ClearSkip().ClearTake(); var commandBuilder = new CommandBuilder(mainCommandBuilder.Text, _adapter.GetSchema(), mainCommandBuilder.Parameters); commandBuilders.Add(commandBuilder); } - private void ApplyPaging(List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, - TakeClause takeClause, IQueryPager queryPager) + private void ApplyPaging(SimpleQuery query, List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) { const int maxInt = 2147483646; IEnumerable commandTexts; - if (skipClause == null) + if (skipClause == null && !hasWithClause) { commandTexts = queryPager.ApplyLimit(mainCommandBuilder.Text, takeClause.Count); } else { - if (takeClause == null) takeClause = new TakeClause(maxInt); - commandTexts = queryPager.ApplyPaging(mainCommandBuilder.Text, skipClause.Count, - takeClause.Count); + var table = _adapter.GetSchema().FindTable(query.TableName); + var keys = new string[0]; + if (table.PrimaryKey != null && table.PrimaryKey.Length > 0) + { + keys = table.PrimaryKey.AsEnumerable() + .Select(k => string.Format("{0}.{1}", table.QualifiedName, _adapter.GetSchema().QuoteObjectName(k))) + .ToArray(); + } + + int skip = skipClause == null ? 0 : skipClause.Count; + int take = takeClause == null ? maxInt : takeClause.Count; + commandTexts = queryPager.ApplyPaging(mainCommandBuilder.Text, keys, skip, take); } commandBuilders.AddRange( @@ -172,23 +202,23 @@ private void ApplyPaging(List commandBuilders, ICommandBuilder new CommandBuilder(commandText, _adapter.GetSchema(), mainCommandBuilder.Parameters))); } - private ICommandBuilder[] GetQueryCommandBuilders(SimpleQuery query, + private ICommandBuilder[] GetQueryCommandBuilders(ref SimpleQuery query, out IEnumerable unhandledClauses) { if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) { - return GetPagedQueryCommandBuilders(query, out unhandledClauses); + return GetPagedQueryCommandBuilders(ref query, out unhandledClauses); } return new[] {new QueryBuilder(_adapter).Build(query, out unhandledClauses)}; } - private IEnumerable GetQueryCommandBuilders(SimpleQuery query, Int32 bulkIndex, + private IEnumerable GetQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, out IEnumerable unhandledClauses) { if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) { - return GetPagedQueryCommandBuilders(query, bulkIndex, out unhandledClauses); + return GetPagedQueryCommandBuilders(ref query, bulkIndex, out unhandledClauses); } return new[] {new QueryBuilder(_adapter, bulkIndex).Build(query, out unhandledClauses)}; } @@ -206,14 +236,26 @@ public IEnumerable>> RunQueries(SimpleQu for (int i = 0; i < queries.Length; i++) { IEnumerable unhandledClausesForThisQuery; - commandBuilders.AddRange(GetQueryCommandBuilders(queries[i], i, out unhandledClausesForThisQuery)); + commandBuilders.AddRange(GetQueryCommandBuilders(ref queries[i], i, out unhandledClausesForThisQuery)); unhandledClauses.Add(unhandledClausesForThisQuery); } - IDbConnection connection = _adapter.CreateConnection(); + IDbConnection connection; + if (_transaction != null) + { + connection = _transaction.DbTransaction.Connection; + } + else + { + connection = _adapter.CreateConnection(); + } IDbCommand command = - CommandBuilder.CreateCommand( + new CommandBuilder(_adapter.GetSchema()).CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), - commandBuilders.ToArray(), connection); + commandBuilders.ToArray(), connection, _adapter.AdoOptions); + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + } foreach (var item in command.ToEnumerables(connection)) { yield return item.ToList(); diff --git a/Simple.Data.Ado/AdoAdapterRelatedFinder.cs b/Simple.Data.Ado/AdoAdapterRelatedFinder.cs index f125c24e..8e1426f3 100644 --- a/Simple.Data.Ado/AdoAdapterRelatedFinder.cs +++ b/Simple.Data.Ado/AdoAdapterRelatedFinder.cs @@ -30,7 +30,7 @@ public bool IsValidRelation(string tableName, string relatedTableName) public object FindRelated(string tableName, IDictionary row, string relatedTableName) { var join = TryJoin(tableName, relatedTableName); - if (join == null) throw new AdoAdapterException("Could not resolve relationship."); + if (join == null) throw new AdoAdapterException(string.Format("Could not resolve relationship of tables '{0}' and '{1}'.", tableName, relatedTableName)); if (join.Master == _adapter.GetSchema().FindTable(tableName)) { diff --git a/Simple.Data.Ado/AdoAdapterTransaction.cs b/Simple.Data.Ado/AdoAdapterTransaction.cs index feea07ef..2177f8a3 100644 --- a/Simple.Data.Ado/AdoAdapterTransaction.cs +++ b/Simple.Data.Ado/AdoAdapterTransaction.cs @@ -26,7 +26,7 @@ public AdoAdapterTransaction(IDbTransaction dbTransaction, string name, bool sha _sharedConnection = sharedConnection; } - internal IDbTransaction Transaction + internal IDbTransaction DbTransaction { get { return _dbTransaction; } } diff --git a/Simple.Data.Ado/AdoAdapterUpserter.cs b/Simple.Data.Ado/AdoAdapterUpserter.cs index d46bcdfa..956695ec 100644 --- a/Simple.Data.Ado/AdoAdapterUpserter.cs +++ b/Simple.Data.Ado/AdoAdapterUpserter.cs @@ -45,20 +45,26 @@ private IDictionary Upsert(string tableName, IDictionary().Select(o => o.GetName().Homogenize()); - data = data.Where(kvp => keys.All(k => k != kvp.Key.Homogenize())).ToDictionary(); + var updateData = data.Where(kvp => keys.All(k => k != kvp.Key.Homogenize())).ToDictionary(); + if (updateData.Count == 0) + { + return existing; + } - var commandBuilder = new UpdateHelper(_adapter.GetSchema()).GetUpdateCommand(tableName, data, criteria); + var commandBuilder = new UpdateHelper(_adapter.GetSchema()).GetUpdateCommand(tableName, updateData, criteria); if (_transaction == null) { - AdoAdapter.Execute(commandBuilder, connection); + _adapter.Execute(commandBuilder, connection); } else { - AdoAdapter.Execute(commandBuilder, _transaction); + _adapter.Execute(commandBuilder, _transaction); } return resultRequired ? finder.FindOne(tableName, criteria) : null; } @@ -76,9 +82,17 @@ public IEnumerable> UpsertMany(string tableName, ILi IDictionary result; try { - var criteria = ExpressionHelper.CriteriaDictionaryToExpression(tableName, - _adapter.GetKey(tableName, row)); - result = Upsert(tableName, row, criteria, isResultRequired); + var key = _adapter.GetKey(tableName, row); + if (key.Count == 0) + { + result = new AdoAdapterInserter(_adapter).Insert(tableName, row, isResultRequired); + } + else + { + var criteria = ExpressionHelper.CriteriaDictionaryToExpression(tableName, + key); + result = Upsert(tableName, row, criteria, isResultRequired); + } } catch (Exception ex) { @@ -89,7 +103,7 @@ public IEnumerable> UpsertMany(string tableName, ILi yield return result; } } - + public IEnumerable> UpsertMany(string tableName, IList> list, IList keyFieldNames, bool isResultRequired, Func, Exception, bool> errorCallback) { foreach (var row in list) @@ -130,4 +144,4 @@ private static SimpleExpression GetCriteria(string tableName, IEnumerable> Insert(AdoAdapter adapter, string tableName, IEnumerable> data, IDbTransaction transaction, Func, Exception, bool> onError, bool resultRequired) { var table = adapter.GetSchema().FindTable(tableName); - var columns = table.Columns.Where(c => !c.IsIdentity).ToList(); + var columns = table.Columns.Where(c => c.IsWriteable).ToList(); string columnList = string.Join(",", columns.Select(c => c.QuotedName)); string valueList = string.Join(",", columns.Select(c => "?")); diff --git a/Simple.Data.Ado/BulkInserterHelper.cs b/Simple.Data.Ado/BulkInserterHelper.cs index 3bfc5c74..45f0f098 100644 --- a/Simple.Data.Ado/BulkInserterHelper.cs +++ b/Simple.Data.Ado/BulkInserterHelper.cs @@ -1,9 +1,10 @@ +using Simple.Data.Extensions; + namespace Simple.Data.Ado { using System; using System.Collections.Generic; using System.Data; - using System.Data.Common; using System.Diagnostics; using System.Linq; using Schema; @@ -47,7 +48,7 @@ public virtual IEnumerable> InsertRowsWithSeparateSt using (connection.MaybeDisposable()) { using (var insertCommand = new CommandHelper(Adapter).CreateInsert(connection, insertSql, _columns)) - using (var selectCommand = connection.CreateCommand()) + using (var selectCommand = connection.CreateCommand(Adapter.AdoOptions)) { selectCommand.CommandText = selectSql; connection.OpenIfClosed(); @@ -97,7 +98,7 @@ protected int InsertRow(IDictionary row, IDbCommand command, Fun try { - return TryExecute(command); + return command.TryExecuteNonQuery(); } catch (Exception ex) { @@ -113,7 +114,7 @@ protected IDictionary InsertRow(IDictionary row, try { - if (TryExecute(insertCommand) == 1) + if (insertCommand.TryExecuteNonQuery() == 1) return TryExecuteSingletonQuery(selectCommand); } catch (Exception ex) @@ -126,38 +127,17 @@ protected IDictionary InsertRow(IDictionary row, private static IDictionary TryExecuteSingletonQuery(IDbCommand command) { - command.WriteTrace(); - try + using (var reader = command.TryExecuteReader()) { - using (var reader = command.ExecuteReader()) + if (reader.Read()) { - if (reader.Read()) - { - return reader.ToDictionary(); - } + return reader.ToDictionary(); } } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } return null; } - private static int TryExecute(IDbCommand command) - { - command.WriteTrace(); - try - { - return command.ExecuteNonQuery(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } - } - private static void TryPrepare(params IDbCommand[] commands) { foreach (var command in commands) @@ -166,9 +146,10 @@ private static void TryPrepare(params IDbCommand[] commands) { command.Prepare(); } - catch (InvalidOperationException) + catch (InvalidOperationException e) { - Trace.TraceWarning("Could not prepare command."); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Warning, SimpleDataTraceSources.GenericWarningMessageId, + "Could not prepare command: {0}", e.Message); } } } @@ -178,7 +159,7 @@ private Action, IDbCommand> BuildParameterSettingActi var actions = _columns.Select, IDbCommand>>((c, i) => (row, cmd) => cmd.SetParameterValue(i, null)).ToArray(); - var usedColumnNames = sample.Keys.Where(k => _columns.Any(c => String.Equals(c.ActualName, k, StringComparison.InvariantCultureIgnoreCase))).ToArray(); + var usedColumnNames = sample.Keys.Where(k => _columns.Any(c => String.Equals(c.HomogenizedName, k.Homogenize(), StringComparison.InvariantCultureIgnoreCase))).ToArray(); foreach (var columnName in usedColumnNames) { diff --git a/Simple.Data.Ado/BulkInserterTransactionHelper.cs b/Simple.Data.Ado/BulkInserterTransactionHelper.cs index 5e69994e..3d2a25ad 100644 --- a/Simple.Data.Ado/BulkInserterTransactionHelper.cs +++ b/Simple.Data.Ado/BulkInserterTransactionHelper.cs @@ -19,7 +19,7 @@ public BulkInserterTransactionHelper(AdoAdapter adapter, IEnumerable> InsertRowsWithSeparateStatements(string insertSql, string selectSql, Func, Exception, bool> onError) { var insertCommand = new CommandHelper(Adapter).Create(_transaction.Connection, insertSql); - var selectCommand = _transaction.Connection.CreateCommand(); + var selectCommand = _transaction.Connection.CreateCommand(Adapter.AdoOptions); selectCommand.CommandText = selectSql; insertCommand.Transaction = _transaction; selectCommand.Transaction = _transaction; diff --git a/Simple.Data.Ado/BulkUpdater.cs b/Simple.Data.Ado/BulkUpdater.cs index 03173237..01fe528b 100644 --- a/Simple.Data.Ado/BulkUpdater.cs +++ b/Simple.Data.Ado/BulkUpdater.cs @@ -35,8 +35,12 @@ public int Update(AdoAdapter adapter, string tableName, IList _getParameterFactory; + private readonly DatabaseSchema _schema; private readonly ISchemaProvider _schemaProvider; private readonly Dictionary _parameters = new Dictionary(); private readonly StringBuilder _text; @@ -28,6 +29,7 @@ public CommandBuilder(DatabaseSchema schema) : this(schema, -1) public CommandBuilder(DatabaseSchema schema, int bulkIndex) { _text = new StringBuilder(); + _schema = schema; _schemaProvider = schema.SchemaProvider; _customInterfaceProvider = schema.ProviderHelper; _parameterSuffix = (bulkIndex >= 0) ? "_c" + bulkIndex : string.Empty; @@ -36,6 +38,7 @@ public CommandBuilder(DatabaseSchema schema, int bulkIndex) public CommandBuilder(string text, DatabaseSchema schema, int bulkIndex) { _text = new StringBuilder(text); + _schema = schema; _schemaProvider = schema.SchemaProvider; _customInterfaceProvider = schema.ProviderHelper; _parameterSuffix = (bulkIndex >= 0) ? "_c" + bulkIndex : string.Empty; @@ -100,17 +103,21 @@ public string Text get { return _text.ToString(); } } - public IDbCommand GetCommand(IDbConnection connection) + public IDbCommand GetCommand(IDbConnection connection, AdoOptions options) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(options); command.CommandText = Text; SetParameters(command, string.Empty); + if (options != null) + { + command.CommandTimeout = options.CommandTimeout; + } return command; } - public IDbCommand GetRepeatableCommand(IDbConnection connection) + public IDbCommand GetRepeatableCommand(IDbConnection connection, AdoOptions options) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(options); command.CommandText = Text; var parameterFactory = CreateParameterFactory(command); @@ -119,6 +126,10 @@ public IDbCommand GetRepeatableCommand(IDbConnection connection) { command.Parameters.Add(parameterFactory.CreateParameter(parameter.Name, parameter.Column)); } + if (options != null) + { + command.CommandTimeout = options.CommandTimeout; + } return command; } @@ -166,7 +177,7 @@ private void SetParameters(IDbCommand command, IEnumerable> parameters) + private void SetParameters(IDbParameterFactory parameterFactory, IDbCommand command, IEnumerable> parameters) { var parameterList = parameters.ToList(); if (parameterList.Any(kvp => kvp.Value is IRange) || @@ -189,7 +200,7 @@ private static void SetParameters(IDbParameterFactory parameterFactory, IDbComma } } - private static IEnumerable CreateParameterComplex(IDbParameterFactory parameterFactory, ParameterTemplate template, object value, IDbCommand command) + private IEnumerable CreateParameterComplex(IDbParameterFactory parameterFactory, ParameterTemplate template, object value, IDbCommand command) { if (template.Column != null && template.Column.IsBinary) { @@ -228,13 +239,13 @@ private static IEnumerable CreateParameterComplex(IDbParameter if (command.CommandText.Contains("!= " + template.Name)) { command.CommandText = command.CommandText.Replace("!= " + template.Name, - "NOT IN (" + + _schema.Operators.NotIn + " (" + builder.ToString().Substring(1) + ")"); } else { command.CommandText = command.CommandText.Replace("= " + template.Name, - "IN (" + + _schema.Operators.In + " (" + builder.ToString().Substring(1) + ")"); } @@ -248,17 +259,17 @@ private static IEnumerable CreateParameterComplex(IDbParameter } } - public static void SetBetweenInCommandText(IDbCommand command, string name) + public void SetBetweenInCommandText(IDbCommand command, string name) { if (command.CommandText.Contains("!= " + name)) { command.CommandText = command.CommandText.Replace("!= " + name, - string.Format("NOT BETWEEN {0}_start AND {0}_end", name)); + string.Format("{0} {1}_start AND {1}_end", _schema.Operators.NotBetween, name)); } else { command.CommandText = command.CommandText.Replace("= " + name, - string.Format("BETWEEN {0}_start AND {0}_end", name)); + string.Format("{0} {1}_start AND {1}_end", _schema.Operators.Between, name)); } } @@ -289,9 +300,9 @@ private static IDbDataParameter CreateSingleParameter(IDbParameterFactory parame return parameter; } - internal static IDbCommand CreateCommand(IDbParameterFactory parameterFactory, ICommandBuilder[] commandBuilders, IDbConnection connection) + internal IDbCommand CreateCommand(IDbParameterFactory parameterFactory, ICommandBuilder[] commandBuilders, IDbConnection connection, AdoOptions options) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(options); parameterFactory = parameterFactory ?? new GenericDbParameterFactory(command); for (int i = 0; i < commandBuilders.Length; i++) { @@ -302,9 +313,9 @@ internal static IDbCommand CreateCommand(IDbParameterFactory parameterFactory, I return command; } - internal IDbCommand CreateCommand(ICommandBuilder[] commandBuilders, IDbConnection connection) + internal IDbCommand CreateCommand(ICommandBuilder[] commandBuilders, IDbConnection connection, AdoOptions options) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(options); for (int i = 0; i < commandBuilders.Length; i++) { if (!string.IsNullOrWhiteSpace(command.CommandText)) command.CommandText += "; "; diff --git a/Simple.Data.Ado/CommandHelper.cs b/Simple.Data.Ado/CommandHelper.cs index 2ac66dd0..5083a2d8 100644 --- a/Simple.Data.Ado/CommandHelper.cs +++ b/Simple.Data.Ado/CommandHelper.cs @@ -10,7 +10,7 @@ namespace Simple.Data.Ado { using System.Linq; - internal class CommandHelper + public class CommandHelper { private readonly AdoAdapter _adapter; private readonly ISchemaProvider _schemaProvider; @@ -23,7 +23,7 @@ public CommandHelper(AdoAdapter adapter) internal IDbCommand Create(IDbConnection connection, string sql, IList values) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(_adapter.AdoOptions); command.CommandText = PrepareCommand(sql, command); command.ClearParameterValues(); @@ -34,7 +34,7 @@ internal IDbCommand Create(IDbConnection connection, string sql, IList v internal IDbCommand Create(IDbConnection connection, CommandBuilder commandBuilder) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(_adapter.AdoOptions); command.CommandText = commandBuilder.Text; PrepareCommand(commandBuilder, command); return command; @@ -50,8 +50,8 @@ private string PrepareCommand(IEnumerable sql, IDbCommand command) { if (c == '?') { - var parameter = command.CreateParameter(); - parameter.ParameterName = _schemaProvider.NameParameter("p" + index); + var parameter = parameterFactory.CreateParameter(_schemaProvider.NameParameter("p" + index)); + //parameter.ParameterName = _schemaProvider.NameParameter("p" + index); command.Parameters.Add(parameter); sqlBuilder.Append(parameter.ParameterName); @@ -122,7 +122,7 @@ public static object FixObjectType(object value) public IDbCommand Create(IDbConnection connection, string insertSql) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(_adapter.AdoOptions); command.CommandText = PrepareCommand(insertSql, command); return command; @@ -130,16 +130,16 @@ public IDbCommand Create(IDbConnection connection, string insertSql) public IDbCommand CreateInsert(IDbConnection connection, string insertSql, IEnumerable columns) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(_adapter.AdoOptions); command.CommandText = PrepareInsertCommand(insertSql, command, columns); command.ClearParameterValues(); return command; } - internal IDbCommand CreateInsert(IDbConnection connection, string sql, IEnumerable columns, IList values) + public IDbCommand CreateInsert(IDbConnection connection, string sql, IEnumerable columns, IList values) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(_adapter.AdoOptions); command.CommandText = PrepareInsertCommand(sql, command, columns); command.ClearParameterValues(); diff --git a/Simple.Data.Ado/CommandTemplate.cs b/Simple.Data.Ado/CommandTemplate.cs index bb012307..10ba8a21 100644 --- a/Simple.Data.Ado/CommandTemplate.cs +++ b/Simple.Data.Ado/CommandTemplate.cs @@ -29,19 +29,19 @@ public Dictionary Index get { return _index; } } - public IDbCommand GetDbCommand(IDbConnection connection, IEnumerable parameterValues) + public IDbCommand GetDbCommand(AdoAdapter adapter, IDbConnection connection, IEnumerable parameterValues) { - var command = connection.CreateCommand(); + var command = connection.CreateCommand(adapter.AdoOptions); command.CommandText = _commandText; - foreach (var parameter in CreateParameters(command, parameterValues)) + foreach (var parameter in CreateParameters(adapter.GetSchema(), command, parameterValues)) { command.Parameters.Add(parameter); } return command; } - private IEnumerable CreateParameters(IDbCommand command, IEnumerable parameterValues) + private IEnumerable CreateParameters(DatabaseSchema schema, IDbCommand command, IEnumerable parameterValues) { var fixedParameters = _parameters.Where(pt => pt.Type == ParameterType.FixedValue).ToArray(); if ((!parameterValues.Any(pv => pv != null)) && fixedParameters.Length == 0) yield break; @@ -55,14 +55,14 @@ private IEnumerable CreateParameters(IDbCommand command, IEnum var columnParameters = _parameters.Where(pt => pt.Type != ParameterType.FixedValue).ToArray(); foreach (var parameter in parameterValues.Any(o => o is IEnumerable && !(o is string)) || parameterValues.Any(o => o is IRange) - ? parameterValues.SelectMany((v, i) => CreateParameters(command, columnParameters[i], v)) + ? parameterValues.SelectMany((v, i) => CreateParameters(schema, command, columnParameters[i], v)) : parameterValues.Select((v, i) => CreateParameter(command, columnParameters[i], v))) { yield return parameter; } } - private IEnumerable CreateParameters(IDbCommand command, ParameterTemplate parameterTemplate, object value) + private IEnumerable CreateParameters(DatabaseSchema schema, IDbCommand command, ParameterTemplate parameterTemplate, object value) { if (value == null || TypeHelper.IsKnownType(value.GetType()) || parameterTemplate.DbType == DbType.Binary) { @@ -75,7 +75,7 @@ private IEnumerable CreateParameters(IDbCommand command, Param { yield return CreateParameter(command, parameterTemplate, range.Start, "_start"); yield return CreateParameter(command, parameterTemplate, range.End, "_end"); - CommandBuilder.SetBetweenInCommandText(command, parameterTemplate.Name); + new CommandBuilder(schema).SetBetweenInCommandText(command, parameterTemplate.Name); } else { @@ -89,7 +89,7 @@ private IEnumerable CreateParameters(IDbCommand command, Param builder.AppendFormat(",{0}_{1}", parameterTemplate.Name, i); yield return CreateParameter(command, parameterTemplate, array[i], "_" + i); } - RewriteSqlEqualityToInClause(command, parameterTemplate, builder); + RewriteSqlEqualityToInClause(schema, command, parameterTemplate, builder); } else { @@ -99,19 +99,19 @@ private IEnumerable CreateParameters(IDbCommand command, Param } } - private static void RewriteSqlEqualityToInClause(IDbCommand command, ParameterTemplate parameterTemplate, StringBuilder builder) + private static void RewriteSqlEqualityToInClause(DatabaseSchema schema, IDbCommand command, ParameterTemplate parameterTemplate, StringBuilder builder) { if (command.CommandText.Contains("!= " + parameterTemplate.Name)) { command.CommandText = command.CommandText.Replace("!= " + parameterTemplate.Name, - "NOT IN (" + + schema.Operators.NotIn + " (" + builder.ToString().Substring(1) + ")"); } else { command.CommandText = command.CommandText.Replace("= " + parameterTemplate.Name, - "IN (" + + schema.Operators.In + " (" + builder.ToString().Substring(1) + ")"); } diff --git a/Simple.Data.Ado/ConnectionEx.cs b/Simple.Data.Ado/ConnectionEx.cs index b6cb6d26..b4d6a2ac 100644 --- a/Simple.Data.Ado/ConnectionEx.cs +++ b/Simple.Data.Ado/ConnectionEx.cs @@ -11,7 +11,7 @@ public static class ConnectionEx { public static IDisposable MaybeDisposable(this IDbConnection connection) { - if (connection.State == ConnectionState.Open) return ActionDisposable.NoOp; + if (connection == null || connection.State == ConnectionState.Open) return ActionDisposable.NoOp; return new ActionDisposable(connection.Dispose); } } diff --git a/Simple.Data.Ado/DataReaderEnumerable.cs b/Simple.Data.Ado/DataReaderEnumerable.cs index 8755ebc5..313554ec 100644 --- a/Simple.Data.Ado/DataReaderEnumerable.cs +++ b/Simple.Data.Ado/DataReaderEnumerable.cs @@ -15,6 +15,7 @@ internal class DataReaderEnumerable : IEnumerable> private IEnumerable> _cache; private IDictionary _index; private readonly IDbCommand _command; + private readonly IDbTransaction _transaction; private readonly Func _createConnection; public DataReaderEnumerable(IDbCommand command, Func createConnection) @@ -29,6 +30,11 @@ public DataReaderEnumerable(IDbCommand command, Func createConnec _index = index; } + public DataReaderEnumerable(IDbCommand command, IDbTransaction transaction, IDictionary index) : this(command, () => transaction.Connection, index) + { + _transaction = transaction; + } + public IEnumerator> GetEnumerator() { if (_cache != null) return _cache.GetEnumerator(); @@ -40,6 +46,10 @@ public IEnumerator> GetEnumerator() { command = (IDbCommand) clonable.Clone(); command.Connection = _createConnection(); + if (_transaction != null) + { + command.Transaction = _transaction; + } } else { @@ -102,6 +112,11 @@ public bool MoveNext() if (_reader == null) return false; } + if (_reader.IsClosed) + { + return false; + } + return _reader.Read() ? SetCurrent() : EndRead(); } @@ -137,16 +152,9 @@ private bool EndRead() private void ExecuteReader() { - try - { - _command.Connection.OpenIfClosed(); - _reader = _command.ExecuteReader(); - CreateIndexIfNecessary(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, ex); - } + _command.Connection.OpenIfClosed(); + _reader = _command.TryExecuteReader(); + CreateIndexIfNecessary(); } private void CreateIndexIfNecessary() diff --git a/Simple.Data.Ado/DataReaderMultipleEnumerator.cs b/Simple.Data.Ado/DataReaderMultipleEnumerator.cs index 68e247b1..32fb3705 100644 --- a/Simple.Data.Ado/DataReaderMultipleEnumerator.cs +++ b/Simple.Data.Ado/DataReaderMultipleEnumerator.cs @@ -51,16 +51,9 @@ public bool MoveNext() private void ExecuteReader() { - try - { - _connection.OpenIfClosed(); - _reader = _command.ExecuteReader(); - _index = _index ?? _reader.CreateDictionaryIndex(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, ex); - } + _connection.OpenIfClosed(); + _reader = _command.TryExecuteReader(); + _index = _index ?? _reader.CreateDictionaryIndex(); } public void Reset() diff --git a/Simple.Data.Ado/DbCommandExtensions.cs b/Simple.Data.Ado/DbCommandExtensions.cs index a39400f7..31b0a7ab 100644 --- a/Simple.Data.Ado/DbCommandExtensions.cs +++ b/Simple.Data.Ado/DbCommandExtensions.cs @@ -8,7 +8,7 @@ using System.Dynamic; using System.Linq; - static class DbCommandExtensions + public static class DbCommandExtensions { public static IEnumerable> ToEnumerable(this IDbCommand command, Func createConnection) { @@ -57,7 +57,7 @@ private static object FixObjectType(object value) return value; } - public static IDataReader ExecuteReaderWithExceptionWrap(this IDbCommand command) + public static IDataReader TryExecuteReader(this IDbCommand command) { command.WriteTrace(); try @@ -66,12 +66,30 @@ public static IDataReader ExecuteReaderWithExceptionWrap(this IDbCommand command } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command.CommandText, - command.Parameters.Cast() - .ToDictionary(p => p.ParameterName, p => p.Value)); + throw CreateAdoAdapterException(command, ex); } } + public static int TryExecuteNonQuery(this IDbCommand command) + { + command.WriteTrace(); + try + { + return command.ExecuteNonQuery(); + } + catch (DbException ex) + { + throw CreateAdoAdapterException(command, ex); + } + } + + private static AdoAdapterException CreateAdoAdapterException(IDbCommand command, DbException ex) + { + return new AdoAdapterException(ex.Message, command.CommandText, + command.Parameters.Cast() + .ToDictionary(p => p.ParameterName, p => p.Value), ex); + } + internal static void DisposeCommandAndReader(IDbConnection connection, IDbCommand command, IDataReader reader) { using (connection) @@ -102,6 +120,16 @@ public static void SetParameterValue(this IDbCommand command, int index, object { ((IDbDataParameter) command.Parameters[index]).Value = CommandHelper.FixObjectType(value); } + + public static IEnumerable> ToEnumerable(this IDbCommand command, IDbTransaction transaction) + { + return ToEnumerable(command, transaction, null); + } + + public static IEnumerable> ToEnumerable(this IDbCommand command, IDbTransaction transaction, IDictionary index) + { + return new DataReaderEnumerable(command, transaction, index); + } } class EnumerableShim : IEnumerable diff --git a/Simple.Data.Ado/DbConnectionEx.cs b/Simple.Data.Ado/DbConnectionEx.cs index 9cb2ee32..e4f8b847 100644 --- a/Simple.Data.Ado/DbConnectionEx.cs +++ b/Simple.Data.Ado/DbConnectionEx.cs @@ -16,5 +16,14 @@ public static void OpenIfClosed(this IDbConnection connection) connection.Open(); } } + + public static IDbCommand CreateCommand(this IDbConnection connection, AdoOptions options) + { + if (options == null || options.CommandTimeout < 0) return connection.CreateCommand(); + + var command = connection.CreateCommand(); + command.CommandTimeout = options.CommandTimeout; + return command; + } } } diff --git a/Simple.Data.Ado/DelegatingConnectionBase1.cs b/Simple.Data.Ado/DelegatingConnectionBase1.cs index d06b925c..0877a7e7 100644 --- a/Simple.Data.Ado/DelegatingConnectionBase1.cs +++ b/Simple.Data.Ado/DelegatingConnectionBase1.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Data; using System.Data.Common; -using System.Linq; -using System.Text; namespace Simple.Data.Ado { diff --git a/Simple.Data.Ado/EagerLoadingEnumerable.cs b/Simple.Data.Ado/EagerLoadingEnumerable.cs index 3b48483d..a5aa5669 100644 --- a/Simple.Data.Ado/EagerLoadingEnumerable.cs +++ b/Simple.Data.Ado/EagerLoadingEnumerable.cs @@ -83,7 +83,10 @@ private Dictionary, IDictionary row) { + if (row.All(kvp => ReferenceEquals(null, kvp.Value))) return; if (Collection == null) Collection = new HashSet>(new DictionaryEqualityComparer()); Collection.Add(row); } public void SetSingle(IDictionary row) { + if (row.All(kvp => ReferenceEquals(null, kvp.Value))) return; Single = row; } } diff --git a/Simple.Data.Ado/EventHandlerEx.cs b/Simple.Data.Ado/EventHandlerEx.cs new file mode 100644 index 00000000..2ad7dfa7 --- /dev/null +++ b/Simple.Data.Ado/EventHandlerEx.cs @@ -0,0 +1,19 @@ +namespace Simple.Data.Ado +{ + using System; + + internal static class EventHandlerEx + { + public static T Raise(this EventHandler handler, object sender, Func args) + where T : EventArgs + { + if (handler != null) + { + var e = args(); + handler(sender, e); + return e; + } + return null; + } + } +} \ No newline at end of file diff --git a/Simple.Data.Ado/ExpressionFormatter.cs b/Simple.Data.Ado/ExpressionFormatter.cs index c6124b2e..107331d2 100644 --- a/Simple.Data.Ado/ExpressionFormatter.cs +++ b/Simple.Data.Ado/ExpressionFormatter.cs @@ -11,7 +11,7 @@ class ExpressionFormatter : ExpressionFormatterBase private readonly DatabaseSchema _schema; private readonly SimpleReferenceFormatter _simpleReferenceFormatter; - public ExpressionFormatter(ICommandBuilder commandBuilder, DatabaseSchema schema) + public ExpressionFormatter(ICommandBuilder commandBuilder, DatabaseSchema schema) : base(() => schema.Operators) { _commandBuilder = commandBuilder; _schema = schema; diff --git a/Simple.Data.Ado/ExpressionFormatterBase.cs b/Simple.Data.Ado/ExpressionFormatterBase.cs index 16a26e9c..fe5c45a1 100644 --- a/Simple.Data.Ado/ExpressionFormatterBase.cs +++ b/Simple.Data.Ado/ExpressionFormatterBase.cs @@ -4,12 +4,16 @@ namespace Simple.Data.Ado { + using Schema; + abstract class ExpressionFormatterBase : IExpressionFormatter { + private readonly Lazy _operators; private readonly Dictionary> _expressionFormatters; - protected ExpressionFormatterBase() + protected ExpressionFormatterBase(Func createOperators) { + _operators = new Lazy(createOperators); _expressionFormatters = new Dictionary> { {SimpleExpressionType.And, LogicalExpressionToWhereClause}, @@ -17,14 +21,19 @@ protected ExpressionFormatterBase() {SimpleExpressionType.Equal, EqualExpressionToWhereClause}, {SimpleExpressionType.NotEqual, NotEqualExpressionToWhereClause}, {SimpleExpressionType.Function, FunctionExpressionToWhereClause}, - {SimpleExpressionType.GreaterThan, expr => BinaryExpressionToWhereClause(expr, ">")}, - {SimpleExpressionType.GreaterThanOrEqual, expr => BinaryExpressionToWhereClause(expr, ">=")}, - {SimpleExpressionType.LessThan, expr => BinaryExpressionToWhereClause(expr, "<")}, - {SimpleExpressionType.LessThanOrEqual, expr => BinaryExpressionToWhereClause(expr, "<=")}, + {SimpleExpressionType.GreaterThan, expr => BinaryExpressionToWhereClause(expr, Operators.GreaterThan)}, + {SimpleExpressionType.GreaterThanOrEqual, expr => BinaryExpressionToWhereClause(expr, Operators.GreaterThanOrEqual)}, + {SimpleExpressionType.LessThan, expr => BinaryExpressionToWhereClause(expr, Operators.LessThan)}, + {SimpleExpressionType.LessThanOrEqual, expr => BinaryExpressionToWhereClause(expr, Operators.LessThanOrEqual)}, {SimpleExpressionType.Empty, expr => string.Empty }, }; } + protected Operators Operators + { + get { return _operators.Value; } + } + public string Format(SimpleExpression expression) { Func formatter; @@ -47,18 +56,18 @@ private string LogicalExpressionToWhereClause(SimpleExpression expression) private string EqualExpressionToWhereClause(SimpleExpression expression) { - if (expression.RightOperand == null) return string.Format("{0} IS NULL", FormatObject(expression.LeftOperand, null)); - if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, "="); + if (expression.RightOperand == null) return string.Format("{0} {1}", FormatObject(expression.LeftOperand, null), Operators.IsNull); + if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, Operators.Equal); - return FormatAsComparison(expression, "="); + return FormatAsComparison(expression, Operators.Equal); } private string NotEqualExpressionToWhereClause(SimpleExpression expression) { - if (expression.RightOperand == null) return string.Format("{0} IS NOT NULL", FormatObject(expression.LeftOperand, null)); - if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, "!="); + if (expression.RightOperand == null) return string.Format("{0} {1}", FormatObject(expression.LeftOperand, null), Operators.IsNotNull); + if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, Operators.NotEqual); - return FormatAsComparison(expression, "!="); + return FormatAsComparison(expression, Operators.NotEqual); } private string FormatAsComparison(SimpleExpression expression, string op) @@ -67,24 +76,6 @@ private string FormatAsComparison(SimpleExpression expression, string op) FormatObject(expression.RightOperand, expression.LeftOperand)); } - private string TryFormatAsInList(SimpleExpression expression, IEnumerable list, string op) - { - return (list != null) - ? - string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand, expression.RightOperand), op, FormatList(list, expression.LeftOperand)) - : - null; - } - - private string TryFormatAsRange(SimpleExpression expression, IRange range, string op) - { - return (range != null) - ? - string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand, expression.RightOperand), op, FormatRange(range, expression.LeftOperand)) - : - null; - } - private string FunctionExpressionToWhereClause(SimpleExpression expression) { var function = expression.RightOperand as SimpleFunction; @@ -92,13 +83,13 @@ private string FunctionExpressionToWhereClause(SimpleExpression expression) if (function.Name.Equals("like", StringComparison.InvariantCultureIgnoreCase)) { - return string.Format("{0} LIKE {1}", FormatObject(expression.LeftOperand, expression.RightOperand), + return string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand, expression.RightOperand), Operators.Like, FormatObject(function.Args[0], expression.LeftOperand)); } if (function.Name.Equals("notlike", StringComparison.InvariantCultureIgnoreCase)) { - return string.Format("{0} NOT LIKE {1}", FormatObject(expression.LeftOperand, expression.RightOperand), + return string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand, expression.RightOperand), Operators.NotLike, FormatObject(function.Args[0], expression.LeftOperand)); } @@ -116,4 +107,27 @@ private string BinaryExpressionToWhereClause(SimpleExpression expression, string protected abstract string FormatRange(IRange range, object otherOperand); protected abstract string FormatList(IEnumerable list, object otherOperand); } + + public class Operators + { + public virtual string Equal { get { return "="; } } + public virtual string NotEqual { get { return "!="; } } + public virtual string GreaterThan { get { return ">"; } } + public virtual string GreaterThanOrEqual { get { return ">="; } } + public virtual string LessThan { get { return "<"; } } + public virtual string LessThanOrEqual { get { return "<="; } } + public virtual string IsNull { get { return "IS NULL"; } } + public virtual string IsNotNull { get { return "IS NOT NULL"; } } + public virtual string Like { get { return "LIKE"; } } + public virtual string NotLike { get { return "NOT LIKE"; } } + public virtual string In { get { return "IN"; } } + public virtual string NotIn { get { return "NOT IN"; } } + public virtual string Between { get { return "BETWEEN"; } } + public virtual string NotBetween { get { return "NOT BETWEEN"; } } + public virtual string Add { get { return "+"; } } + public virtual string Subtract { get { return "-"; } } + public virtual string Multiply { get { return "*"; } } + public virtual string Divide { get { return "/"; } } + public virtual string Modulo { get { return "%"; } } + } } \ No newline at end of file diff --git a/Simple.Data.Ado/ExpressionHasher.cs b/Simple.Data.Ado/ExpressionHasher.cs index eebd39fa..2970b6c2 100644 --- a/Simple.Data.Ado/ExpressionHasher.cs +++ b/Simple.Data.Ado/ExpressionHasher.cs @@ -3,8 +3,14 @@ namespace Simple.Data.Ado { + using System; + class ExpressionHasher : ExpressionFormatterBase { + public ExpressionHasher() : base(() => new Operators()) + { + } + protected override string FormatObject(object value, object otherOperand) { var reference = value as SimpleReference; diff --git a/Simple.Data.Ado/ICommandBuilder.cs b/Simple.Data.Ado/ICommandBuilder.cs index 60c43e92..9ad6a93e 100644 --- a/Simple.Data.Ado/ICommandBuilder.cs +++ b/Simple.Data.Ado/ICommandBuilder.cs @@ -10,8 +10,8 @@ public interface ICommandBuilder ParameterTemplate AddParameter(object value); ParameterTemplate AddParameter(object value, Column column); void Append(string text); - IDbCommand GetCommand(IDbConnection connection); - IDbCommand GetRepeatableCommand(IDbConnection connection); + IDbCommand GetCommand(IDbConnection connection, AdoOptions options); + IDbCommand GetRepeatableCommand(IDbConnection connection, AdoOptions options); CommandTemplate GetCommandTemplate(Table table); IEnumerable> Parameters { get; } string Text { get; } diff --git a/Simple.Data.Ado/ICustomInserter.cs b/Simple.Data.Ado/ICustomInserter.cs index 46538591..ae4e0ac3 100644 --- a/Simple.Data.Ado/ICustomInserter.cs +++ b/Simple.Data.Ado/ICustomInserter.cs @@ -8,6 +8,6 @@ namespace Simple.Data.Ado { public interface ICustomInserter { - IDictionary Insert(AdoAdapter adapter, string tableName, IDictionary data, IDbTransaction transaction = null); + IDictionary Insert(AdoAdapter adapter, string tableName, IDictionary data, IDbTransaction transaction = null, bool resultRequired = false); } } diff --git a/Simple.Data.Ado/ICustomQueryBuilder.cs b/Simple.Data.Ado/ICustomQueryBuilder.cs new file mode 100644 index 00000000..fcf3839d --- /dev/null +++ b/Simple.Data.Ado/ICustomQueryBuilder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Simple.Data.Ado +{ + public interface ICustomQueryBuilder + { + ICommandBuilder Build(AdoAdapter adapter, int bulkIndex, SimpleQuery query, out IEnumerable unhandledClauses); + } +} diff --git a/Simple.Data.Ado/IObservableQueryRunner.cs b/Simple.Data.Ado/IObservableQueryRunner.cs index 3dac1de5..07912d02 100644 --- a/Simple.Data.Ado/IObservableQueryRunner.cs +++ b/Simple.Data.Ado/IObservableQueryRunner.cs @@ -25,7 +25,7 @@ public IObservable> Run(IDbCommand command, IDbConne { throw new AdoAdapterException(ex.Message, ex); } - var reader = command.ExecuteReaderWithExceptionWrap(); + var reader = command.TryExecuteReader(); if (index == null) index = reader.CreateDictionaryIndex(); return ColdObservable.Create>(o => RunObservable(command, connection, reader, index, o)); diff --git a/Simple.Data.Ado/IQueryPager.cs b/Simple.Data.Ado/IQueryPager.cs index d5329c68..137c7455 100644 --- a/Simple.Data.Ado/IQueryPager.cs +++ b/Simple.Data.Ado/IQueryPager.cs @@ -5,6 +5,6 @@ namespace Simple.Data.Ado public interface IQueryPager { IEnumerable ApplyLimit(string sql, int take); - IEnumerable ApplyPaging(string sql, int skip, int take); + IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take); } } \ No newline at end of file diff --git a/Simple.Data.Ado/ISchemaConnectionProvider.cs b/Simple.Data.Ado/ISchemaConnectionProvider.cs new file mode 100644 index 00000000..8134bd3e --- /dev/null +++ b/Simple.Data.Ado/ISchemaConnectionProvider.cs @@ -0,0 +1,9 @@ +namespace Simple.Data.Ado +{ + public interface ISchemaConnectionProvider : IConnectionProvider + { + void SetSchema(string schema); + string Schema { get; } + string ConnectionProviderKey { get; } + } +} diff --git a/Simple.Data.Ado/Joiner.cs b/Simple.Data.Ado/Joiner.cs index 5476b7b8..79b8e6ac 100644 --- a/Simple.Data.Ado/Joiner.cs +++ b/Simple.Data.Ado/Joiner.cs @@ -8,7 +8,7 @@ namespace Simple.Data.Ado { - class Joiner + public class Joiner { private readonly JoinType _joinType; private readonly DatabaseSchema _schema; @@ -103,15 +103,15 @@ private void AddJoin(ObjectName table1Name, ObjectName table2Name, JoinType join private static ForeignKey GetForeignKey(Table table1, Table table2) { var foreignKey = - table2.ForeignKeys.SingleOrDefault( + table2.ForeignKeys.FirstOrDefault( fk => fk.MasterTable.Schema == table1.Schema && fk.MasterTable.Name == table1.ActualName) ?? - table1.ForeignKeys.SingleOrDefault( + table1.ForeignKeys.FirstOrDefault( fk => fk.MasterTable.Schema == table2.Schema && fk.MasterTable.Name == table2.ActualName); if (foreignKey == null) throw new SchemaResolutionException( - string.Format("Could not join '{0}' and '{1}'", table1.ActualName, table2.ActualName)); + string.Format("Could not join tables '{0}' and '{1}', foreign key not found.", table1.ActualName, table2.ActualName)); return foreignKey; } @@ -121,12 +121,12 @@ private string MakeJoinText(Table rightTable, string alias, ForeignKey foreignKe builder.AppendFormat(" JOIN {0}", rightTable.QualifiedName); if (!string.IsNullOrWhiteSpace(alias)) builder.Append(" " + _schema.QuoteObjectName(alias)); builder.Append(" ON ("); - builder.Append(FormatJoinExpression(foreignKey, 0, alias)); + builder.Append(FormatJoinExpression(foreignKey, 0, rightTable, alias)); for (int i = 1; i < foreignKey.Columns.Length; i++) { builder.Append(" AND "); - builder.Append(FormatJoinExpression(foreignKey, i, alias)); + builder.Append(FormatJoinExpression(foreignKey, i, rightTable, alias)); } builder.Append(")"); return builder.ToString(); @@ -167,13 +167,25 @@ private SimpleExpression CreateJoinExpression(ObjectReference table, ForeignKey return masterObjectReference == detailObjectReference; } - private string FormatJoinExpression(ForeignKey foreignKey, int columnIndex, string alias) + private string FormatJoinExpression(ForeignKey foreignKey, int columnIndex, Table rightTable, string alias) { - var leftTable = string.IsNullOrWhiteSpace(alias) - ? _schema.QuoteObjectName(foreignKey.MasterTable) - : _schema.QuoteObjectName(alias); - return string.Format("{0}.{1} = {2}.{3}", leftTable, _schema.QuoteObjectName(foreignKey.UniqueColumns[columnIndex]), - _schema.QuoteObjectName(foreignKey.DetailTable), _schema.QuoteObjectName(foreignKey.Columns[columnIndex])); + if (rightTable.ActualName == foreignKey.MasterTable.Name && + rightTable.Schema == foreignKey.MasterTable.Schema) + { + var rightTableName = string.IsNullOrWhiteSpace(alias) + ? _schema.QuoteObjectName(foreignKey.MasterTable) + : _schema.QuoteObjectName(alias); + return string.Format("{0}.{1} = {2}.{3}", + rightTableName, _schema.QuoteObjectName(foreignKey.UniqueColumns[columnIndex]), + _schema.QuoteObjectName(foreignKey.DetailTable), _schema.QuoteObjectName(foreignKey.Columns[columnIndex]) + ); + } + + var leftTableName = string.IsNullOrWhiteSpace(alias) + ? _schema.QuoteObjectName(foreignKey.DetailTable) + : _schema.QuoteObjectName(alias); + return string.Format("{0}.{1} = {2}.{3}", _schema.QuoteObjectName(foreignKey.MasterTable), _schema.QuoteObjectName(foreignKey.UniqueColumns[columnIndex]), + leftTableName, _schema.QuoteObjectName(foreignKey.Columns[columnIndex])); } private string JoinKeyword diff --git a/Simple.Data.Ado/ListExtensions.cs b/Simple.Data.Ado/ListExtensions.cs index 2d55aa17..bc05cd18 100644 --- a/Simple.Data.Ado/ListExtensions.cs +++ b/Simple.Data.Ado/ListExtensions.cs @@ -1,5 +1,6 @@ namespace Simple.Data.Ado { + using System; using System.Collections.Generic; internal static class ListExtensions @@ -8,7 +9,18 @@ public static void SetWithBuffer(this List list, int index, T value) { if (list.Capacity > index) { - list[index] = value; + while (list.Count < index) + { + list.Add(default(T)); + } + if (list.Count == index) + { + list.Add(value); + } + else + { + list[index] = value; + } } else { diff --git a/Simple.Data.Ado/NugetPack.cmd b/Simple.Data.Ado/NugetPack.cmd index 05da626a..318cc787 100644 --- a/Simple.Data.Ado/NugetPack.cmd +++ b/Simple.Data.Ado/NugetPack.cmd @@ -1 +1 @@ -nuget pack -sym Simple.Data.Ado.csproj -Properties Configuration=Release +nuget pack -sym Simple.Data.Ado.csproj -Properties Configuration=Release;Platform=AnyCPU -Build diff --git a/Simple.Data.Ado/ObjectName.cs b/Simple.Data.Ado/ObjectName.cs index 1ae0afdc..25e3b117 100644 --- a/Simple.Data.Ado/ObjectName.cs +++ b/Simple.Data.Ado/ObjectName.cs @@ -57,7 +57,7 @@ public bool Equals(ObjectName other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other._schema, _schema) && Equals(other._name, _name); + return Equals(other._schema, _schema) && Equals(other._name, _name) && Equals(other._alias, _alias); } /// @@ -86,7 +86,7 @@ public override int GetHashCode() { unchecked { - return ((_schema??string.Empty).GetHashCode()*397) ^ _name.GetHashCode(); + return ((_schema??string.Empty).GetHashCode()*397) ^ (_name.GetHashCode()*397) ^ ((_alias??string.Empty).GetHashCode()); } } diff --git a/Simple.Data.Ado/OptimizedDictionary2.cs b/Simple.Data.Ado/OptimizedDictionary2.cs index 6b8f9b16..d68143ad 100644 --- a/Simple.Data.Ado/OptimizedDictionary2.cs +++ b/Simple.Data.Ado/OptimizedDictionary2.cs @@ -14,7 +14,8 @@ public class OptimizedDictionary : IDictionary, IClone public OptimizedDictionary(IDictionary index, IEnumerable values) { _index = index; - _values = new List(values); + _values = new List(_index.Count); + _values.AddRange(values); } /// @@ -218,6 +219,10 @@ public TValue this[TKey key] { throw new KeyNotFoundException(); } + catch (ArgumentOutOfRangeException) + { + return default(TValue); + } } set { diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index 1a40b048..e82670c7 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -40,12 +40,12 @@ public IEnumerable Execute(IDictionary suppliedParame var procedure = _adapter.GetSchema().FindProcedure(_procedureName); if (procedure == null) { - throw new UnresolvableObjectException(_procedureName.ToString()); + throw new UnresolvableObjectException(_procedureName.ToString(), string.Format("Procedure '{0}' not found.", _procedureName)); } var cn = transaction == null ? _adapter.CreateConnection() : transaction.Connection; using (cn.MaybeDisposable()) - using (var command = cn.CreateCommand()) + using (var command = cn.CreateCommand(_adapter.AdoOptions)) { command.Transaction = transaction; command.CommandText = procedure.QualifiedName; @@ -61,7 +61,7 @@ public IEnumerable Execute(IDictionary suppliedParame } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command); + throw new AdoAdapterException(ex.Message, command, ex); } } } @@ -77,9 +77,8 @@ private static void RetrieveOutputParameterValues(Procedure procedure, IDbComman public IEnumerable ExecuteReader(IDbCommand command) { - command.WriteTrace(); command.Connection.OpenIfClosed(); - using (var reader = command.ExecuteReader()) + using (var reader = command.TryExecuteReader()) { // Reader isn't always returned - added check to stop NullReferenceException if ((reader != null) && (reader.FieldCount > 0)) @@ -95,10 +94,11 @@ public IEnumerable ExecuteReader(IDbCommand command) private static IEnumerable ExecuteNonQuery(IDbCommand command) { - command.WriteTrace(); - Trace.TraceInformation("ExecuteNonQuery", "Simple.Data.SqlTest"); +#if(DEBUG) + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Verbose, SimpleDataTraceSources.DebugMessageId, "Simple.Data.SqlTest ExecuteNonQuery"); +#endif command.Connection.OpenIfClosed(); - command.ExecuteNonQuery(); + command.TryExecuteNonQuery(); return Enumerable.Empty(); } diff --git a/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs b/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs new file mode 100644 index 00000000..9f98a15c --- /dev/null +++ b/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Simple.Data.Ado +{ + [AttributeUsage(AttributeTargets.Assembly)] + public abstract class ProviderAssemblyAttributeBase : Attribute + { + static ProviderAssemblyAttributeBase() + { + //AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomainOnReflectionOnlyAssemblyResolve; + } + + private static Assembly CurrentDomainOnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) + { + return Assembly.Load(args.Name); + } + + private readonly HashSet _adoProviderNames; + + protected ProviderAssemblyAttributeBase(string providerName, params string[] additionalProviderNames) + { + _adoProviderNames = new HashSet(additionalProviderNames, StringComparer.InvariantCultureIgnoreCase) {providerName}; + } + + public bool IsForProviderName(string adoProviderName) + { + return _adoProviderNames.Contains(adoProviderName); + } + + public abstract bool TryGetProvider(string connectionString, out IConnectionProvider provider, out Exception exception); + + public static IEnumerable Get(Assembly assembly) + { + if (assembly.ReflectionOnly) + { + foreach (var referencedAssembly in assembly.GetReferencedAssemblies()) + { + if (AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().All(a => a.GetFullName() != referencedAssembly.FullName)) + { + try + { + Assembly.ReflectionOnlyLoad(referencedAssembly.FullName); + } + catch (FileNotFoundException) + { + return Enumerable.Empty(); + } + } + } + var hasAttribute = assembly.GetCustomAttributesData().Any( + cad => typeof (ProviderAssemblyAttributeBase).IsAssignableFrom(cad.Constructor.DeclaringType)); + if (hasAttribute) + { + assembly = Assembly.Load(assembly.GetFullName()); + } + else + { + return Enumerable.Empty(); + } + } + return assembly.GetCustomAttributes(typeof (ProviderAssemblyAttributeBase), false) + .Cast(); + } + } +} diff --git a/Simple.Data.Ado/ProviderHelper.cs b/Simple.Data.Ado/ProviderHelper.cs index 31912edd..482318a8 100644 --- a/Simple.Data.Ado/ProviderHelper.cs +++ b/Simple.Data.Ado/ProviderHelper.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Configuration; using System.Data.OleDb; +using System.Linq; using System.Reflection; using System.IO; using System.Text.RegularExpressions; @@ -66,7 +68,7 @@ private static string GetFileExtension(string filename) { var extension = Path.GetExtension(filename); - if (extension == null) throw new ArgumentException("Unrecognised file."); + if (extension == null) throw new ArgumentException(string.Format("Unrecognised file name '{0}': no extension.", filename), "filename"); return extension.TrimStart('.').ToLower(); } @@ -80,7 +82,7 @@ private static IConnectionProvider ComposeProvider(string extension) return Composer.Default.Compose(extension); } - public IConnectionProvider GetProviderByConnectionName(string connectionName) + public IConnectionProvider GetProviderByConnectionName(string connectionName, string schemaName = null) { var connectionSettings = ConfigurationManager.ConnectionStrings[connectionName]; if (connectionSettings == null) @@ -88,24 +90,36 @@ public IConnectionProvider GetProviderByConnectionName(string connectionName) throw new ArgumentOutOfRangeException("connectionName"); } - return GetProviderByConnectionString(connectionSettings.ConnectionString, connectionSettings.ProviderName); + return GetProviderByConnectionString(connectionSettings.ConnectionString, connectionSettings.ProviderName, schemaName); } - public IConnectionProvider GetProviderByConnectionString(string connectionString, string providerName) + public IConnectionProvider GetProviderByConnectionString(string connectionString, string providerName, string schemaName = null) { - return _connectionProviderCache.GetOrAdd(new ConnectionToken(connectionString, providerName), + return _connectionProviderCache.GetOrAdd(new ConnectionToken(connectionString, providerName, schemaName), LoadProviderByConnectionToken); } private static IConnectionProvider LoadProviderByConnectionToken(ConnectionToken token) { - var provider = ComposeProvider(token.ProviderName); + IConnectionProvider provider; + + if (TryLoadAssemblyUsingAttribute(token.ConnectionString, token.ProviderName, out provider)) + { + return provider; + } + + provider = ComposeProvider(token.ProviderName); if (provider == null) { - throw new InvalidOperationException("Provider could not be resolved."); + throw new InvalidOperationException(string.Format("Provider '{0}' could not be resolved.", token.ProviderName)); } provider.SetConnectionString(token.ConnectionString); + + var schemaConnectionProvider = provider as ISchemaConnectionProvider; + if(schemaConnectionProvider != null) + schemaConnectionProvider.SetSchema(token.SchemaName); + return provider; } @@ -152,6 +166,75 @@ private static T GetCustomProviderExport(ISchemaProvider schemaProvider) } } + internal static bool TryLoadAssemblyUsingAttribute(string connectionString, string providerName, out IConnectionProvider connectionProvider) + { + var attributes = LoadAssemblyAttributes(); + if (attributes.Count == 0) + { + connectionProvider = null; + return false; + } + if (!string.IsNullOrWhiteSpace(providerName)) + { + attributes = attributes.Where(a => a.IsForProviderName(providerName)).ToList(); + } + if (attributes.Count == 0) + { + connectionProvider = null; + return false; + } + + return LoadUsingAssemblyAttribute(connectionString, attributes, out connectionProvider); + } + + private static bool LoadUsingAssemblyAttribute(string connectionString, ICollection attributes, + out IConnectionProvider connectionProvider) + { + if (attributes.Count == 0) + { + { + connectionProvider = null; + return true; + } + } + + foreach (var attribute in attributes) + { + Exception exception; + if (attribute.TryGetProvider(connectionString, out connectionProvider, out exception)) + { + return true; + } + } + connectionProvider = null; + return false; + } + + private static List LoadAssemblyAttributes() + { + var attributes = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase)) + .SelectMany(ProviderAssemblyAttributeBase.Get) + .ToList(); + + if (attributes.Count == 0) + { + foreach (var file in Directory.EnumerateFiles(Composer.GetSimpleDataAssemblyPath(), "Simple.Data.*.dll")) + { + Assembly assembly; + if (Composer.TryLoadAssembly(file, out assembly)) + { + if (ProviderAssemblyAttributeBase.Get(assembly).Any()) + { + assembly = Assembly.LoadFrom(file); + attributes.AddRange(ProviderAssemblyAttributeBase.Get(assembly)); + } + } + } + } + return attributes; + } + private class ConnectionToken : IEquatable { /// @@ -194,7 +277,7 @@ public override int GetHashCode() { unchecked { - return (ConnectionString.GetHashCode()*397) ^ ProviderName.GetHashCode(); + return (ConnectionString.GetHashCode()*397) ^ (ProviderName.GetHashCode() * 397) ^ SchemaName.GetHashCode(); } } @@ -210,12 +293,14 @@ public override int GetHashCode() private readonly string _connectionString; private readonly string _providerName; + private readonly string _schemaName; - public ConnectionToken(string connectionString, string providerName) + public ConnectionToken(string connectionString, string providerName, string schemaName = null) { if (connectionString == null) throw new ArgumentNullException("connectionString"); _connectionString = connectionString; _providerName = providerName ?? string.Empty; + _schemaName = schemaName ?? string.Empty; } public string ConnectionString @@ -227,6 +312,11 @@ public string ProviderName { get { return _providerName; } } + + public string SchemaName + { + get { return _schemaName; } + } } } } diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index 88977b1f..1fac828a 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -1,44 +1,30 @@ -using System; -using System.Data; -using System.Linq; -using System.Collections.Generic; -using Simple.Data.Ado.Schema; +using System.Collections.Generic; namespace Simple.Data.Ado { - using Extensions; - - public class QueryBuilder + public class QueryBuilder : QueryBuilderBase { - private readonly SimpleReferenceFormatter _simpleReferenceFormatter; - private readonly AdoAdapter _adoAdapter; - private readonly int _bulkIndex; - private readonly DatabaseSchema _schema; - private ObjectName _tableName; - private Table _table; - private SimpleQuery _query; - private SimpleExpression _whereCriteria; - private SimpleExpression _havingCriteria; - private IList _columns; - private readonly CommandBuilder _commandBuilder; private List _unhandledClauses; - public QueryBuilder(AdoAdapter adoAdapter) : this(adoAdapter, -1) + public QueryBuilder(AdoAdapter adoAdapter) + : base(adoAdapter) { } - public QueryBuilder(AdoAdapter adoAdapter, int bulkIndex) + public QueryBuilder(AdoAdapter adoAdapter, int bulkIndex) : base(adoAdapter, bulkIndex) { - _adoAdapter = adoAdapter; - _bulkIndex = bulkIndex; - _schema = _adoAdapter.GetSchema(); - _commandBuilder = new CommandBuilder(_schema, _bulkIndex); - _simpleReferenceFormatter = new SimpleReferenceFormatter(_schema, _commandBuilder); } - public ICommandBuilder Build(SimpleQuery query, out IEnumerable unhandledClauses) + public override ICommandBuilder Build(SimpleQuery query, out IEnumerable unhandledClauses) { + + var customBuilder = _adoAdapter.ProviderHelper.GetCustomProvider(_adoAdapter.ConnectionProvider); + if (customBuilder != null) + { + return customBuilder.Build(_adoAdapter, _bulkIndex, query, out unhandledClauses); + } + _unhandledClauses = new List(); SetQueryContext(query); @@ -52,298 +38,5 @@ public ICommandBuilder Build(SimpleQuery query, out IEnumerable().SingleOrDefault(); - if (selectClause != null) - { - if (selectClause.Columns.OfType().Any()) - { - _columns = ExpandAllColumnsReferences(selectClause.Columns).ToArray(); - } - else - { - _columns = selectClause.Columns.ToArray(); - } - } - else - { - _columns = _table.Columns.Select(c => ObjectReference.FromStrings(_table.Schema, _table.ActualName, c.ActualName)).ToArray(); - } - - HandleWithClauses(); - - _whereCriteria = _query.Clauses.OfType().Aggregate(SimpleExpression.Empty, - (seed, where) => seed && where.Criteria); - _havingCriteria = _query.Clauses.OfType().Aggregate(SimpleExpression.Empty, - (seed, having) => seed && having.Criteria); - - _commandBuilder.SetText(GetSelectClause(_tableName)); - } - - private IEnumerable ExpandAllColumnsReferences(IEnumerable columns) - { - foreach (var column in columns) - { - var allColumns = column as AllColumnsSpecialReference; - if (ReferenceEquals(allColumns, null)) yield return column; - else - { - foreach (var allColumn in _schema.FindTable(allColumns.Table.GetName()).Columns) - { - yield return new ObjectReference(allColumn.ActualName, allColumns.Table); - } - } - } - } - - private void HandleWithClauses() - { - var withClauses = _query.Clauses.OfType().ToList(); - var relationTypeDict = new Dictionary(); - if (withClauses.Count > 0) - { - foreach (var withClause in withClauses) - { - if (withClause.ObjectReference.GetOwner().IsNull()) - { - HandleWithClauseUsingAssociatedJoinClause(relationTypeDict, withClause); - } - else - { - HandleWithClauseUsingNaturalJoin(withClause, relationTypeDict); - } - } - _columns = - _columns.OfType() - .Select(c => IsCoreTable(c.GetOwner()) ? c : AddWithAlias(c, relationTypeDict[c.GetOwner()])) - .ToArray(); - } - } - - private void HandleWithClauseUsingAssociatedJoinClause(Dictionary relationTypeDict, WithClause withClause) - { - var joinClause = - _query.Clauses.OfType().FirstOrDefault( - j => j.Table.GetAliasOrName() == withClause.ObjectReference.GetAliasOrName()); - if (joinClause != null) - { - _columns = - _columns.Concat( - _schema.FindTable(joinClause.Table.GetName()).Columns.Select( - c => new ObjectReference(c.ActualName, joinClause.Table))) - .ToArray(); - relationTypeDict[joinClause.Table] = WithTypeToRelationType(withClause.Type, RelationType.OneToMany); - } - } - - private void HandleWithClauseUsingNaturalJoin(WithClause withClause, Dictionary relationTypeDict) - { - relationTypeDict[withClause.ObjectReference] = WithTypeToRelationType(withClause.Type, RelationType.None); - _columns = - _columns.Concat( - _schema.FindTable(withClause.ObjectReference.GetName()).Columns.Select( - c => new ObjectReference(c.ActualName, withClause.ObjectReference))) - .ToArray(); - } - - private static RelationType WithTypeToRelationType(WithType withType, RelationType defaultRelationType) - { - switch (withType) - { - case WithType.One: - return RelationType.ManyToOne; - case WithType.Many: - return RelationType.OneToMany; - default: - return defaultRelationType; - } - } - - private bool IsCoreTable(ObjectReference tableReference) - { - if (ReferenceEquals(tableReference, null)) throw new ArgumentNullException("tableReference"); - if (!string.IsNullOrWhiteSpace(tableReference.GetAlias())) return false; - return _schema.FindTable(tableReference.GetName()) == _table; - } - - private ObjectReference AddWithAlias(ObjectReference c, RelationType relationType = RelationType.None) - { - if (relationType == RelationType.None) - relationType = _schema.GetRelationType(c.GetOwner().GetOwner().GetName(), c.GetOwner().GetName()); - if (relationType == RelationType.None) throw new InvalidOperationException("No Join found"); - return c.As(string.Format("__with{0}__{1}__{2}", - relationType == RelationType.OneToMany - ? "n" - : "1", c.GetOwner().GetAliasOrName(), c.GetName())); - } - - private void HandleJoins() - { - if (_whereCriteria == SimpleExpression.Empty && _havingCriteria == SimpleExpression.Empty - && (!_query.Clauses.OfType().Any()) - && (_columns.All(r => (r is CountSpecialReference)))) return; - - var joiner = new Joiner(JoinType.Inner, _schema); - - string dottedTables = RemoveSchemaFromQueryTableName(); - - var fromTable = dottedTables.Contains('.') - ? joiner.GetJoinClauses(_tableName, dottedTables.Split('.').Reverse()) - : Enumerable.Empty(); - - var joinClauses = _query.Clauses.OfType().ToArray(); - var fromJoins = joiner.GetJoinClauses(joinClauses, _commandBuilder); - - var fromCriteria = joiner.GetJoinClauses(_tableName, _whereCriteria); - - var fromHavingCriteria = joiner.GetJoinClauses(_tableName, _havingCriteria); - - var fromColumnList = _columns.Any(r => !(r is SpecialReference)) - ? joiner.GetJoinClauses(_tableName, GetObjectReferences(_columns).Where(o => !joinClauses.Any(j => o.GetOwner().Equals(j.Table))), JoinType.Outer) - : Enumerable.Empty(); - - var joins = string.Join(" ", fromTable.Concat(fromJoins) - .Concat(fromCriteria) - .Concat(fromHavingCriteria) - .Concat(fromColumnList) - .Distinct()); - - if (!string.IsNullOrWhiteSpace(joins)) - { - _commandBuilder.Append(" " + joins); - } - } - - private IEnumerable GetObjectReferences(IEnumerable source) - { - var list = source.ToList(); - foreach (var objectReference in list.OfType()) - { - yield return objectReference; - } - - foreach (var objectReference in list.OfType().Select(fr => fr.Argument).OfType()) - { - yield return objectReference; - } - } - - private string RemoveSchemaFromQueryTableName() - { - return _query.TableName.StartsWith(_table.Schema + '.', StringComparison.InvariantCultureIgnoreCase) - ? _query.TableName.Substring(_query.TableName.IndexOf('.') + 1) - : _query.TableName; - } - - private void HandleQueryCriteria() - { - if (_whereCriteria == SimpleExpression.Empty) return; - _commandBuilder.Append(" WHERE " + new ExpressionFormatter(_commandBuilder, _schema).Format(_whereCriteria)); - } - - private void HandleHavingCriteria() - { - if (_havingCriteria == SimpleExpression.Empty) return; - _commandBuilder.Append(" HAVING " + new ExpressionFormatter(_commandBuilder, _schema).Format(_havingCriteria)); - } - - private void HandleGrouping() - { - if (_havingCriteria == SimpleExpression.Empty && !_columns.OfType().Any(fr => fr.IsAggregate)) return; - - var groupColumns = - GetColumnsToSelect(_table).Where(c => (!(c is FunctionReference)) || !((FunctionReference) c).IsAggregate).ToList(); - - if (groupColumns.Count == 0) return; - - _commandBuilder.Append(" GROUP BY " + string.Join(",", groupColumns.Select(_simpleReferenceFormatter.FormatColumnClauseWithoutAlias))); - } - - private void HandleOrderBy() - { - if (!_query.Clauses.OfType().Any()) return; - - var orderNames = _query.Clauses.OfType().Select(ToOrderByDirective); - _commandBuilder.Append(" ORDER BY " + string.Join(", ", orderNames)); - } - - private string ToOrderByDirective(OrderByClause item) - { - string name; - if (_columns.Any(r => (!string.IsNullOrWhiteSpace(r.GetAlias())) && r.GetAlias().Equals(item.Reference.GetName()))) - { - name = item.Reference.GetName(); - } - else - { - name = _table.FindColumn(item.Reference.GetName()).QualifiedName; - } - - var direction = item.Direction == OrderByDirection.Descending ? " DESC" : string.Empty; - return name + direction; - } - - private string GetSelectClause(ObjectName tableName) - { - var table = _schema.FindTable(tableName); - string template = _query.Clauses.OfType().Any() - ? "select distinct {0} from {1}" - : "select {0} from {1}"; - return string.Format(template, - GetColumnsClause(table), - table.QualifiedName); - } - - private string GetColumnsClause(Table table) - { - if (_columns != null && _columns.Count == 1 && _columns[0] is SpecialReference) - { - return FormatSpecialReference((SpecialReference) _columns[0]); - } - - return string.Join(",", GetColumnsToSelect(table).Select(_simpleReferenceFormatter.FormatColumnClause)); - } - - private static string FormatSpecialReference(SpecialReference reference) - { - if (reference.GetType() == typeof(CountSpecialReference)) return "COUNT(*)"; - if (reference.GetType() == typeof(ExistsSpecialReference)) return "DISTINCT 1"; - throw new InvalidOperationException("SpecialReference type not recognised."); - } - - private IEnumerable GetColumnsToSelect(Table table) - { - if (_columns != null && _columns.Count > 0) - { - return _columns; - } - else - { - return table.Columns.Select(c => ObjectReference.FromStrings(table.Schema, table.ActualName, c.ActualName)); - } - } - - private string FormatGroupByColumnClause(SimpleReference reference) - { - var objectReference = reference as ObjectReference; - if (!ReferenceEquals(objectReference, null)) - { - var table = _schema.FindTable(objectReference.GetOwner().GetName()); - var column = table.FindColumn(objectReference.GetName()); - return string.Format("{0}.{1}", table.QualifiedName, column.QuotedName); - } - - var functionReference = reference as FunctionReference; - if (!ReferenceEquals(functionReference, null)) - { - return FormatGroupByColumnClause(functionReference.Argument); - } - - throw new InvalidOperationException("SimpleReference type not supported."); - } } } \ No newline at end of file diff --git a/Simple.Data.Ado/QueryBuilderBase.cs b/Simple.Data.Ado/QueryBuilderBase.cs new file mode 100644 index 00000000..66309607 --- /dev/null +++ b/Simple.Data.Ado/QueryBuilderBase.cs @@ -0,0 +1,384 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Simple.Data.Ado.Schema; + +namespace Simple.Data.Ado +{ + public abstract class QueryBuilderBase + { + protected readonly SimpleReferenceFormatter _simpleReferenceFormatter; + protected readonly AdoAdapter _adoAdapter; + protected readonly int _bulkIndex; + protected readonly DatabaseSchema _schema; + + protected ObjectName _tableName; + protected Table _table; + protected SimpleQuery _query; + protected SimpleExpression _whereCriteria; + protected SimpleExpression _havingCriteria; + protected IList _columns; + protected CommandBuilder _commandBuilder; + + protected QueryBuilderBase(AdoAdapter adapter) : this(adapter, -1) + { + } + + protected QueryBuilderBase(AdoAdapter adapter, int bulkIndex) : this(adapter, bulkIndex, null) + { + + } + + protected QueryBuilderBase(AdoAdapter adapter, int bulkIndex, IFunctionNameConverter functionNameConverter) + { + _adoAdapter = adapter; + _bulkIndex = bulkIndex; + _schema = _adoAdapter.GetSchema(); + _commandBuilder = new CommandBuilder(_schema, _bulkIndex); + _simpleReferenceFormatter = new SimpleReferenceFormatter(_schema, _commandBuilder, functionNameConverter); + } + + public abstract ICommandBuilder Build(SimpleQuery query, out IEnumerable unhandledClauses); + + protected virtual void SetQueryContext(SimpleQuery query) + { + _query = query; + _tableName = _schema.BuildObjectName(query.TableName); + _table = _schema.FindTable(_tableName); + var selectClause = _query.Clauses.OfType().SingleOrDefault(); + if (selectClause != null) + { + if (selectClause.Columns.OfType().Any()) + { + _columns = ExpandAllColumnsReferences(selectClause.Columns).ToArray(); + } + else + { + _columns = selectClause.Columns.ToArray(); + } + } + else + { + _columns = _table.Columns.Select(c => ObjectReference.FromStrings(_table.Schema, _table.ActualName, c.ActualName)).ToArray(); + } + + HandleWithClauses(); + + _whereCriteria = _query.Clauses.OfType().Aggregate(SimpleExpression.Empty, + (seed, where) => seed && where.Criteria); + _havingCriteria = _query.Clauses.OfType().Aggregate(SimpleExpression.Empty, + (seed, having) => seed && having.Criteria); + + _commandBuilder.SetText(GetSelectClause(_tableName)); + } + + protected IEnumerable ExpandAllColumnsReferences(IEnumerable columns) + { + foreach (var column in columns) + { + var allColumns = column as AllColumnsSpecialReference; + if (ReferenceEquals(allColumns, null)) yield return column; + else + { + foreach (var allColumn in _schema.FindTable(allColumns.Table.GetName()).Columns) + { + yield return new ObjectReference(allColumn.ActualName, allColumns.Table); + } + } + } + } + + protected virtual void HandleWithClauses() + { + var withClauses = _query.Clauses.OfType().ToList(); + var relationTypeDict = new Dictionary(); + if (withClauses.Count > 0) + { + foreach (var withClause in withClauses) + { + if (withClause.ObjectReference.GetOwner().IsNull()) + { + HandleWithClauseUsingAssociatedJoinClause(relationTypeDict, withClause); + } + else + { + if (withClause.Type == WithType.NotSpecified) + { + InferWithType(withClause); + } + HandleWithClauseUsingNaturalJoin(withClause, relationTypeDict); + } + } + _columns = + _columns.OfType() + .Select(c => IsCoreTable(c.GetOwner()) ? c : AddWithAlias(c, relationTypeDict[c.GetOwner()])) + .ToArray(); + } + } + + protected void InferWithType(WithClause withClause) + { + var objectReference = withClause.ObjectReference; + while (!ReferenceEquals(objectReference.GetOwner(), null)) + { + var toTable = _schema.FindTable(objectReference.GetName()); + var fromTable = _schema.FindTable(objectReference.GetOwner().GetName()); + if (_schema.GetRelationType(fromTable.ActualName, toTable.ActualName) == RelationType.OneToMany) + { + withClause.Type = WithType.Many; + return; + } + objectReference = objectReference.GetOwner(); + } + } + + protected void HandleWithClauseUsingAssociatedJoinClause(Dictionary relationTypeDict, WithClause withClause) + { + var joinClause = + _query.Clauses.OfType().FirstOrDefault( + j => j.Table.GetAliasOrName() == withClause.ObjectReference.GetAliasOrName()); + if (joinClause != null) + { + _columns = + _columns.Concat( + _schema.FindTable(joinClause.Table.GetName()).Columns.Select( + c => new ObjectReference(c.ActualName, joinClause.Table))) + .ToArray(); + relationTypeDict[joinClause.Table] = WithTypeToRelationType(withClause.Type, RelationType.OneToMany); + } + } + + protected void HandleWithClauseUsingNaturalJoin(WithClause withClause, Dictionary relationTypeDict) + { + relationTypeDict[withClause.ObjectReference] = WithTypeToRelationType(withClause.Type, RelationType.None); + _columns = + _columns.Concat( + _schema.FindTable(withClause.ObjectReference.GetName()).Columns.Select( + c => new ObjectReference(c.ActualName, withClause.ObjectReference))) + .ToArray(); + } + + protected static RelationType WithTypeToRelationType(WithType withType, RelationType defaultRelationType) + { + switch (withType) + { + case WithType.One: + return RelationType.ManyToOne; + case WithType.Many: + return RelationType.OneToMany; + default: + return defaultRelationType; + } + } + + protected bool IsCoreTable(ObjectReference tableReference) + { + if (ReferenceEquals(tableReference, null)) throw new ArgumentNullException("tableReference"); + if (!string.IsNullOrWhiteSpace(tableReference.GetAlias())) return false; + return _schema.FindTable(tableReference.GetName()) == _table; + } + + protected ObjectReference AddWithAlias(ObjectReference c, RelationType relationType = RelationType.None) + { + if (relationType == RelationType.None) + relationType = _schema.GetRelationType(c.GetOwner().GetOwner().GetName(), c.GetOwner().GetName()); + if (relationType == RelationType.None) throw new InvalidOperationException("No Join found"); + return c.As(string.Format("__with{0}__{1}__{2}", + relationType == RelationType.OneToMany + ? "n" + : "1", c.GetOwner().GetAliasOrName(), c.GetName())); + } + + protected virtual void HandleJoins() + { + if (_whereCriteria == SimpleExpression.Empty && _havingCriteria == SimpleExpression.Empty + && (!_query.Clauses.OfType().Any()) + && (_columns.All(r => (r is CountSpecialReference)))) return; + + var joiner = new Joiner(JoinType.Inner, _schema); + + string dottedTables = RemoveSchemaFromQueryTableName(); + + var fromTable = dottedTables.Contains('.') + ? joiner.GetJoinClauses(_tableName, dottedTables.Split('.').Reverse()) + : Enumerable.Empty(); + + var joinClauses = _query.Clauses.OfType().ToArray(); + var fromJoins = joiner.GetJoinClauses(joinClauses, _commandBuilder); + + var fromCriteria = joiner.GetJoinClauses(_tableName, _whereCriteria); + + var fromHavingCriteria = joiner.GetJoinClauses(_tableName, _havingCriteria); + + var fromColumnList = _columns.Any(r => !(r is SpecialReference)) + ? GetJoinClausesFromColumnList(joinClauses, joiner) + : Enumerable.Empty(); + + var joinList = fromTable.Concat(fromJoins).Concat(fromCriteria).Concat(fromHavingCriteria).Concat(fromColumnList).Select(s => s.Trim()).Distinct().ToList(); + + var leftJoinList = joinList.Where(s => s.StartsWith("LEFT ", StringComparison.OrdinalIgnoreCase)).ToList(); + + foreach (var leftJoin in leftJoinList) + { + if (joinList.Any(s => s.Equals(leftJoin.Substring(5), StringComparison.OrdinalIgnoreCase))) + { + joinList.Remove(leftJoin); + } + } + + var joins = string.Join(" ", joinList); + + if (!string.IsNullOrWhiteSpace(joins)) + { + _commandBuilder.Append(" " + joins); + } + } + + protected IEnumerable + GetJoinClausesFromColumnList(IEnumerable joinClauses, Joiner joiner) + { + return joiner.GetJoinClauses(_tableName, GetObjectReferences(_columns) + .Where(o => !joinClauses.Any(j => ObjectReferenceIsInJoinClause(j, o))), JoinType.Outer); + + } + + protected static bool ObjectReferenceIsInJoinClause(JoinClause clause, ObjectReference reference) + { + return reference.GetOwner().GetAliasOrName().Equals(clause.Table.GetAliasOrName()); + } + + protected IEnumerable GetObjectReferences(IEnumerable source) + { + var list = source.ToList(); + foreach (var objectReference in list.OfType()) + { + yield return objectReference; + } + + foreach (var objectReference in list.OfType().Select(fr => fr.Argument).OfType()) + { + yield return objectReference; + } + } + + protected string RemoveSchemaFromQueryTableName() + { + return _query.TableName.StartsWith(_table.Schema + '.', StringComparison.InvariantCultureIgnoreCase) + ? _query.TableName.Substring(_query.TableName.IndexOf('.') + 1) + : _query.TableName; + } + + protected virtual void HandleQueryCriteria() + { + if (_whereCriteria == SimpleExpression.Empty) return; + _commandBuilder.Append(" WHERE " + new ExpressionFormatter(_commandBuilder, _schema).Format(_whereCriteria)); + } + + protected virtual void HandleHavingCriteria() + { + if (_havingCriteria == SimpleExpression.Empty) return; + _commandBuilder.Append(" HAVING " + new ExpressionFormatter(_commandBuilder, _schema).Format(_havingCriteria)); + } + + protected virtual void HandleGrouping() + { + if (_havingCriteria == SimpleExpression.Empty && !_columns.OfType().Any(f => f.IsAggregate)) return; + + var groupColumns = + GetColumnsToSelect(_table).Where(c => (!(c is FunctionReference)) || !((FunctionReference)c).IsAggregate).ToList(); + + if (groupColumns.Count == 0) return; + + _commandBuilder.Append(" GROUP BY " + string.Join(",", groupColumns.Select(_simpleReferenceFormatter.FormatColumnClauseWithoutAlias))); + } + + protected virtual void HandleOrderBy() + { + if (!_query.Clauses.OfType().Any()) return; + + var orderNames = _query.Clauses.OfType().Select(ToOrderByDirective); + _commandBuilder.Append(" ORDER BY " + string.Join(", ", orderNames)); + } + + protected string ToOrderByDirective(OrderByClause item) + { + string name; + if (!string.IsNullOrWhiteSpace(item.Reference.GetOwner().GetAlias())) + { + name = string.Format("{0}.{1}", _schema.QuoteObjectName(item.Reference.GetOwner().GetAlias()), + _schema.QuoteObjectName(item.Reference.GetName())); + } + else if (_columns.Any(r => (!string.IsNullOrWhiteSpace(r.GetAlias())) && r.GetAlias().Equals(item.Reference.GetName()))) + { + name = item.Reference.GetName(); + } + else + { + var table = _schema.FindTable(item.Reference.GetOwner().GetName()); + name = table.FindColumn(item.Reference.GetName()).QualifiedName; + } + + var direction = item.Direction == OrderByDirection.Descending ? " DESC" : string.Empty; + return name + direction; + } + + protected virtual string GetSelectClause(ObjectName tableName) + { + var table = _schema.FindTable(tableName); + string template = _query.Clauses.OfType().Any() + ? "select distinct {0} from {1}" + : "select {0} from {1}"; + return string.Format(template, + GetColumnsClause(table), + table.QualifiedName); + } + + protected virtual string GetColumnsClause(Table table) + { + if (_columns != null && _columns.Count == 1 && _columns[0] is SpecialReference) + { + return FormatSpecialReference((SpecialReference)_columns[0]); + } + + return string.Join(",", GetColumnsToSelect(table).Select(_simpleReferenceFormatter.FormatColumnClause)); + } + + protected static string FormatSpecialReference(SpecialReference reference) + { + if (reference.GetType() == typeof(CountSpecialReference)) return "COUNT(*)"; + if (reference.GetType() == typeof(ExistsSpecialReference)) return "DISTINCT 1"; + throw new InvalidOperationException("SpecialReference type not recognised."); + } + + protected IEnumerable GetColumnsToSelect(Table table) + { + if (_columns != null && _columns.Count > 0) + { + return _columns; + } + else + { + return table.Columns.Select(c => ObjectReference.FromStrings(table.Schema, table.ActualName, c.ActualName)); + } + } + + protected string FormatGroupByColumnClause(SimpleReference reference) + { + var objectReference = reference as ObjectReference; + if (!ReferenceEquals(objectReference, null)) + { + var table = _schema.FindTable(objectReference.GetOwner().GetName()); + var column = table.FindColumn(objectReference.GetName()); + return string.Format("{0}.{1}", table.QualifiedName, column.QuotedName); + } + + var functionReference = reference as FunctionReference; + if (!ReferenceEquals(functionReference, null)) + { + return FormatGroupByColumnClause(functionReference.Argument); + } + + throw new InvalidOperationException("SimpleReference type not supported."); + } + } +} diff --git a/Simple.Data.Ado/Schema/Column.cs b/Simple.Data.Ado/Schema/Column.cs index 5e6b6c3b..6bcdf23d 100644 --- a/Simple.Data.Ado/Schema/Column.cs +++ b/Simple.Data.Ado/Schema/Column.cs @@ -69,6 +69,11 @@ public bool IsIdentity get { return _isIdentity; } } + public virtual bool IsWriteable + { + get { return !IsIdentity; } + } + public virtual bool IsBinary { get { return _dbType == DbType.Binary; } diff --git a/Simple.Data.Ado/Schema/ColumnCollection.cs b/Simple.Data.Ado/Schema/ColumnCollection.cs index d8b48ab5..9b0985d1 100644 --- a/Simple.Data.Ado/Schema/ColumnCollection.cs +++ b/Simple.Data.Ado/Schema/ColumnCollection.cs @@ -26,7 +26,7 @@ public ColumnCollection(IEnumerable columns) : base(columns.ToList()) public Column Find(string columnName) { var column = FindColumnWithName(columnName); - if (column == null) throw new UnresolvableObjectException(columnName); + if (column == null) throw new UnresolvableObjectException(columnName, string.Format("Column '{0}' not found.", columnName)); return column; } diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 63ecab56..2a1e6265 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -14,11 +14,14 @@ public class DatabaseSchema private readonly ISchemaProvider _schemaProvider; private readonly Lazy _lazyTables; private readonly Lazy _lazyProcedures; + private readonly Lazy _operators; + private string _defaultSchema; private DatabaseSchema(ISchemaProvider schemaProvider, ProviderHelper providerHelper) { _lazyTables = new Lazy(CreateTableCollection); _lazyProcedures = new Lazy(CreateProcedureCollection); + _operators = new Lazy(CreateOperators); _schemaProvider = schemaProvider; _providerHelper = providerHelper; } @@ -43,8 +46,24 @@ public IEnumerable
Tables get { return _lazyTables.Value.AsEnumerable(); } } + public bool IsTable(string name) + { + try + { + var table = FindTable(name); + return table != null; + } + catch (UnresolvableObjectException) + { + return false; + } + } public Table FindTable(string tableName) { + if (!string.IsNullOrWhiteSpace(DefaultSchema) && !(tableName.Contains("."))) + { + tableName = DefaultSchema + "." + tableName; + } return _lazyTables.Value.Find(tableName); } @@ -55,19 +74,27 @@ public Table FindTable(ObjectName tableName) public Procedure FindProcedure(string procedureName) { + if (!string.IsNullOrWhiteSpace(DefaultSchema) && !(procedureName.Contains("."))) + { + procedureName = DefaultSchema + "." + procedureName; + } return _lazyProcedures.Value.Find(procedureName); } public Procedure FindProcedure(ObjectName procedureName) { + if (string.IsNullOrWhiteSpace(procedureName.Schema) && !string.IsNullOrWhiteSpace(DefaultSchema)) + { + procedureName = new ObjectName(DefaultSchema, procedureName.Name); + } return _lazyProcedures.Value.Find(procedureName); } - private String GetDefaultSchema() + private string DefaultSchema { - return _schemaProvider.GetDefaultSchema(); + get { return _defaultSchema ?? (_defaultSchema = _schemaProvider.GetDefaultSchema() ?? string.Empty); } } - + private TableCollection CreateTableCollection() { return new TableCollection(_schemaProvider.GetTables() @@ -80,7 +107,7 @@ private ProcedureCollection CreateProcedureCollection() .Select( proc => new Procedure(proc.Name, proc.SpecificName, proc.Schema, - this))); + this)), _schemaProvider.GetDefaultSchema()); } public string QuoteObjectName(string unquotedName) @@ -98,8 +125,11 @@ public string QuoteObjectName(ObjectName unquotedName) public static DatabaseSchema Get(IConnectionProvider connectionProvider, ProviderHelper providerHelper) { - return Instances.GetOrAdd(connectionProvider.ConnectionString, - sp => new DatabaseSchema(connectionProvider.GetSchemaProvider(), providerHelper)); + var instance = connectionProvider is ISchemaConnectionProvider + ? Instances.GetOrAdd(((ISchemaConnectionProvider)connectionProvider).ConnectionProviderKey, sp => new DatabaseSchema(connectionProvider.GetSchemaProvider(), providerHelper)) + : Instances.GetOrAdd(connectionProvider.ConnectionString, sp => new DatabaseSchema(connectionProvider.GetSchemaProvider(), providerHelper)); + + return instance; } public static void ClearCache() @@ -110,9 +140,9 @@ public static void ClearCache() public ObjectName BuildObjectName(String text) { if (text == null) throw new ArgumentNullException("text"); - if (!text.Contains('.')) return new ObjectName(this.GetDefaultSchema(), text); + if (!text.Contains('.')) return new ObjectName(this.DefaultSchema, text); var schemaDotTable = text.Split('.'); - if (schemaDotTable.Length != 2) throw new InvalidOperationException("Could not parse table name."); + if (schemaDotTable.Length != 2) throw new InvalidOperationException(string.Format("Could not parse table name '{0}'.", text)); return new ObjectName(schemaDotTable[0], schemaDotTable[1]); } @@ -124,6 +154,21 @@ public RelationType GetRelationType(string fromTableName, string toTableName) if (fromTable.GetDetail(toTableName) != null) return RelationType.OneToMany; return RelationType.None; } + + public bool IsProcedure(string procedureName) + { + return _lazyProcedures.Value.IsProcedure(procedureName); + } + + public Operators Operators + { + get { return _operators.Value; } + } + + private Operators CreateOperators() + { + return ProviderHelper.GetCustomProvider(_schemaProvider) ?? new Operators(); + } } public enum RelationType diff --git a/Simple.Data.Ado/Schema/ForeignKey.cs b/Simple.Data.Ado/Schema/ForeignKey.cs index 7ffb4fce..661fef1b 100644 --- a/Simple.Data.Ado/Schema/ForeignKey.cs +++ b/Simple.Data.Ado/Schema/ForeignKey.cs @@ -7,16 +7,27 @@ public sealed class ForeignKey private readonly Key _columns; private readonly ObjectName _detailTable; private readonly ObjectName _masterTable; + private readonly string _name; private readonly Key _masterColumns; - public ForeignKey(ObjectName detailTable, IEnumerable columns, ObjectName masterTable, IEnumerable masterColumns) + public ForeignKey(ObjectName detailTable, IEnumerable columns, ObjectName masterTable, IEnumerable masterColumns) : this(detailTable, columns, masterTable, masterColumns, null) + { + } + + public ForeignKey(ObjectName detailTable, IEnumerable columns, ObjectName masterTable, IEnumerable masterColumns, string name) { _columns = new Key(columns); _detailTable = detailTable; _masterTable = masterTable; + _name = name; _masterColumns = new Key(masterColumns); } + public string Name + { + get { return _name; } + } + public ObjectName DetailTable { get { return _detailTable; } diff --git a/Simple.Data.Ado/Schema/ForeignKeyCollection.cs b/Simple.Data.Ado/Schema/ForeignKeyCollection.cs index c5bc14ed..1e576386 100644 --- a/Simple.Data.Ado/Schema/ForeignKeyCollection.cs +++ b/Simple.Data.Ado/Schema/ForeignKeyCollection.cs @@ -2,18 +2,7 @@ namespace Simple.Data.Ado.Schema { - public class ForeignKeyCollection : KeyedCollection + public class ForeignKeyCollection : Collection { - /// - /// When implemented in a derived class, extracts the key from the specified element. - /// - /// - /// The key for the specified element. - /// - /// The element from which to extract the key. - protected override ObjectName GetKeyForItem(ForeignKey item) - { - return item.MasterTable; - } } } diff --git a/Simple.Data.Ado/Schema/ProcedureCollection.cs b/Simple.Data.Ado/Schema/ProcedureCollection.cs index 200a8bd7..7608108e 100644 --- a/Simple.Data.Ado/Schema/ProcedureCollection.cs +++ b/Simple.Data.Ado/Schema/ProcedureCollection.cs @@ -9,14 +9,16 @@ namespace Simple.Data.Ado.Schema { class ProcedureCollection : Collection { + private readonly string _defaultSchema; + public ProcedureCollection() { } - public ProcedureCollection(IEnumerable procedures) : base(procedures.ToList()) + public ProcedureCollection(IEnumerable procedures, string defaultSchema) : base(procedures.ToList()) { - + _defaultSchema = defaultSchema; } /// @@ -26,23 +28,44 @@ public ProcedureCollection(IEnumerable procedures) : base(procedures. /// Name of the procedure. /// A if a match is found; otherwise, null. public Procedure Find(string procedureName) + { + var procedure = FindImpl(procedureName); + + if (procedure == null) + { + throw new UnresolvableObjectException(procedureName, string.Format("Procedure '{0}' not found, or insufficient permissions.", procedureName)); + } + + return procedure; + } + + public bool IsProcedure(string procedureName) + { + try + { + return FindImpl(procedureName) != null; + } + catch (UnresolvableObjectException) + { + return false; + } + } + + private Procedure FindImpl(string procedureName) { if (procedureName.Contains('.')) { var schemaDotprocedure = procedureName.Split('.'); - if (schemaDotprocedure.Length != 2) throw new InvalidOperationException("Could not resolve qualified procedure name."); + if (schemaDotprocedure.Length != 2) throw new InvalidOperationException(string.Format("Could not resolve qualified procedure name '{0}'.", procedureName)); return Find(schemaDotprocedure[1], schemaDotprocedure[0]); } - var procedure = FindprocedureWithName(procedureName.Homogenize()) - ?? FindprocedureWithPluralName(procedureName.Homogenize()) - ?? FindprocedureWithSingularName(procedureName.Homogenize()); - - if (procedure == null) + if (!string.IsNullOrWhiteSpace(_defaultSchema)) { - throw new UnresolvableObjectException(procedureName, "No matching procedure found, or insufficient permissions."); + return Find(procedureName, _defaultSchema); } - - return procedure; + return FindprocedureWithName(procedureName.Homogenize()) + ?? FindprocedureWithPluralName(procedureName.Homogenize()) + ?? FindprocedureWithSingularName(procedureName.Homogenize()); } /// @@ -60,7 +83,8 @@ public Procedure Find(string procedureName, string schemaName) if (procedure == null) { - throw new UnresolvableObjectException(schemaName + '.' + procedureName, "No matching procedure found, or insufficient permissions."); + string fullProcedureName = schemaName + '.' + procedureName; + throw new UnresolvableObjectException(fullProcedureName, string.Format("Procedure '{0}' not found, or insufficient permissions.", fullProcedureName)); } return procedure; @@ -78,16 +102,12 @@ private Procedure FindprocedureWithPluralName(string procedureName, string schem private Procedure FindprocedureWithName(string procedureName, string schemaName) { - return this - .Where(sp => sp.HomogenizedName.Equals(procedureName) && (sp.Schema == null || sp.Schema.Homogenize().Equals(schemaName))) - .SingleOrDefault(); + return this.SingleOrDefault(sp => sp.HomogenizedName.Equals(procedureName) && (sp.Schema == null || sp.Schema.Homogenize().Equals(schemaName))); } private Procedure FindprocedureWithName(string procedureName) { - return this - .Where(t => t.HomogenizedName.Equals(procedureName)) - .SingleOrDefault(); + return this.SingleOrDefault(t => t.HomogenizedName.Equals(procedureName)); } private Procedure FindprocedureWithPluralName(string procedureName) diff --git a/Simple.Data.Ado/Schema/Table.cs b/Simple.Data.Ado/Schema/Table.cs index 64057ce7..a659dac3 100644 --- a/Simple.Data.Ado/Schema/Table.cs +++ b/Simple.Data.Ado/Schema/Table.cs @@ -88,10 +88,23 @@ public Column FindColumn(string columnName) } catch (UnresolvableObjectException ex) { - throw new UnresolvableObjectException(_actualName + "." + ex.ObjectName, "Column not found", ex); + string fullColumnName = _actualName + "." + ex.ObjectName; + throw new UnresolvableObjectException(fullColumnName, string.Format("Column '{0}' not found.", fullColumnName), ex); } } + public bool TryFindColumn(string columnName, out Column column) + { + var columns = _lazyColumns.Value; + if (columns.Contains(columnName)) + { + column = columns.Find(columnName); + return true; + } + column = null; + return false; + } + public bool HasColumn(string columnName) { return _lazyColumns.Value.Contains(columnName); @@ -145,7 +158,7 @@ internal TableJoin GetMaster(string name) var table = _databaseSchema.FindTable(name); var foreignKey = - this.ForeignKeys.SingleOrDefault(fk => fk.MasterTable.Schema == table.Schema && fk.MasterTable.Name == table.ActualName); + this.ForeignKeys.FirstOrDefault(fk => fk.MasterTable.Schema == table.Schema && fk.MasterTable.Name == table.ActualName); if (foreignKey == null) return null; @@ -164,7 +177,7 @@ internal TableJoin GetDetail(string name) { var table = DatabaseSchema.FindTable(name); var foreignKey = - table.ForeignKeys.SingleOrDefault(fk => fk.MasterTable.Schema == this.Schema && fk.MasterTable.Name == this.ActualName); + table.ForeignKeys.FirstOrDefault(fk => fk.MasterTable.Schema == this.Schema && fk.MasterTable.Name == this.ActualName); if (foreignKey == null) return null; diff --git a/Simple.Data.Ado/Schema/TableCollection.cs b/Simple.Data.Ado/Schema/TableCollection.cs index 06ef191f..787f98a2 100644 --- a/Simple.Data.Ado/Schema/TableCollection.cs +++ b/Simple.Data.Ado/Schema/TableCollection.cs @@ -36,7 +36,7 @@ public Table Find(string tableName) if (table == null) { - throw new UnresolvableObjectException(tableName, "No matching table found, or insufficient permissions."); + throw new UnresolvableObjectException(tableName, string.Format("Table '{0}' not found, or insufficient permissions.", tableName)); } return table; @@ -66,7 +66,8 @@ public Table Find(string tableName, string schemaName) if (table == null) { - throw new UnresolvableObjectException(schemaName + '.' + tableName, "No matching table found, or insufficient permissions."); + string fullTableName = schemaName + '.' + tableName; + throw new UnresolvableObjectException(fullTableName, string.Format("Table '{0}' not found, or insufficient permissions.", fullTableName)); } return table; diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index 76b1b530..8f5e43ed 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -1,170 +1,176 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {ECC2D7DB-EC7F-44B6-B09F-5B471C629685} - Library - Properties - Simple.Data.Ado - Simple.Data.Ado - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - SecurityRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - CommonAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {148CEE80-2E84-4ABD-B5AB-20415B2BBD21} - Simple.Data - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {ECC2D7DB-EC7F-44B6-B09F-5B471C629685} + Library + Properties + Simple.Data.Ado + Simple.Data.Ado + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + SecurityRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + CommonAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {148CEE80-2E84-4ABD-B5AB-20415B2BBD21} + Simple.Data + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index bdafe2d8..44185d8e 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 1.0.0-beta1 + 0.18.3.1 Mark Rendle Mark Rendle ADO Adapter for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver database data ado .net40 en-us - + diff --git a/Simple.Data.Ado/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index 9226e41c..bc5e6b53 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace Simple.Data.Ado { using System; @@ -5,16 +7,22 @@ namespace Simple.Data.Ado using System.Text; using Schema; - class SimpleReferenceFormatter + public class SimpleReferenceFormatter { - private readonly IFunctionNameConverter _functionNameConverter = new FunctionNameConverter(); + private readonly IFunctionNameConverter _functionNameConverter; private readonly DatabaseSchema _schema; private readonly ICommandBuilder _commandBuilder; - public SimpleReferenceFormatter(DatabaseSchema schema, ICommandBuilder commandBuilder) + public SimpleReferenceFormatter(DatabaseSchema schema, ICommandBuilder commandBuilder) : this(schema, commandBuilder, null) + { + + } + + public SimpleReferenceFormatter(DatabaseSchema schema, ICommandBuilder commandBuilder, IFunctionNameConverter functionNameConverter) { _schema = schema; _commandBuilder = commandBuilder; + _functionNameConverter = functionNameConverter ?? new FunctionNameConverter(); } public string FormatColumnClause(SimpleReference reference) @@ -42,7 +50,9 @@ private string FormatColumnClause(SimpleReference reference, bool excludeAlias) throw new InvalidOperationException("SimpleReference type not supported."); } +// ReSharper disable UnusedParameter.Local private string TryFormatAsAllColumnsReference(AllColumnsSpecialReference allColumnsSpecialReference, bool excludeAlias) +// ReSharper restore UnusedParameter.Local { if (ReferenceEquals(allColumnsSpecialReference, null)) return null; var table = _schema.FindTable(allColumnsSpecialReference.Table.GetAllObjectNamesDotted()); @@ -55,7 +65,7 @@ private string TryFormatAsAllColumnsReference(AllColumnsSpecialReference allColu private string FormatObject(object obj) { var reference = obj as SimpleReference; - return reference != null ? FormatColumnClause(reference) : obj.ToString(); + return reference != null ? FormatColumnClause(reference) : _commandBuilder.AddParameter(obj).Name; } private string TryFormatAsMathReference(MathReference mathReference, bool excludeAlias) @@ -64,34 +74,34 @@ private string TryFormatAsMathReference(MathReference mathReference, bool exclud if (excludeAlias || mathReference.GetAlias() == null) { - return string.Format("{0} {1} {2}", FormatObject(mathReference.LeftOperand), + return string.Format("({0} {1} {2})", FormatObject(mathReference.LeftOperand), MathOperatorToString(mathReference.Operator), FormatObject(mathReference.RightOperand)); } - return string.Format("{0} {1} {2} AS {3}", FormatObject(mathReference.LeftOperand), + return string.Format("({0} {1} {2}) AS {3}", FormatObject(mathReference.LeftOperand), MathOperatorToString(mathReference.Operator), FormatObject(mathReference.RightOperand), - mathReference.GetAlias()); + _schema.QuoteObjectName(mathReference.GetAlias())); } - private static string MathOperatorToString(MathOperator @operator) + private string MathOperatorToString(MathOperator @operator) { switch (@operator) { case MathOperator.Add: - return "+"; + return _schema.Operators.Add; case MathOperator.Subtract: - return "-"; + return _schema.Operators.Subtract; case MathOperator.Multiply: - return "*"; + return _schema.Operators.Multiply; case MathOperator.Divide: - return "/"; + return _schema.Operators.Divide; case MathOperator.Modulo: - return "%"; + return _schema.Operators.Modulo; default: - throw new InvalidOperationException("Invalid MathOperator specified."); + throw new InvalidEnumArgumentException("Invalid MathOperator specified."); } } @@ -100,16 +110,24 @@ private string TryFormatAsFunctionReference(FunctionReference functionReference, if (ReferenceEquals(functionReference, null)) return null; var sqlName = _functionNameConverter.ConvertToSqlName(functionReference.Name); - if (excludeAlias || functionReference.GetAlias() == null) + string formatted; + + if (sqlName.Equals("countdistinct", StringComparison.OrdinalIgnoreCase)) + { + formatted = string.Format("count(distinct {0})", FormatColumnClause(functionReference.Argument)); + } + else { - return string.Format("{0}({1}{2})", sqlName, + formatted = string.Format("{0}({1}{2})", sqlName, FormatColumnClause(functionReference.Argument), FormatAdditionalArguments(functionReference.AdditionalArguments)); } - return string.Format("{0}({1}{2}) AS {3}", sqlName, - FormatColumnClause(functionReference.Argument), - FormatAdditionalArguments(functionReference.AdditionalArguments), - _schema.QuoteObjectName(functionReference.GetAlias())); + + if ((!excludeAlias) && functionReference.GetAlias() != null) + { + formatted = string.Format("{0} AS {1}", formatted, _schema.QuoteObjectName(functionReference.GetAlias())); + } + return formatted; } private string FormatAdditionalArguments(IEnumerable additionalArguments) @@ -133,10 +151,12 @@ private string TryFormatAsObjectReference(ObjectReference objectReference, bool : _schema.QuoteObjectName(objectReference.GetOwner().GetAlias()); var column = table.FindColumn(objectReference.GetName()); if (excludeAlias || objectReference.GetAlias() == null) + { return string.Format("{0}.{1}", tableName, column.QuotedName); - else - return string.Format("{0}.{1} AS {2}", tableName, column.QuotedName, - _schema.QuoteObjectName(objectReference.GetAlias())); + } + + return string.Format("{0}.{1} AS {2}", tableName, column.QuotedName, + _schema.QuoteObjectName(objectReference.GetAlias())); } } diff --git a/Simple.Data.Ado/TraceHelper.cs b/Simple.Data.Ado/TraceHelper.cs index 5faac08b..562c570a 100644 --- a/Simple.Data.Ado/TraceHelper.cs +++ b/Simple.Data.Ado/TraceHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; @@ -8,26 +7,28 @@ namespace Simple.Data.Ado { - using System.Security; - public static class TraceHelper { - public static void WriteTrace(this IDbCommand command) + private static readonly string EOL = Environment.NewLine; + + public static void WriteTrace (this IDbCommand command) { - if (Database.TraceLevel < TraceLevel.Info) return; + if (!SimpleDataTraceSources.TraceSource.Switch.Level.HasFlag(SourceLevels.Information)) + return; try { - var str = new StringBuilder(); - str.AppendLine(); - str.AppendLine(command.CommandType.ToString()); - str.AppendLine(command.CommandText); - foreach (var parameter in command.Parameters.OfType()) + var sb = new StringBuilder(); + sb.AppendFormat("SQL command (CommandType={0}):{1} {2}", command.CommandType, EOL, command.CommandText); + if (command.Parameters.Count > 0) { - str.AppendFormat("{0} ({1}) = {2}", parameter.ParameterName, parameter.DbType, parameter.Value); - str.AppendLine(); + sb.AppendFormat("{0}Parameters:", EOL); + foreach (var parameter in command.Parameters.OfType()) + { + object strValue = parameter.Value is string ? string.Format("\"{0}\"", parameter.Value) : parameter.Value; + sb.AppendFormat("{0} {1} ({2}) = {3}", EOL, parameter.ParameterName, parameter.DbType, strValue); + } } - - Trace.WriteLine(str.ToString(), "Simple.Data.Ado"); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Information, SimpleDataTraceSources.SqlMessageId, sb.ToString()); } catch (Exception) { @@ -37,4 +38,4 @@ public static void WriteTrace(this IDbCommand command) } } } -} +} \ No newline at end of file diff --git a/Simple.Data.Ado/TypeHelper.cs b/Simple.Data.Ado/TypeHelper.cs index 1d91fa7e..3da1022f 100644 --- a/Simple.Data.Ado/TypeHelper.cs +++ b/Simple.Data.Ado/TypeHelper.cs @@ -23,6 +23,7 @@ public static class TypeHelper typeof (double), typeof (decimal), typeof (DateTime), + typeof (DateTimeOffset), typeof (string), typeof (byte[]), typeof (Guid), @@ -49,14 +50,14 @@ public static class TypeHelper {DbType.StringFixedLength, typeof(string)}, {DbType.AnsiStringFixedLength, typeof(string)}, {DbType.Xml, typeof(string)}, - {DbType.DateTimeOffset, typeof(DateTime)}, {DbType.DateTime2, typeof(DateTime)}, {DbType.VarNumeric, typeof(double)}, {DbType.UInt16, typeof(ushort)}, {DbType.String, typeof(string)}, {DbType.Time, typeof(TimeSpan)}, {DbType.UInt64, typeof(ulong)}, - {DbType.UInt32, typeof(uint)} + {DbType.UInt32, typeof(uint)}, + {DbType.DateTimeOffset, typeof(DateTimeOffset)}, }; public static bool IsKnownType(Type type) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index abebf4b8..836afe9b 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -6,6 +6,7 @@ namespace Simple.Data.Ado { + using System.Diagnostics; using Extensions; internal class UpdateHelper @@ -23,11 +24,29 @@ public UpdateHelper(DatabaseSchema schema) public ICommandBuilder GetUpdateCommand(string tableName, IDictionary data, SimpleExpression criteria) { - _commandBuilder.Append(GetUpdateClause(tableName, data)); + var table = _schema.FindTable(tableName); + var updateClause = GetUpdateClause(table, data); + if (string.IsNullOrWhiteSpace(updateClause)) throw new InvalidOperationException("No columns to update."); + _commandBuilder.Append(updateClause); if (criteria != null ) { - var whereStatement = _expressionFormatter.Format(criteria); + string whereStatement = null; + if (criteria.GetOperandsOfType().Any(o => IsTableChain(tableName, o))) + { + if (table.PrimaryKey.Length == 1) + { + whereStatement = CreateWhereInStatement(criteria, table); + } + else if (table.PrimaryKey.Length > 1) + { + whereStatement = CreateWhereExistsStatement(criteria, table); + } + } + else + { + whereStatement = _expressionFormatter.Format(criteria); + } if (!string.IsNullOrEmpty(whereStatement)) _commandBuilder.Append(" where " + whereStatement); } @@ -35,12 +54,54 @@ public ICommandBuilder GetUpdateCommand(string tableName, IDictionary> data) + private bool IsTableChain(string tableName, ObjectReference o) + { + var ownerName = tableName.Contains(".") ? o.GetOwner().GetAllObjectNamesDotted() : o.GetOwner().GetName(); + return (!ownerName.Equals(tableName, StringComparison.InvariantCultureIgnoreCase)) && _schema.IsTable(ownerName); + } + + private string CreateWhereInStatement(SimpleExpression criteria, Table table) + { + var inClauseBuilder = new CommandBuilder(_schema); + var keyColumn = table.FindColumn(table.PrimaryKey[0]); + inClauseBuilder.Append(string.Format("SELECT {0} FROM {1}", + keyColumn.QualifiedName, table.QualifiedName)); + inClauseBuilder.Append(" "); + inClauseBuilder.Append(string.Join(" ", + new Joiner(JoinType.Inner, _schema).GetJoinClauses( + new ObjectName(table.Schema, table.ActualName), criteria))); + inClauseBuilder.Append(" where "); + inClauseBuilder.Append(_expressionFormatter.Format(criteria)); + return string.Format("{0} IN ({1})", keyColumn.QualifiedName, inClauseBuilder.Text); + } + + private string CreateWhereExistsStatement(SimpleExpression criteria, Table table) + { + var inClauseBuilder = new CommandBuilder(_schema); + inClauseBuilder.Append(string.Join(" ", + new Joiner(JoinType.Inner, _schema).GetJoinClauses( + new ObjectName(table.Schema, table.ActualName), criteria))); + inClauseBuilder.Append(" where "); + inClauseBuilder.Append(_expressionFormatter.Format(criteria)); + var updateJoin = _schema.QuoteObjectName("_updatejoin"); + var whereClause = new StringBuilder(string.Format("SELECT 1 FROM {0} [_updatejoin] ", table.QualifiedName)); + whereClause.Append(inClauseBuilder.Text.Replace(table.QualifiedName, updateJoin)); + whereClause.Append(" AND ("); + bool appendAnd = false; + foreach (var column in table.PrimaryKey.AsEnumerable().Select(table.FindColumn)) + { + if (appendAnd) whereClause.Append(" AND "); + whereClause.AppendFormat("{0}.{1} = {2}", updateJoin, column.QuotedName, column.QualifiedName); + appendAnd = true; + } + whereClause.Append(")"); + return string.Format("EXISTS ({0})", whereClause); + } + + private string GetUpdateClause(Table table, IEnumerable> data) { - var table = _schema.FindTable(tableName); - var setClause = string.Join(", ", - data.Where(kvp => table.HasColumn(kvp.Key)) + data.Where(kvp => table.HasColumn(kvp.Key) && table.FindColumn(kvp.Key).IsWriteable) .Select(kvp => CreateColumnUpdateClause(kvp.Key, kvp.Value, table))); return string.Format("update {0} set {1}", table.QualifiedName, setClause); } @@ -48,7 +109,20 @@ private string GetUpdateClause(string tableName, IEnumerable q.ToList()); + GeneratedSqlIs(expectedSql); + } + } +} diff --git a/Simple.Data.BehaviourTest/App.config b/Simple.Data.BehaviourTest/App.config index ebc651a8..a718d8bf 100644 --- a/Simple.Data.BehaviourTest/App.config +++ b/Simple.Data.BehaviourTest/App.config @@ -8,4 +8,7 @@ + + + \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/BulkInsertTest.cs b/Simple.Data.BehaviourTest/BulkInsertTest.cs index fd1f6d60..19fdb910 100644 --- a/Simple.Data.BehaviourTest/BulkInsertTest.cs +++ b/Simple.Data.BehaviourTest/BulkInsertTest.cs @@ -11,11 +11,16 @@ public class BulkInsertTest : DatabaseIntegrationContext protected override void SetSchema(MockSchemaProvider schemaProvider) { schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, - new[] { "dbo", "NoIdentityColumnUsers", "BASE TABLE" }); + new[] { "dbo", "NoIdentityColumnUsers", "BASE TABLE" }, + new[] { "foo", "Users", "BASE TABLE" }); schemaProvider.SetColumns(new object[] { "dbo", "Users", "Id", true }, new[] { "dbo", "Users", "Name" }, new[] { "dbo", "Users", "Password" }, new[] { "dbo", "Users", "Age" }, + new object[] { "foo", "Users", "Id", true }, + new[] { "foo", "Users", "Name" }, + new[] { "foo", "Users", "Password" }, + new[] { "foo", "Users", "Age" }, new[] { "dbo", "NoIdentityColumnUsers", "Id" }, new[] { "dbo", "NoIdentityColumnUsers", "Name" }, new[] { "dbo", "NoIdentityColumnUsers", "Password" }, @@ -33,6 +38,17 @@ public void TestBulkInsertWithStaticTypeObjectAndIdentityColumn() Parameter(1).Is(DBNull.Value); Parameter(2).Is(42); } + + [Test] + public void TestBulkInsertWithStaticTypeObjectAndIdentityColumnOnSchemaQualifiedTable() + { + var users = new[] { new User { Name = "Steve", Age = 50 }, new User { Name = "Phil", Age = 42 }}; + _db.foo.Users.Insert(users); + GeneratedSqlIs("insert into [foo].[Users] ([Name],[Password],[Age]) values (@p0,@p1,@p2)"); + Parameter(0).Is("Phil"); + Parameter(1).Is(DBNull.Value); + Parameter(2).Is(42); + } [Test] public void TestBulkInsertWithStaticTypeObjectAndIdentityColumnAndIdentityFunctionThatExpectsAValueSelects() diff --git a/Simple.Data.BehaviourTest/DatabaseIntegrationContext.cs b/Simple.Data.BehaviourTest/DatabaseIntegrationContext.cs index 12ecb0b1..6bf0508f 100644 --- a/Simple.Data.BehaviourTest/DatabaseIntegrationContext.cs +++ b/Simple.Data.BehaviourTest/DatabaseIntegrationContext.cs @@ -11,7 +11,7 @@ namespace Simple.Data.IntegrationTest public abstract class DatabaseIntegrationContext { protected MockDatabase _mockDatabase; - protected MockConnectionProvider _MockConnectionProvider; + protected Mocking.Ado.MockConnectionProvider _MockConnectionProvider; protected dynamic _db; protected abstract void SetSchema(MockSchemaProvider schemaProvider); @@ -52,7 +52,7 @@ Database CreateDatabase(MockDatabase mockDatabase) SetSchema(mockSchemaProvider); - _MockConnectionProvider = new MockConnectionProvider(new MockDbConnection(mockDatabase), mockSchemaProvider); + _MockConnectionProvider = new Mocking.Ado.MockConnectionProvider(new MockDbConnection(mockDatabase), mockSchemaProvider); var adapter = MockHelper.CreateMockAdoAdapter(_MockConnectionProvider); MockHelper.UseMockAdapter(adapter); return Database.Open(); diff --git a/Simple.Data.BehaviourTest/FindTest.cs b/Simple.Data.BehaviourTest/FindTest.cs index a34533b9..32860526 100644 --- a/Simple.Data.BehaviourTest/FindTest.cs +++ b/Simple.Data.BehaviourTest/FindTest.cs @@ -1,6 +1,6 @@ namespace Simple.Data.IntegrationTest { - using System.Collections; + using System; using System.Collections.Generic; using Mocking.Ado; using NUnit.Framework; @@ -10,6 +10,7 @@ public class FindTest : DatabaseIntegrationContext { protected override void SetSchema(MockSchemaProvider schemaProvider) { +// ReSharper disable CoVariantArrayConversion schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, new[] { "dbo", "MyTable", "BASE TABLE" }); schemaProvider.SetColumns(new object[] { "dbo", "Users", "Id", true }, @@ -18,14 +19,15 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) new[] { "dbo", "Users", "Age" }, new[] { "dbo", "MyTable", "Column1"}); schemaProvider.SetPrimaryKeys(new object[] { "dbo", "Users", "Id", 0 }); +// ReSharper restore CoVariantArrayConversion } - private const string usersColumns = "[dbo].[Users].[Id], [dbo].[Users].[Name], [dbo].[Users].[Password], [dbo].[Users].[Age]"; + private const string UsersColumns = "[dbo].[Users].[Id], [dbo].[Users].[Name], [dbo].[Users].[Password], [dbo].[Users].[Age]"; [Test] public void TestFindEqualWithInt32() { _db.Users.Find(_db.Users.Id == 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[users] where [dbo].[users].[id] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[users] where [dbo].[users].[id] = @p1"); Parameter(0).Is(1); } @@ -33,14 +35,14 @@ public void TestFindEqualWithInt32() public void TestFindWithNull() { _db.Users.Find(_db.Users.Id == null); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[users] where [dbo].[users].[id] IS NULL"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[users] where [dbo].[users].[id] IS NULL"); } [Test] public void TestFindWithTwoCriteriasOneBeingNull() { _db.Users.Find(_db.Users.Id == 1 || _db.Users.Id == null); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[users] where ([dbo].[users].[id] = @p1 OR [dbo].[users].[id] IS NULL)"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[users] where ([dbo].[users].[id] = @p1 OR [dbo].[users].[id] IS NULL)"); Parameter(0).Is(1); Parameter(1).DoesNotExist(); } @@ -49,7 +51,7 @@ public void TestFindWithTwoCriteriasOneBeingNull() public void TestFindNotEqualWithInt32() { _db.Users.Find(_db.Users.Id != 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] != @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] != @p1"); Parameter(0).Is(1); } @@ -57,7 +59,7 @@ public void TestFindNotEqualWithInt32() public void TestFindGreaterThanWithInt32() { _db.Users.Find(_db.Users.Id > 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] > @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] > @p1"); Parameter(0).Is(1); } @@ -65,7 +67,7 @@ public void TestFindGreaterThanWithInt32() public void TestFindGreaterThanOrEqualWithInt32() { _db.Users.Find(_db.Users.Id >= 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] >= @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] >= @p1"); Parameter(0).Is(1); } @@ -73,7 +75,7 @@ public void TestFindGreaterThanOrEqualWithInt32() public void TestFindLessThanWithInt32() { _db.Users.Find(_db.Users.Id < 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] < @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] < @p1"); Parameter(0).Is(1); } @@ -81,7 +83,7 @@ public void TestFindLessThanWithInt32() public void TestFindLessThanOrEqualWithInt32() { _db.Users.Find(_db.Users.Id <= 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] <= @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] <= @p1"); Parameter(0).Is(1); } @@ -89,23 +91,33 @@ public void TestFindLessThanOrEqualWithInt32() public void TestFindModulo() { _db.Users.Find(_db.Users.Id % 2 == 1); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] % 2 = @p1"); - Parameter(0).Is(1); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] % @p1) = @p2"); + Parameter(0).Is(2); + Parameter(1).Is(1); } [Test] public void TestFindWithAdd() { _db.Users.Find(_db.Users.Id + _db.Users.Age == 42); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] + [dbo].[Users].[Age] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] + [dbo].[Users].[Age]) = @p1"); Parameter(0).Is(42); } + [Test] + public void TestFindWithAddAndMultiply() + { + _db.Users.Find((_db.Users.Id + _db.Users.Age) * 2 == 42); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where (([dbo].[Users].[Id] + [dbo].[Users].[Age]) * @p1) = @p2"); + Parameter(0).Is(2); + Parameter(1).Is(42); + } + [Test] public void TestFindWithSubtract() { _db.Users.Find(_db.Users.Id - _db.Users.Age == 42); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] - [dbo].[Users].[Age] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] - [dbo].[Users].[Age]) = @p1"); Parameter(0).Is(42); } @@ -113,7 +125,7 @@ public void TestFindWithSubtract() public void TestFindWithMultiply() { _db.Users.Find(_db.Users.Id * _db.Users.Age == 42); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] * [dbo].[Users].[Age] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] * [dbo].[Users].[Age]) = @p1"); Parameter(0).Is(42); } @@ -121,7 +133,7 @@ public void TestFindWithMultiply() public void TestFindWithDivide() { _db.Users.Find(_db.Users.Id / _db.Users.Age == 42); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] / [dbo].[Users].[Age] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] / [dbo].[Users].[Age]) = @p1"); Parameter(0).Is(42); } @@ -129,15 +141,22 @@ public void TestFindWithDivide() public void TestFindByNamedParameterSingleColumn() { _db.Users.FindBy(Name: "Foo"); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[name] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[name] = @p1"); Parameter(0).Is("Foo"); } + + [Test] + public void TestFindByNamedParameterSingleColumnNullValue() + { + _db.Users.FindBy(Name: null); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[name] is null"); + } [Test] public void TestFindByNamedParameterTwoColumns() { _db.Users.FindBy(Name: "Foo", Password: "password"); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where ([dbo].[Users].[name] = @p1 and [dbo].[Users].[password] = @p2)"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[name] = @p1 and [dbo].[Users].[password] = @p2)"); Parameter(0).Is("Foo"); Parameter(1).Is("password"); } @@ -146,7 +165,7 @@ public void TestFindByNamedParameterTwoColumns() public void TestFindByDynamicSingleColumn() { _db.Users.FindByName("Foo"); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[name] = @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[name] = @p1"); Parameter(0).Is("Foo"); } @@ -160,7 +179,9 @@ public void TestFindByDynamicSingleColumnNull() [Test] public void TestFindAllByDynamicSingleColumnNull() { +#pragma warning disable 168 IEnumerable result = _db.MyTable.FindAllByColumn1(null).ToList(); +#pragma warning restore 168 GeneratedSqlIs("select [dbo].[MyTable].[Column1] from [dbo].[MyTable] where [dbo].[MyTable].[Column1] is null"); } @@ -183,7 +204,7 @@ public void TestFindByWithAnonymousObjectNullValue() public void TestFindWithLike() { _db.Users.Find(_db.Users.Name.Like("Foo")); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[name] like @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[name] like @p1"); Parameter(0).Is("Foo"); } @@ -191,7 +212,7 @@ public void TestFindWithLike() public void TestFindWithNotLike() { _db.Users.Find(_db.Users.Name.NotLike("Foo")); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where [dbo].[Users].[name] not like @p1"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[name] not like @p1"); Parameter(0).Is("Foo"); } @@ -203,6 +224,18 @@ public void TestFindAllWithLike() Parameter(0).Is("Foo"); } + [Test] + public void FindAllWithNoParametersThrowsBadExpressionException() + { + Assert.Throws(() => _db.Users.FindAll().ToList()); + } + + [Test] + public void FindAllWithStringParameterThrowsBadExpressionException() + { + Assert.Throws(() => _db.Users.FindAll("Answer").ToList()); + } + [Test] public void TestFindAllWithNotLike() { @@ -219,6 +252,13 @@ public void TestFindAllByNamedParameterSingleColumn() Parameter(0).Is("Foo"); } + [Test] + public void TestFindAllByNamedParameterSingleColumnNull() + { + _db.Users.FindAllBy(Name: null).ToList(); + GeneratedSqlIs("select [dbo].[Users].[id],[dbo].[Users].[name],[dbo].[Users].[password],[dbo].[Users].[age] from [dbo].[Users] where [dbo].[Users].[name] is null"); + } + [Test] public void TestFindAllByNamedParameterTwoColumns() { @@ -233,7 +273,7 @@ public void TestFindAllByNamedParameterTwoColumns() public void TestFindByDynamicTwoColumns() { _db.Users.FindByNameAndPassword("Foo", "secret"); - GeneratedSqlIs("select " + usersColumns + " from [dbo].[Users] where ([dbo].[Users].[name] = @p1 and [dbo].[Users].[password] = @p2)"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[name] = @p1 and [dbo].[Users].[password] = @p2)"); Parameter(0).Is("Foo"); Parameter(1).Is("secret"); } @@ -245,6 +285,18 @@ public void TestFindAllByDynamic() GeneratedSqlIs("select [dbo].[Users].[id],[dbo].[Users].[name],[dbo].[Users].[password],[dbo].[Users].[age] from [dbo].[Users] where [dbo].[Users].[name] = @p1"); Parameter(0).Is("Foo"); } + + [Test] + public void FindByWithoutArgsThrowsArgumentException() + { + Assert.Throws(() => _db.Users.FindBy()); + } + + [Test] + public void FindAllByWithoutArgsThrowsArgumentException() + { + Assert.Throws(() => _db.Users.FindAllBy()); + } } class MyTable diff --git a/Simple.Data.BehaviourTest/GetCountTest.cs b/Simple.Data.BehaviourTest/GetCountTest.cs new file mode 100644 index 00000000..e33b5940 --- /dev/null +++ b/Simple.Data.BehaviourTest/GetCountTest.cs @@ -0,0 +1,66 @@ +namespace Simple.Data.IntegrationTest +{ + using System; + using Mocking.Ado; + using NUnit.Framework; + + [TestFixture] + public class GetCountTest : DatabaseIntegrationContext + { + protected override void SetSchema(MockSchemaProvider schemaProvider) + { + schemaProvider.SetTables(new[] {"dbo", "Users", "BASE TABLE"}); + + schemaProvider.SetColumns(new object[] {"dbo", "Users", "Id", true}, + new[] {"dbo", "Users", "Name"}, + new[] {"dbo", "Users", "Password"}, + new[] {"dbo", "Users", "Age"}); + } + + [Test] + public void GetCountBasic() + { + EatException(() => _db.Users.GetCount()); + GeneratedSqlIs("select count(*) from [dbo].[users]"); + } + + [Test] + public void QueryCountBasic() + { + EatException(() => _db.Users.All().Count()); + GeneratedSqlIs("select count(*) from [dbo].[users]"); + } + + [Test] + public void GetCountByBasic() + { + EatException(() => _db.Users.GetCountByAge(42)); + GeneratedSqlIs("select count(*) from [dbo].[users] where [dbo].[users].[age] = @p1"); + } + + [Test] + public void GetCountByNamedParameters() + { + EatException(() => _db.Users.GetCountBy(Age: 42)); + GeneratedSqlIs("select count(*) from [dbo].[users] where [dbo].[users].[age] = @p1"); + } + + [Test] + public void GetCountWithColumnThrowsException() + { + Assert.Throws(() => _db.Users.GetCount(_db.Users.Id)); + } + + [Test] + public void AssigningToColumnThrowsException() + { + Assert.Throws(() => _db.Users.GetCount(_db.Users.Id = 1)); + } + + [Test] + public void MultipleArgumentsThrowsException() + { + Assert.Throws(() => _db.Users.GetCount(_db.Users.Id == 1, _db.Users.Name == "Bob")); + } + } +} \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/GetTest.cs b/Simple.Data.BehaviourTest/GetTest.cs index cb14a007..4d188863 100644 --- a/Simple.Data.BehaviourTest/GetTest.cs +++ b/Simple.Data.BehaviourTest/GetTest.cs @@ -36,6 +36,14 @@ public void TestGetWithSingleColumn() Parameter(0).Is(1); } + [Test] + public void TestSchemaQualifiedGetWithSingleColumn() + { + _db.dbo.Users.Get(1); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[users] where [id] = @p1"); + Parameter(0).Is(1); + } + [Test] public void TestGetWithTwoColumns() { diff --git a/Simple.Data.BehaviourTest/NameResolutionTests.cs b/Simple.Data.BehaviourTest/NameResolutionTests.cs index 1b426ab7..fad7c85f 100644 --- a/Simple.Data.BehaviourTest/NameResolutionTests.cs +++ b/Simple.Data.BehaviourTest/NameResolutionTests.cs @@ -78,7 +78,7 @@ public void CompaniesPluralizationIsResolved() class EntityPluralizer : IPluralizer { private readonly PluralizationService _pluralizationService = - PluralizationService.CreateService(CultureInfo.CurrentCulture); + PluralizationService.CreateService(CultureInfo.GetCultureInfo("en-us")); // only English is supported public bool IsPlural(string word) { diff --git a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs index e673e542..a580d8b1 100644 --- a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs +++ b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs @@ -276,6 +276,27 @@ public void TwoJoins() "WHERE (([dbo].[Activity].[ID_Trip] = @p1 AND [dbo].[Activity].[Activity_Time] = @p2) AND [dbo].[Activity].[Is_Public] = @p3)"); } + [Test] + public void PassingTrueToOnThrowsBadExpressionException() + { + Assert.Throws( + () => _db.Activity.All().Join(_db.Location).On(true)); + } + + [Test] + public void PassingObjectReferenceToOnThrowsBadExpressionException() + { + Assert.Throws( + () => _db.Activity.All().Join(_db.Location).On(_db.Location.ID_Location)); + } + + [Test] + public void PassingTablesWrongWayRoundThrowsBadExpressionException() + { + Assert.Throws( + () => _db.Activity.All().Join(_db.Activity, LocationId: _db.Location.ID_Location)); + } + class Activity { public int ID_Activity { get; set; } diff --git a/Simple.Data.BehaviourTest/Query/FunctionTest.cs b/Simple.Data.BehaviourTest/Query/FunctionTest.cs index d00e5418..3eb406b7 100644 --- a/Simple.Data.BehaviourTest/Query/FunctionTest.cs +++ b/Simple.Data.BehaviourTest/Query/FunctionTest.cs @@ -63,5 +63,15 @@ public void GroupingAndOrderingOnFunction() GeneratedSqlIs(expected); } + + [Test] + public void CountingOnColumn() + { + const string expected = + @"select [dbo].[users].[name],count(distinct isnull([dbo].[users].[password],@p1)) as [c] from [dbo].[users] group by [dbo].[users].[name]"; + EatException( + () => _db.Users.All().Select(_db.Users.Name, _db.Users.Password.IsNull("No Password").CountDistinct().As("c")).ToList()); + GeneratedSqlIs(expected); + } } } diff --git a/Simple.Data.BehaviourTest/Query/QueryTest.cs b/Simple.Data.BehaviourTest/Query/QueryTest.cs index b8488f25..c4749644 100644 --- a/Simple.Data.BehaviourTest/Query/QueryTest.cs +++ b/Simple.Data.BehaviourTest/Query/QueryTest.cs @@ -71,6 +71,15 @@ public void SpecifyingColumnWithAliasShouldAddAsClause() .ToList(); GeneratedSqlIs("select [dbo].[users].[name],[dbo].[users].[password] as [supersecretpassword] from [dbo].[users]"); } + + [Test] + public void SpecifyingColumnMathsWithAliasShouldAddAsClause() + { + _db.Users.All() + .Select(_db.Users.Name, (_db.Users.Id + _db.Users.Age).As("Nonsense")) + .ToList(); + GeneratedSqlIs("select [dbo].[users].[name],([dbo].[users].[id] + [dbo].[users].[age]) as [nonsense] from [dbo].[users]"); + } [Test] public void SpecifyingColumnsFromOtherTablesShouldAddJoin() @@ -220,6 +229,22 @@ public void SpecifyingJoinTableShouldCreateDirectQuery() " join [dbo].[users] on ([dbo].[users].[id] = [dbo].[userbio].[userid]) where [dbo].[users].[id] = @p1"); } + [Test] + public void SpecifyOrderByWithoutReferenceThrowsException() + { + Assert.Throws(() => _db.Users.All().OrderBy(1)); + } + [Test] + public void SpecifyOrderByDescendingWithoutReferenceThrowsException() + { + Assert.Throws(() => _db.Users.All().OrderByDescending(1)); + } + + [Test] + public void CallingFirstOnTableShouldThrowInvalidOperationException() + { + Assert.Throws(() => _db.Users.First()); + } } } diff --git a/Simple.Data.BehaviourTest/Query/WhereTest.cs b/Simple.Data.BehaviourTest/Query/WhereTest.cs new file mode 100644 index 00000000..c9e18b30 --- /dev/null +++ b/Simple.Data.BehaviourTest/Query/WhereTest.cs @@ -0,0 +1,52 @@ +namespace Simple.Data.IntegrationTest +{ + using System; + using Mocking.Ado; + using NUnit.Framework; + + [TestFixture] + public class WhereTest : DatabaseIntegrationContext + { + [Test] + public void WhereWithNoParametersShouldThrowBadExpressionException() + { + Assert.Throws(() => _db.Users.All().Where()); + } + + [Test] + public void WhereWithStringParameterShouldThrowBadExpressionException() + { + Assert.Throws(() => _db.Users.All().Where("Answers")); + } + + [Test] + public void WhereWithNullParameterShouldThrowArgumentNullException() + { + Assert.Throws(() => _db.Users.All().Where(null)); + } + + protected override void SetSchema(MockSchemaProvider schemaProvider) + { + schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, + new[] { "dbo", "UserBio", "BASE TABLE" }, + new[] { "dbo", "UserPayment", "BASE TABLE" }, + new[] { "dbo", "Employee", "BASE TABLE" }); + + schemaProvider.SetColumns(new object[] { "dbo", "Users", "Id", true }, + new[] { "dbo", "Users", "Name" }, + new[] { "dbo", "Users", "Password" }, + new[] { "dbo", "Users", "Age" }, + new[] { "dbo", "UserBio", "UserId" }, + new[] { "dbo", "UserBio", "Text" }, + new[] { "dbo", "UserPayment", "UserId" }, + new[] { "dbo", "UserPayment", "Amount" }, + new[] { "dbo", "Employee", "Id" }, + new[] { "dbo", "Employee", "Name" }, + new[] { "dbo", "Employee", "ManagerId" }); + + schemaProvider.SetPrimaryKeys(new object[] { "dbo", "Users", "Id", 0 }); + schemaProvider.SetForeignKeys(new object[] { "FK_Users_UserBio", "dbo", "UserBio", "UserId", "dbo", "Users", "Id", 0 }, + new object[] { "FK_Users_UserPayment", "dbo", "UserPayment", "UserId", "dbo", "Users", "Id", 0 }); + } + } +} \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index 506b6059..d74b618b 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -1,6 +1,5 @@ namespace Simple.Data.IntegrationTest.Query { - using System; using Mocking.Ado; using NUnit.Framework; @@ -9,9 +8,14 @@ public class WithTest : DatabaseIntegrationContext { protected override void SetSchema(MockSchemaProvider schemaProvider) { +// ReSharper disable CoVariantArrayConversion schemaProvider.SetTables(new[] { "dbo", "Employee", "BASE TABLE" }, new[] { "dbo", "Department", "BASE TABLE" }, new[] { "dbo", "Activity", "BASE TABLE" }, + new[] { "dbo", "Customer", "BASE TABLE" }, + new[] { "dbo", "Order", "BASE TABLE" }, + new[] { "dbo", "Item", "BASE TABLE" }, + new[] { "dbo", "Note", "BASE TABLE" }, new[] { "dbo", "Activity_Join", "BASE TABLE" }, new[] { "dbo", "Location", "BASE_TABLE" }); @@ -21,19 +25,42 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) new[] { "dbo", "Employee", "DepartmentId" }, new[] { "dbo", "Department", "Id" }, new[] { "dbo", "Department", "Name" }, - new[] { "dbo", "Activity", "ID_Activity" }, - new[] { "dbo", "Activity", "ID_Trip" }, - new[] { "dbo", "Activity", "Activity_Time" }, - new[] { "dbo", "Activity", "Is_Public" }, + new[] { "dbo", "Customer", "Id" }, + new[] { "dbo", "Customer", "Name" }, + new[] { "dbo", "Order", "Id" }, + new[] { "dbo", "Order", "CustomerId" }, + new[] { "dbo", "Order", "Description" }, + new[] { "dbo", "Item", "Id" }, + new[] { "dbo", "Item", "OrderId" }, + new[] { "dbo", "Item", "Description" }, + new[] { "dbo", "Note", "Id" }, + new[] { "dbo", "Note", "CustomerId" }, + new[] { "dbo", "Note", "Text" }, + new[] { "dbo", "Activity", "ID" }, + new[] { "dbo", "Activity", "Description" }, new[] { "dbo", "Activity_Join", "ID_Activity" }, new[] { "dbo", "Activity_Join", "ID_Location" }, - new[] { "dbo", "Location", "ID_Location" } + new[] { "dbo", "Location", "ID" }, + new[] { "dbo", "Location", "Address" } ); schemaProvider.SetPrimaryKeys(new object[] {"dbo", "Employee", "Id", 0}, - new object[] {"dbo", "Department", "Id", 0}); - - schemaProvider.SetForeignKeys(new object[] { "FK_Employee_Department", "dbo", "Employee", "DepartmentId", "dbo", "Department", "Id", 0 }); + new object[] {"dbo", "Department", "Id", 0}, + new object[] {"dbo", "Customer", "Id", 0}, + new object[] {"dbo", "Order", "Id", 0}, + new object[] {"dbo", "Item", "Id", 0}, + new object[] {"dbo", "Note", "Id", 0} + ); + + schemaProvider.SetForeignKeys( + new object[] { "FK_Employee_Department", "dbo", "Employee", "DepartmentId", "dbo", "Department", "Id", 0 }, + new object[] { "FK_Activity_Join_Activity", "dbo", "Activity_Join", "ID_Activity", "dbo", "Activity", "ID", 0 }, + new object[] { "FK_Activity_Join_Location", "dbo", "Activity_Join", "ID_Location", "dbo", "Location", "ID", 0 }, + new object[] { "FK_Order_Customer", "dbo", "Order", "CustomerId", "dbo", "Customer", "Id", 0 }, + new object[] { "FK_Item_Order", "dbo", "Item", "OrderId", "dbo", "Order", "Id", 0 }, + new object[] { "FK_Note_Customer", "dbo", "Note", "CustomerId", "dbo", "Customer", "Id", 0 } + ); +// ReSharper restore CoVariantArrayConversion } [Test] @@ -113,6 +140,25 @@ public void SingleWithClauseUsingReferenceWithAliasShouldApplyAliasToSql() GeneratedSqlIs(expectedSql); } + [Test] + public void SingleWithClauseUsingTwoStepReference() + { + const string expectedSql = "select "+ + "[dbo].[activity].[id],"+ + "[dbo].[activity].[description],"+ + "[dbo].[location].[id] as [__withn__location__id]," + + "[dbo].[location].[address] as [__withn__location__address]" + + " from [dbo].[activity] "+ + "left join [dbo].[activity_join] on ([dbo].[activity].[id] = [dbo].[activity_join].[id_activity]) "+ + "left join [dbo].[location] on ([dbo].[location].[id] = [dbo].[activity_join].[id_location])"; + + var q = _db.Activity.All().With(_db.Activity.ActivityJoin.Location); + + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } + [Test] public void SingleWithClauseUsingExplicitJoinShouldApplyAliasToSql() { @@ -172,5 +218,105 @@ public void MultipleWithClauseJustDoesEverythingYouWouldHope() GeneratedSqlIs(expectedSql); } + + /// + /// Test for multiple child tables... + /// + [Test] + public void CustomerWithOrdersAndNotes() + { + const string expectedSql = "select [dbo].[customer].[id],[dbo].[customer].[name]," + + "[dbo].[order].[id] as [__withn__orders__id],[dbo].[order].[customerid] as [__withn__orders__customerid],[dbo].[order].[description] as [__withn__orders__description]," + + "[dbo].[note].[id] as [__withn__notes__id],[dbo].[note].[customerid] as [__withn__notes__customerid],[dbo].[note].[text] as [__withn__notes__text]" + + " from [dbo].[customer]" + + " left join [dbo].[order] on ([dbo].[customer].[id] = [dbo].[order].[customerid])" + + " left join [dbo].[note] on ([dbo].[customer].[id] = [dbo].[note].[customerid])"; + + var q = _db.Customers.All().WithOrders().WithNotes(); + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } + + [Test] + public void CustomerWithOrdersWithItems() + { + const string expectedSql = "select [dbo].[Customer].[Id],[dbo].[Customer].[Name]," + + "[dbo].[Order].[Id] AS [__withn__Orders__Id],[dbo].[Order].[CustomerId] AS [__withn__Orders__CustomerId]," + + "[dbo].[Order].[Description] AS [__withn__Orders__Description],[dbo].[Item].[Id] AS [__withn__Items__Id]," + + "[dbo].[Item].[OrderId] AS [__withn__Items__OrderId],[dbo].[Item].[Description] AS [__withn__Items__Description]" + + " from [dbo].[Customer] LEFT JOIN [dbo].[Order] ON ([dbo].[Customer].[Id] = [dbo].[Order].[CustomerId])" + + " LEFT JOIN [dbo].[Item] ON ([dbo].[Order].[Id] = [dbo].[Item].[OrderId])"; + + var q = _db.Customers.All().With(_db.Customers.Orders).With(_db.Customers.Orders.Items); + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } + + /// + /// Test for issue #157 + /// + [Test] + public void CriteriaReferencesShouldNotBeDuplicatedInSql() + { + const string expectedSql = "select [dbo].[employee].[id],[dbo].[employee].[name]," + + "[dbo].[employee].[managerid],[dbo].[employee].[departmentid]," + + "[dbo].[department].[id] as [__with1__department__id],[dbo].[department].[name] as [__with1__department__name]" + + " from [dbo].[employee] join [dbo].[department] on ([dbo].[department].[id] = [dbo].[employee].[departmentid])" + + " where [dbo].[department].[name] = @p1"; + + var q = _db.Employees.FindAll(_db.Employees.Department.Name == "Dev").WithDepartment(); + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } + + /// + /// Test for issue #184 + /// + [Test] + public void CriteriaReferencesShouldUseWithAliasOutValue() + { + const string expectedSql = "select [dbo].[employee].[id],[dbo].[employee].[name]," + + "[dbo].[employee].[managerid],[dbo].[employee].[departmentid]," + + "[foo].[id] as [__with1__foo__id],[foo].[name] as [__with1__foo__name]" + + " from [dbo].[employee] join [dbo].[department] [foo] on ([foo].[id] = [dbo].[employee].[departmentid])" + + " where ([dbo].[employee].[name] like @p1" + + " and [foo].[name] = @p2)"; + + dynamic foo; + var q = _db.Employees.Query() + .Where(_db.Employees.Name.Like("A%")) + .WithOne(_db.Employees.Department.As("Foo"), out foo) + .Where(foo.Name == "Admin"); + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } + + /// + /// Test for issue #184 + /// + [Test] + public void CriteriaReferencesShouldUseSeparateJoinFromWithAlias() + { + const string expectedSql = "select [dbo].[employee].[id],[dbo].[employee].[name]," + + "[dbo].[employee].[managerid],[dbo].[employee].[departmentid]," + + "[foo].[id] as [__with1__foo__id],[foo].[name] as [__with1__foo__name]" + + " from [dbo].[employee]" + + " join [dbo].[department] on ([dbo].[department].[id] = [dbo].[employee].[departmentid])" + + " left join [dbo].[department] [foo] on ([foo].[id] = [dbo].[employee].[departmentid])" + + " where ([dbo].[employee].[name] like @p1" + + " and [dbo].[department].[name] = @p2)"; + + var q = _db.Employees.Query() + .Where(_db.Employees.Name.Like("A%")) + .WithOne(_db.Employees.Department.As("Foo")) + .Where(_db.Employees.Department.Name == "Admin"); + EatException(() => q.ToList()); + + GeneratedSqlIs(expectedSql); + } } } \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/SchemaQualifiedTableTest.cs b/Simple.Data.BehaviourTest/SchemaQualifiedTableTest.cs index 9b72c523..46a83acb 100644 --- a/Simple.Data.BehaviourTest/SchemaQualifiedTableTest.cs +++ b/Simple.Data.BehaviourTest/SchemaQualifiedTableTest.cs @@ -15,11 +15,14 @@ public class SchemaQualifiedTableTest static Database CreateDatabase(MockDatabase mockDatabase) { var mockSchemaProvider = new MockSchemaProvider(); - mockSchemaProvider.SetTables(new[] { "foo", "Users", "BASE TABLE" }); + mockSchemaProvider.SetTables(new[] {"foo", "Users", "BASE TABLE"}, + new[] {"foo.bar", "Test", "BASE TABLE"}); mockSchemaProvider.SetColumns(new[] { "foo", "Users", "Id" }, new[] { "foo", "Users", "Name" }, new[] { "foo", "Users", "Password" }, - new[] { "foo", "Users", "Age" }); + new[] { "foo", "Users", "Age" }, + new[] { "foo.bar", "Test", "Id"}, + new[] { "foo.bar", "Test", "Value"}); mockSchemaProvider.SetPrimaryKeys(new object[] { "foo", "Users", "Id", 0 }); return new Database(new AdoAdapter(new MockConnectionProvider(new MockDbConnection(mockDatabase), mockSchemaProvider))); } @@ -243,5 +246,15 @@ public void TestInsertOnTable() Assert.AreEqual("Phil", mockDatabase.Parameters[0]); Assert.AreEqual(42, mockDatabase.Parameters[1]); } + + [Test] + public void TestFindWithDotInSchemaName() + { + var mockDatabase = new MockDatabase(); + dynamic database = CreateDatabase(mockDatabase); + database.foobar.Test.Find(database.foobar.Test.Id == 1); + Assert.AreEqual("select [foo.bar].[Test].[Id], [foo.bar].[Test].[Value] from [foo.bar].[Test] where [foo.bar].[Test].[id] = @p1".ToLowerInvariant(), mockDatabase.Sql.ToLowerInvariant()); + Assert.AreEqual(1, mockDatabase.Parameters[0]); + } } } diff --git a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj index d77b4151..caca7c23 100644 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj @@ -63,11 +63,13 @@ + + @@ -77,9 +79,11 @@ + + diff --git a/Simple.Data.BehaviourTest/StopUsingMockTest.cs b/Simple.Data.BehaviourTest/StopUsingMockTest.cs new file mode 100644 index 00000000..5980bb71 --- /dev/null +++ b/Simple.Data.BehaviourTest/StopUsingMockTest.cs @@ -0,0 +1,26 @@ +namespace Simple.Data.IntegrationTest +{ + using Ado; + using Mocking.Ado; + using NUnit.Framework; + + [TestFixture] + public class StopUsingMockTest : DatabaseIntegrationContext + { + [Test] + public void StopUsingMockAdapterStopsUsingMockAdapter() + { + var mock = new InMemoryAdapter(); + Database.UseMockAdapter(mock); + Database db = Database.OpenNamedConnection("Mock"); + Assert.AreSame(mock, db.GetAdapter()); + Database.StopUsingMockAdapter(); + db = Database.OpenNamedConnection("Mock"); + Assert.IsInstanceOf(db.GetAdapter()); + } + + protected override void SetSchema(MockSchemaProvider schemaProvider) + { + } + } +} diff --git a/Simple.Data.BehaviourTest/TransactionTest.cs b/Simple.Data.BehaviourTest/TransactionTest.cs index b1c54b15..416a1982 100644 --- a/Simple.Data.BehaviourTest/TransactionTest.cs +++ b/Simple.Data.BehaviourTest/TransactionTest.cs @@ -152,6 +152,31 @@ public void TestUpdateWithStaticObject() Assert.AreEqual(1, mockDatabase.Parameters[3]); Assert.IsTrue(MockDbTransaction.CommitCalled); } + + [Test] + public void TestBulkUpdateWithStaticObject() + { + var mockDatabase = new MockDatabase(); + dynamic database = CreateDatabase(mockDatabase); + var user = new User + { + Id = 1, + Name = "Steve", + Age = 50 + }; + var users = new[] {user}; + using (var transaction = database.BeginTransaction()) + { + transaction.Users.Update(users); + transaction.Commit(); + } + Assert.AreEqual("update [dbo].[Users] set [Name] = @p1, [Password] = @p2, [Age] = @p3 where [dbo].[Users].[Id] = @p4".ToLowerInvariant(), mockDatabase.Sql.ToLowerInvariant()); + Assert.AreEqual("Steve", mockDatabase.Parameters[0]); + Assert.AreEqual(DBNull.Value, mockDatabase.Parameters[1]); + Assert.AreEqual(50, mockDatabase.Parameters[2]); + Assert.AreEqual(1, mockDatabase.Parameters[3]); + Assert.IsTrue(MockDbTransaction.CommitCalled); + } [Test] public void TestUpdateByWithStaticObject() diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 91e12fa9..c5ba5a81 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -3,6 +3,7 @@ namespace Simple.Data.IntegrationTest { + using System; using System.Collections.Generic; [TestFixture] @@ -10,20 +11,45 @@ public class UpdateTest : DatabaseIntegrationContext { protected override void SetSchema(MockSchemaProvider schemaProvider) { - schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, +// ReSharper disable CoVariantArrayConversion + schemaProvider.SetTables(new object[] { "dbo", "Users", "BASE TABLE" }, + new object[] { "dbo", "UserHistory", "BASE TABLE"}, + new object[] { "dbo", "AnnoyingMaster", "BASE TABLE"}, + new[] { "dbo", "AnnoyingDetail", "BASE TABLE"}, new[] {"dbo", "USER_TABLE", "BASE TABLE"}); schemaProvider.SetColumns(new object[] { "dbo", "Users", "Id", true }, new[] { "dbo", "Users", "Name" }, new[] { "dbo", "Users", "Password" }, new[] { "dbo", "Users", "Age" }, + new[] { "dbo", "UserHistory", "Id" }, + new[] { "dbo", "UserHistory", "UserId" }, + new[] { "dbo", "UserHistory", "LastSeen" }, + new[] { "dbo", "AnnoyingMaster", "Id1" }, + new[] { "dbo", "AnnoyingMaster", "Id2" }, + new[] { "dbo", "AnnoyingMaster", "Text" }, + new[] { "dbo", "AnnoyingDetail", "Id" }, + new[] { "dbo", "AnnoyingDetail", "MasterId1" }, + new[] { "dbo", "AnnoyingDetail", "MasterId2" }, + new[] { "dbo", "AnnoyingDetail", "Value" }, new object[] { "dbo", "USER_TABLE", "ID", true }, new[] { "dbo", "USER_TABLE", "NAME" }, new[] { "dbo", "USER_TABLE", "PASSWORD" }, new[] { "dbo", "USER_TABLE", "AGE" }); schemaProvider.SetPrimaryKeys(new object[] { "dbo", "Users", "Id", 0 }, + new object[] { "dbo", "UserHistory", "Id", 0 }, + new object[] { "dbo", "AnnoyingMaster", "Id1", 0 }, + new object[] { "dbo", "AnnoyingMaster", "Id2", 1 }, + new object[] { "dbo", "AnnoyingDetail", "Id", 0 }, new object[] { "dbo", "USER_TABLE", "ID", 0 }); + + schemaProvider.SetForeignKeys( + new object[] { "FK_UserHistory_User", "dbo", "UserHistory", "UserId", "dbo", "Users", "Id", 0 }, + new object[] { "FK_AnnoyingDetail_AnnoyingMaster", "dbo", "AnnoyingDetail", "MasterId1", "dbo", "AnnoyingMaster", "Id1", 0 }, + new object[] { "FK_AnnoyingDetail_AnnoyingMaster", "dbo", "AnnoyingDetail", "MasterId2", "dbo", "AnnoyingMaster", "Id2", 1 } + ); +// ReSharper restore CoVariantArrayConversion } [Test] @@ -36,6 +62,24 @@ public void TestUpdateWithNamedArguments() Parameter(2).Is(1); } + [Test] + public void TestUpdateWithNamedArgumentsUsingDifferentCase() + { + _db.Users.UpdateById(id: 1, Name: "Steve", Age: 50); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1, [Age] = @p2 where [dbo].[Users].[Id] = @p3"); + Parameter(0).Is("Steve"); + Parameter(1).Is(50); + Parameter(2).Is(1); + } + + [Test] + public void TestUpdateWithNamedArgumentsUsingExpression() + { + _db.Users.UpdateAll(Age: _db.Users.Age + 1); + GeneratedSqlIs("update [dbo].[Users] set [Age] = ([dbo].[Users].[Age] + @p1)"); + Parameter(0).Is(1); + } + [Test] public void TestUpdateWithDynamicObject() { @@ -55,17 +99,18 @@ public void TestUpdateWithDynamicObjectAndOriginalValues() { dynamic newRecord = new SimpleRecord(); newRecord.Id = 1; - newRecord.Name = "Steve"; + newRecord.Name = "Steve-o"; newRecord.Age = 50; dynamic originalRecord = new SimpleRecord(); - originalRecord.Id = 2; + originalRecord.Id = 1; originalRecord.Name = "Steve"; originalRecord.Age = 50; _db.Users.Update(newRecord, originalRecord); - GeneratedSqlIs("update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); - Parameter(0).Is(1); - Parameter(1).Is(2); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1 where ([dbo].[Users].[Id] = @p2 and [dbo].[Users].[Name] = @p3)"); + Parameter(0).Is("Steve-o"); + Parameter(1).Is(1); + Parameter(2).Is("Steve"); } [Test] @@ -73,17 +118,18 @@ public void TestUpdateWithDynamicObjectsAndOriginalValues() { dynamic newRecord = new SimpleRecord(); newRecord.Id = 1; - newRecord.Name = "Steve"; + newRecord.Name = "Steve-o"; newRecord.Age = 50; dynamic originalRecord = new SimpleRecord(); - originalRecord.Id = 2; + originalRecord.Id = 1; originalRecord.Name = "Steve"; originalRecord.Age = 50; _db.Users.Update(new[] {newRecord}, new[] {originalRecord}); - GeneratedSqlIs("update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); - Parameter(0).Is(1); - Parameter(1).Is(2); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1 where ([dbo].[Users].[Id] = @p2 and [dbo].[Users].[Name] = @p3)"); + Parameter(0).Is("Steve-o"); + Parameter(1).Is(1); + Parameter(2).Is("Steve"); } [Test] @@ -160,15 +206,15 @@ public void TestUpdateWithStaticObjectAndOriginalObject() var newUser = new User { Id = 1, - Name = "Steve", + Name = "Steve-o", Age = 50 }; - var originalUser = new User() {Id = 2, Name = "Steve", Age = 50}; + var originalUser = new User {Id = 1, Name = "Steve", Age = 50}; _db.Users.Update(newUser, originalUser); - GeneratedSqlIs( - "update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); - Parameter(0).Is(1); - Parameter(1).Is(2); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1 where ([dbo].[Users].[Id] = @p2 and [dbo].[Users].[Name] = @p3)"); + Parameter(0).Is("Steve-o"); + Parameter(1).Is(1); + Parameter(2).Is("Steve"); } [Test] @@ -177,15 +223,15 @@ public void TestUpdateWithStaticObjectsAndOriginalObject() var newUser = new User { Id = 1, - Name = "Steve", + Name = "Steve-o", Age = 50 }; - var originalUser = new User() { Id = 2, Name = "Steve", Age = 50 }; + var originalUser = new User { Id = 1, Name = "Steve", Age = 50 }; _db.Users.Update(new[] {newUser}, new[] {originalUser}); - GeneratedSqlIs( - "update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); - Parameter(0).Is(1); - Parameter(1).Is(2); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1 where ([dbo].[Users].[Id] = @p2 and [dbo].[Users].[Name] = @p3)"); + Parameter(0).Is("Steve-o"); + Parameter(1).Is(1); + Parameter(2).Is("Steve"); } [Test] @@ -231,7 +277,7 @@ public void TestUpdateWithStaticObjectList() var users = new[] { new User { Id = 2, Name = "Bob", Age = 42 }, - new User { Id = 1, Name = "Steve", Age = 50 }, + new User { Id = 1, Name = "Steve", Age = 50 } }; _db.Users.Update(users); GeneratedSqlIs( @@ -248,7 +294,7 @@ public void TestUpdateByWithStaticObjectList() var users = new[] { new User { Id = 2, Name = "Bob", Age = 42 }, - new User { Id = 1, Name = "Steve", Age = 50 }, + new User { Id = 1, Name = "Steve", Age = 50 } }; _db.Users.UpdateById(users); GeneratedSqlIs( @@ -302,6 +348,28 @@ public void TestUpdateWithCriteria() Parameter(1).Is(30); } + [Test] + public void TestUpdateWithCriteriaWithNaturalJoin() + { + var yearAgo = DateTime.Today.Subtract(TimeSpan.FromDays(365)); + _db.Users.UpdateAll(_db.Users.UserHistory.LastSeen < yearAgo, Name: "Dead User"); + GeneratedSqlIs("update [dbo].[Users] set [Name] = @p1 where [dbo].[Users].[Id] in " + + "(select [dbo].[Users].[Id] from [dbo].[Users] join [dbo].[UserHistory] on ([dbo].[Users].[Id] = [dbo].[UserHistory].[UserId]) where [dbo].[UserHistory].[LastSeen] < @p2)"); + Parameter(0).Is("Dead User"); + Parameter(1).Is(yearAgo); + } + + [Test] + public void TestUpdateWithCriteriaWithNaturalJoinOnCompoundKeyTable() + { + _db.AnnoyingMaster.UpdateAll(_db.AnnoyingMaster.AnnoyingDetail.Value < 42, Text: "Really annoying"); + GeneratedSqlIs("update [dbo].[AnnoyingMaster] set [Text] = @p1 where exists " + + "(select 1 from [dbo].[AnnoyingMaster] [_updatejoin] join [dbo].[AnnoyingDetail] on ([_updatejoin].[Id1] = [dbo].[AnnoyingDetail].[MasterId1] and [_updatejoin].[Id2] = [dbo].[AnnoyingDetail].[MasterId2]) "+ + "where [dbo].[AnnoyingDetail].[Value] < @p2 and ([_updatejoin].[Id1] = [dbo].[AnnoyingMaster].[Id1] and [_updatejoin].[Id2] = [dbo].[AnnoyingMaster].[Id2]))"); + Parameter(0).Is("Really annoying"); + Parameter(1).Is(42); + } + [Test] public void TestUpdateWithCriteriaAndDictionary() { diff --git a/Simple.Data.InMemoryTest/CanAssignArray.cs b/Simple.Data.InMemoryTest/CanAssignArray.cs new file mode 100644 index 00000000..2fb52e1e --- /dev/null +++ b/Simple.Data.InMemoryTest/CanAssignArray.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.InMemoryTest +{ + using NUnit.Framework; + + [TestFixture] + public class CanAssignArray + { + [Test] + public void InsertAndGetWithArrayPropertyShouldWork() + { + var adapter = new InMemoryAdapter(); + adapter.SetKeyColumn("Test", "Id"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Test.Insert(Id: 1, Names: new List {"Alice", "Bob", "Charlie"}); + People record = db.Test.Get(1); + Assert.IsNotNull(record); + Assert.AreEqual(1, record.Id); + Assert.AreEqual("Alice", record.Names[0]); + Assert.AreEqual("Bob", record.Names[1]); + Assert.AreEqual("Charlie", record.Names[2]); + } + } + + class People + { + public int Id { get; set; } + public string[] Names { get; set; } + } +} diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index ae925602..d462583d 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -35,6 +35,32 @@ public void InsertAndFindShouldWork() Assert.AreEqual("Alice", record.Name); } + [Test] + public void InsertAndFindAllShouldWork() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + db.Test.Insert(Id: 2, Name: "Bob"); + List records = db.Test.FindAllById(new[] { 1, 5 }).ToList(); + var record = records.Single(); + Assert.IsNotNull(record); + Assert.AreEqual(1, record.Id); + Assert.AreEqual("Alice", record.Name); + } + + [Test] + public void InsertAndFindAllByStringTypeShouldWork() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + db.Test.Insert(Id: 2, Name: "Bob"); + List records = db.Test.FindAllByName(new[] { "Alice", "Bob" }).ToList(); + Assert.IsNotNull(records); + Assert.AreEqual(2, records.Count); + } + [Test] public void InsertAndFindWithTwoColumnsShouldWork() { @@ -47,6 +73,18 @@ public void InsertAndFindWithTwoColumnsShouldWork() Assert.AreEqual("Alice", record.Name); } + [Test] + public void InsertAndFindWithTwoColumnsUsingNamedParametersShouldWork() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + var record = db.Test.FindBy(Id: 1, Name: "Alice"); + Assert.IsNotNull(record); + Assert.AreEqual(1, record.Id); + Assert.AreEqual("Alice", record.Name); + } + [Test] public void AllShouldReturnAllRecords() { @@ -64,10 +102,10 @@ public void TestFindAllByPartialName() { Database.UseMockAdapter(new InMemoryAdapter()); var db = Database.Open(); - db.Test.Insert(Id: 1, Name: "Alice"); - db.Test.Insert(Id: 2, Name: "Bob"); - db.Test.Insert(Id: 2, Name: "Clive"); - List records = db.Test.FindAll(db.Test.Name.Like("A%")).ToList(); + db.MySchema.Test.Insert(Id: 1, Name: "Alice"); + db.MySchema.Test.Insert(Id: 2, Name: "Bob"); + db.MySchema.Test.Insert(Id: 2, Name: "Clive"); + List records = db.MySchema.Test.FindAll(db.MySchema.Test.Name.Like("A%")).ToList(); Assert.IsNotNull(records); Assert.AreEqual(1, records.Count); } @@ -77,10 +115,10 @@ public void TestFindAllByExcludedPartialName() { Database.UseMockAdapter(new InMemoryAdapter()); var db = Database.Open(); - db.Test.Insert(Id: 1, Name: "Alice"); - db.Test.Insert(Id: 2, Name: "Bob"); - db.Test.Insert(Id: 2, Name: "Clive"); - List records = db.Test.FindAll(db.Test.Name.NotLike("A%")).ToList(); + db.MySchema.Test.Insert(Id: 1, Name: "Alice"); + db.MySchema.Test.Insert(Id: 2, Name: "Bob"); + db.MySchema.Test.Insert(Id: 2, Name: "Clive"); + List records = db.MySchema.Test.FindAll(db.MySchema.Test.Name.NotLike("A%")).ToList(); Assert.IsNotNull(records); Assert.AreEqual(2, records.Count); } @@ -224,7 +262,43 @@ public void TestOrderBy() var records = db.Test.All().OrderByIdDescending().ToList(); Assert.AreEqual(9, records[0].Id); } + + [Test] + public void TestOrderByThenBy() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + + db.Test.Insert(Id: 3, Company: "B", Name: "Alfred"); + db.Test.Insert(Id: 2, Company: "A", Name: "Bob"); + db.Test.Insert(Id: 4, Company: "B", Name: "Steve"); + db.Test.Insert(Id: 1, Company: "A", Name: "Alice"); + + var records = db.Test.All().OrderByCompany().ThenByName().ToList(); + Assert.AreEqual("Alice", records[0].Name); + Assert.AreEqual("Bob", records[1].Name); + Assert.AreEqual("Alfred", records[2].Name); + Assert.AreEqual("Steve", records[3].Name); + } + + [Test] + public void TestOrderByMultiple() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + + db.Test.Insert(Id: 3, Company: "B", Name: "Alfred"); + db.Test.Insert(Id: 2, Company: "A", Name: "Bob"); + db.Test.Insert(Id: 4, Company: "B", Name: "Steve"); + db.Test.Insert(Id: 1, Company: "A", Name: "Alice"); + var records = db.Test.All().OrderBy(db.Test.Company, db.Test.Name).ToList(); + Assert.AreEqual("Alice", records[0].Name); + Assert.AreEqual("Bob", records[1].Name); + Assert.AreEqual("Alfred", records[2].Name); + Assert.AreEqual("Steve", records[3].Name); + } + [Test] public void TestSkip() { @@ -285,6 +359,23 @@ public void TestJoin() Assert.AreEqual(1, customers.Count); } + [Test] + public void TestJoinConfig() + { + var adapter = new InMemoryAdapter(); + adapter.Join.Master("Customer", "ID").Detail("Order", "CustomerID"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Customer.Insert(ID: 1, Name: "NASA"); + db.Customer.Insert(ID: 2, Name: "ACME"); + db.Order.Insert(ID: 1, CustomerID: 1, Date: new DateTime(1997, 1, 12)); + db.Order.Insert(ID: 2, CustomerID: 2, Date: new DateTime(2001, 1, 1)); + + var customers = db.Customer.FindAll(db.Customer.Order.Date < new DateTime(1999, 12, 31)).ToList(); + Assert.IsNotNull(customers); + Assert.AreEqual(1, customers.Count); + } + /// ///A test for Find /// @@ -471,6 +562,36 @@ public void ExistsByNameShouldReturnFalseForNonExistingData() // Assert Assert.AreEqual(false, bobExists); } + + [Test] + public void ExistsByNameParameterShouldReturnTrueForExistingData() + { + // Arrange + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Users.Insert(Id: 1, Name: "Bob", Age: 30); + + // Act + var bobExists = db.Users.ExistsBy(Name: "Bob"); + + // Assert + Assert.AreEqual(true, bobExists); + } + + [Test] + public void ExistsByNameParameterShouldReturnFalseForNonExistingData() + { + // Arrange + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Users.Insert(Id: 1, Name: "Alice", Age: 30); + + // Act + var bobExists = db.Users.ExistsBy(Name: "Bob"); + + // Assert + Assert.AreEqual(false, bobExists); + } [Test] public void BulkInsertWithCallbackShouldWork() @@ -542,5 +663,176 @@ public void InsertAndGetWithTransactionShouldWork() Assert.AreEqual("Alice", record.Name); } } + + [Test] + public void ProcedureShouldWork() + { + var adapter = new InMemoryAdapter(); + adapter.AddFunction("Test", () => new Dictionary { { "Foo", "Bar"}}); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + foreach (var row in db.Test()) + { + Assert.AreEqual("Bar", row.Foo); + } + } + + [Test] + public void ProcedureWithParametersShouldWork() + { + var adapter = new InMemoryAdapter(); + adapter.AddFunction>("Test", (key, value) => new Dictionary { { key, value } }); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + foreach (var row in db.Test("Foo", "Bar")) + { + Assert.AreEqual("Bar", row.Foo); + } + } + + [Test] + public void ProcedureReturningArrayShouldWork() + { + var adapter = new InMemoryAdapter(); + adapter.AddFunction("Test", () => new[] { new Dictionary { { "Foo", "Bar" } } }); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + foreach (var row in db.Test()) + { + Assert.AreEqual("Bar", row.Foo); + } + } + + [Test] + public void ProcedureWithParametersReturningArrayShouldWork() + { + var adapter = new InMemoryAdapter(); + adapter.AddFunction[]>("Test", (key, value) => new IDictionary[] {new Dictionary { { key, value } }}); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + foreach (var row in db.Test("Foo", "Bar")) + { + Assert.AreEqual("Bar", row.Foo); + } + } + + [Test] + public void CanSimulateProcedureWithOutputParameters() + { + const string key = "outparam"; + const string value = "outParamValue"; + var adapter = new InMemoryAdapter(); + adapter.AddFunction("Test", p => + { + p.Add(key, value); + return p; + }); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + var result = db.Test(); + Assert.That(result.OutputValues[key], Is.EqualTo(value)); + } + + [Test] + public void UpsertShouldAddNewRecord() + { + var adapter = new InMemoryAdapter(); + adapter.SetKeyColumn("Test", "Id"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Test.Upsert(Id: 1, SomeValue: "Testing"); + var record = db.Test.Get(1); + Assert.IsNotNull(record); + Assert.AreEqual("Testing", record.SomeValue); + } + + [Test] + public void UpsertShouldUpdateExistingRecord() + { + var adapater = new InMemoryAdapter(); + adapater.SetKeyColumn("Test","Id"); + Database.UseMockAdapter(adapater); + var db = Database.Open(); + db.Test.Upsert(Id: 1, SomeValue: "Testing"); + db.Test.Upsert(Id: 1, SomeValue: "Updated"); + List allRecords = db.Test.All().ToList(); + Assert.IsNotNull(allRecords); + Assert.AreEqual(1,allRecords.Count); + Assert.AreEqual("Updated", allRecords.Single().SomeValue); + } + + [Test] + public void UpsertWithoutDefinedKeyColumnsShouldThrowMeaningfulException() + { + var adapter = new InMemoryAdapter(); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + var exception = Assert.Throws(() => db.Test.Upsert(Id: 1, HasTowel: true)); + Assert.AreEqual("No key columns defined for table \"Test\"", exception.Message); + } + + [Test] + public void JoinTest() + { + Guid masterId = Guid.NewGuid(); + InMemoryAdapter adapter = new InMemoryAdapter(); + adapter.Join.Master("Master", "Id").Detail("Detail", "MasterId"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Master.Insert(Id: masterId); + db.Detail.Insert(Id: Guid.NewGuid(), MasterId: masterId, Box: 999); + // Act + IEnumerable list = db.Detail.All() + .Join(db.Master).On(db.Master.Id == db.Detail.MasterId) + .Select(db.Master.Id, db.Detail.Box) + .Cast(); + // Assert + dynamic detail = list.FirstOrDefault(); + Assert.NotNull(detail); + Assert.That(detail.Id, Is.EqualTo(masterId)); + Assert.That(detail.Box, Is.EqualTo(999)); + } + + [Test] + public void LeftJoinTest() + { + var adapter = new InMemoryAdapter(); + adapter.SetKeyColumn("Events", "Id"); + adapter.SetAutoIncrementColumn("Events", "Id"); + adapter.SetKeyColumn("Doors", "Id"); + adapter.SetAutoIncrementColumn("Doors", "Id"); + adapter.Join.Master("Events", "Id").Detail("Doors", "EventId"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Events.Insert(Id: 1, Code: "CodeMash2013", Name: "CodeMash 2013"); + db.Events.Insert(Id: 2, Code: "SomewhereElse", Name: "Some Other Conf"); + db.Doors.Insert(Id: 1, Code: "F7E08AC9-5E75-417D-A7AA-60E88B5B99AD", EventID: 1); + db.Doors.Insert(Id: 2, Code: "0631C802-2748-4C63-A6D9-CE8C803002EB", EventID: 1); + db.Doors.Insert(Id: 3, Code: "281ED88F-677D-49B9-84FA-4FAE022BBC73", EventID: 1); + db.Doors.Insert(Id: 4, Code: "9DF7E964-1ECE-42E3-8211-1F2BF7054A0D", EventID: 2); + db.Doors.Insert(Id: 5, Code: "9418123D-312A-4E8C-8807-59F0A63F43B9", EventID: 2); + + List actual = db.Doors.FindAll(db.Doors.Events.Code == "CodeMash2013") + .Select(db.Doors.Id, db.Events.Name) + .ToList(); + + Assert.AreEqual(3, actual.Count); + } + + [Test] + public void UpdateWithOriginalValuesRowsUpdatedShouldBeZeroIfNewValueAreSameAsOriginalValue() + { + var adapter = new InMemoryAdapter(); + adapter.SetKeyColumn("Test", "Id"); + Database.UseMockAdapter(adapter); + var db = Database.Open(); + db.Test.Upsert(Id: 1, SomeValue: "Testing"); + var record = db.Test.Get(1); + var record1 = record.Clone(); + + var rowsUpdated = db.Test.Update(record, record1); + + Assert.AreEqual(0, rowsUpdated); + } } } diff --git a/Simple.Data.InMemoryTest/NameResolutionTest.cs b/Simple.Data.InMemoryTest/NameResolutionTest.cs new file mode 100644 index 00000000..2dfcd0fa --- /dev/null +++ b/Simple.Data.InMemoryTest/NameResolutionTest.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.InMemoryTest +{ + using NUnit.Framework; + + [TestFixture] + class NameResolutionTest + { + [Test] + public void InsertAndFindByTableNameResolvesCorrectlyWithHomogenisedStringComparer() + { + var inMemoryAdapter = new InMemoryAdapter(new AdoCompatibleComparer()); + + Database.UseMockAdapter(inMemoryAdapter); + var db = Database.Open(); + + db.CUSTOMER.Insert(ID: 1, NAME: "ACME"); + + var actual = db.Customers.FindById(1); + Assert.IsNotNull(actual); + Assert.AreEqual("ACME", actual.Name); + } + + [Test] + public void UpdateTableNameResolvesCorrectlyWithHomogenisedStringComparer() + { + var inMemoryAdapter = new InMemoryAdapter(new AdoCompatibleComparer()); + + Database.UseMockAdapter(inMemoryAdapter); + var db = Database.Open(); + + db.CUSTOMER.Insert(ID: 1, NAME: "ACME"); + + db.Customers.UpdateById(Id: 1, Name: "ACME Inc."); + var actual = db.Customers.FindById(1); + Assert.IsNotNull(actual); + Assert.AreEqual("ACME Inc.", actual.Name); + } + + [Test] + public void DeleteTableNameResolvesCorrectlyWithHomogenisedStringComparer() + { + var inMemoryAdapter = new InMemoryAdapter(new AdoCompatibleComparer()); + + Database.UseMockAdapter(inMemoryAdapter); + var db = Database.Open(); + + db.CUSTOMER.Insert(ID: 1, NAME: "ACME"); + + var actual = db.Customers.FindById(1); + Assert.IsNotNull(actual); + + db.Customers.DeleteById(1); + actual = db.Customers.FindById(1); + Assert.IsNull(actual); + } + } +} diff --git a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj index 8eb30a7c..9ef4977c 100644 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj @@ -1,4 +1,4 @@ - + Debug @@ -56,8 +56,11 @@ + + + diff --git a/Simple.Data.InMemoryTest/SimpleQueryConversionTests.cs b/Simple.Data.InMemoryTest/SimpleQueryConversionTests.cs new file mode 100644 index 00000000..5f4a9e65 --- /dev/null +++ b/Simple.Data.InMemoryTest/SimpleQueryConversionTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using NUnit.Framework; + +namespace Simple.Data.InMemoryTest +{ + [TestFixture] + public class SimpleQueryConversionTests + { + [Test] + public void ShouldCastToList() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + db.Test.Insert(Id: 2, Name: "Bob"); + List records = db.Test.All(); + Assert.IsNotNull(records); + Assert.AreEqual(2, records.Count); + } + + [Test] + public void ShouldCastToPersonCollection() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + db.Test.Insert(Id: 2, Name: "Bob"); + PersonCollection records = db.Test.All(); + Assert.IsNotNull(records); + Assert.AreEqual(2, records.Count); + } + + [Test] + public void ShouldCastToIEnumerableOfPerson() + { + Database.UseMockAdapter(new InMemoryAdapter()); + var db = Database.Open(); + db.Test.Insert(Id: 1, Name: "Alice"); + db.Test.Insert(Id: 2, Name: "Bob"); + IEnumerable records = db.Test.All(); + Assert.IsNotNull(records); + Assert.AreEqual(2, records.Count()); + } + } + + public class Person + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class PersonCollection : Collection + { + + } +} \ No newline at end of file diff --git a/Simple.Data.Mocking/Ado/MockConnectionProvider.cs b/Simple.Data.Mocking/Ado/MockConnectionProvider.cs index 4d44b1be..a998ad92 100644 --- a/Simple.Data.Mocking/Ado/MockConnectionProvider.cs +++ b/Simple.Data.Mocking/Ado/MockConnectionProvider.cs @@ -6,12 +6,22 @@ namespace Simple.Data.Mocking.Ado { + using System.ComponentModel.Composition; + + [Export("System.Data.Mock", typeof(IConnectionProvider))] public class MockConnectionProvider : IConnectionProvider { private readonly DbConnection _connection; private readonly MockSchemaProvider _mockSchemaProvider; private string _identityFunction; + public MockConnectionProvider() + { + _connection = new MockDbConnection(new MockDatabase()); + _connection.ConnectionString = string.Empty; + _mockSchemaProvider = new MockSchemaProvider(); + } + public MockConnectionProvider(DbConnection connection, MockSchemaProvider mockSchemaProvider, string identityFunction = null) { _connection = connection; @@ -21,7 +31,6 @@ public MockConnectionProvider(DbConnection connection, MockSchemaProvider mockSc public void SetConnectionString(string connectionString) { - throw new NotImplementedException(); } public IDbConnection CreateConnection() diff --git a/Simple.Data.Mocking/Ado/MockDataReader.cs b/Simple.Data.Mocking/Ado/MockDataReader.cs index 8603d327..03790eb8 100644 --- a/Simple.Data.Mocking/Ado/MockDataReader.cs +++ b/Simple.Data.Mocking/Ado/MockDataReader.cs @@ -9,6 +9,7 @@ namespace Simple.Data.Mocking.Ado class MockDataReader : DbDataReader { private readonly IEnumerator _records; + private bool _isClosed = false; public MockDataReader(IEnumerable records) { @@ -21,7 +22,7 @@ public MockDataReader(IEnumerable records) /// 1 public override void Close() { - + _isClosed = true; } /// @@ -81,7 +82,7 @@ public override int Depth /// The is closed. 1 public override bool IsClosed { - get { throw new NotImplementedException(); } + get { return _isClosed; } } /// diff --git a/Simple.Data.Mocking/Ado/MockDbConnection.cs b/Simple.Data.Mocking/Ado/MockDbConnection.cs index de5f2070..10100806 100644 --- a/Simple.Data.Mocking/Ado/MockDbConnection.cs +++ b/Simple.Data.Mocking/Ado/MockDbConnection.cs @@ -52,7 +52,6 @@ public override string ConnectionString get { return _mockDatabase.GetHashCode().ToString(); } set { - throw new NotImplementedException(); } } diff --git a/Simple.Data.Mocking/Ado/MockSchemaProvider.cs b/Simple.Data.Mocking/Ado/MockSchemaProvider.cs index 9eab8ee4..d3496bec 100644 --- a/Simple.Data.Mocking/Ado/MockSchemaProvider.cs +++ b/Simple.Data.Mocking/Ado/MockSchemaProvider.cs @@ -145,7 +145,7 @@ public string QuoteObjectName(string unquotedName) public string NameParameter(string baseName) { if (baseName == null) throw new ArgumentNullException("baseName"); - if (baseName.Length == 0) throw new ArgumentException("Base name must be provided"); + if (baseName.Length == 0) throw new ArgumentException("Base name must be provided", "baseName"); return (baseName.StartsWith("@")) ? baseName : "@" + baseName; } diff --git a/Simple.Data.Mocking/NugetPack.cmd b/Simple.Data.Mocking/NugetPack.cmd index c94ada3c..0ac3a780 100644 --- a/Simple.Data.Mocking/NugetPack.cmd +++ b/Simple.Data.Mocking/NugetPack.cmd @@ -1 +1 @@ -nuget pack -sym Simple.Data.Mocking.csproj -Properties Configuration=Release +nuget pack -sym Simple.Data.Mocking.csproj -Properties Configuration=Release;Platform=AnyCPU -Build diff --git a/Simple.Data.Mocking/Simple.Data.Mocking.csproj b/Simple.Data.Mocking/Simple.Data.Mocking.csproj index 5a137baf..a128b3f1 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.csproj +++ b/Simple.Data.Mocking/Simple.Data.Mocking.csproj @@ -1,4 +1,4 @@ - + Debug @@ -39,6 +39,7 @@ + diff --git a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 3501cfe8..78ab5242 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 1.0.0-beta1 + 0.18.3.1 Mark Rendle Mark Rendle XML-based Mocking adapter for the Simple.Data data access library. @@ -12,7 +12,7 @@ database data .net40 en-us - + diff --git a/Simple.Data.Mocking/XmlMockAdapter.cs b/Simple.Data.Mocking/XmlMockAdapter.cs index b613364c..36ee72c3 100644 --- a/Simple.Data.Mocking/XmlMockAdapter.cs +++ b/Simple.Data.Mocking/XmlMockAdapter.cs @@ -144,7 +144,7 @@ private IEnumerable> FindAll(string tableName) private XElement GetTableElement(string tableName) { XElement tableElement = Data.Element(tableName); - if (tableElement == null) throw new UnresolvableObjectException(tableName); + if (tableElement == null) throw new UnresolvableObjectException(tableName, string.Format("Table '{0}' not found.", tableName)); return tableElement; } diff --git a/Simple.Data.SqlCe40/NugetPack.cmd b/Simple.Data.SqlCe40/NugetPack.cmd index b659f3e0..f49efa0a 100644 --- a/Simple.Data.SqlCe40/NugetPack.cmd +++ b/Simple.Data.SqlCe40/NugetPack.cmd @@ -1 +1 @@ -nuget pack -sym Simple.Data.SqlCe40.csproj -Properties Configuration=Release +nuget pack -sym Simple.Data.SqlCe40.nuspec diff --git a/Simple.Data.SqlCe40/Properties/AssemblyInfo.cs b/Simple.Data.SqlCe40/Properties/AssemblyInfo.cs index 1adf16f5..e74ea6b9 100644 --- a/Simple.Data.SqlCe40/Properties/AssemblyInfo.cs +++ b/Simple.Data.SqlCe40/Properties/AssemblyInfo.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; +using Simple.Data.SqlCe40; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -16,3 +17,5 @@ [assembly: InternalsVisibleTo("Simple.Data.SqlCe40Test")] [assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)] [assembly: AllowPartiallyTrustedCallers] + +[assembly: SqlCe40Provider] \ No newline at end of file diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj index 47df1a6e..4d53348a 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj @@ -56,6 +56,7 @@ Resources.resx + diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 7d805129..e405cd3b 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -1,18 +1,21 @@ - - + + Simple.Data.SqlCompact40 - 1.0.0-beta1 + 0.18.3.1 Mark Rendle Mark Rendle - SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. - http://github.com/markrendle/Simple.Data http://www.opensource.org/licenses/mit-license.php + http://github.com/markrendle/Simple.Data http://simplefx.org/images/simpledata100x100.png + false + SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - en-us - + - + + + + \ No newline at end of file diff --git a/Simple.Data.SqlCe40/SqlCe40ProviderAttribute.cs b/Simple.Data.SqlCe40/SqlCe40ProviderAttribute.cs new file mode 100644 index 00000000..8fdf65a9 --- /dev/null +++ b/Simple.Data.SqlCe40/SqlCe40ProviderAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Data.SqlServerCe; +using Simple.Data.Ado; + +namespace Simple.Data.SqlCe40 +{ + public class SqlCe40ProviderAttribute : ProviderAssemblyAttributeBase + { + public SqlCe40ProviderAttribute() : base("sdf", "System.Data.SqlServerCe", "System.Data.SqlServerCe.4.0") + { + } + + public override bool TryGetProvider(string connectionString, out IConnectionProvider provider, out Exception exception) + { + try + { + var _ = new SqlCeConnectionStringBuilder(connectionString); + provider = new SqlCe40ConnectionProvider(); + provider.SetConnectionString(connectionString); + exception = null; + return true; + } + catch (Exception ex) + { + exception = ex; + provider = null; + return false; + } + } + } +} \ No newline at end of file diff --git a/Simple.Data.SqlCe40/SqlCe40QueryPager.cs b/Simple.Data.SqlCe40/SqlCe40QueryPager.cs index 28ed1e3a..33ad3c09 100644 --- a/Simple.Data.SqlCe40/SqlCe40QueryPager.cs +++ b/Simple.Data.SqlCe40/SqlCe40QueryPager.cs @@ -1,34 +1,34 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Simple.Data.Ado; - -namespace Simple.Data.SqlCe40 -{ - [Export(typeof(IQueryPager))] - public class SqlCe40QueryPager : IQueryPager - { - private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase); - private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*", RegexOptions.IgnoreCase); - - public IEnumerable ApplyLimit(string sql, int take) - { - yield return SelectMatch.Replace(sql, match => match.Value + " TOP(" + take + ") "); - } - - public IEnumerable ApplyPaging(string sql, int skip, int take) - { - if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0) - { - var match = ColumnExtract.Match(sql); - var columns = match.Groups[1].Value.Trim(); - sql += " ORDER BY " + columns.Split(',').First().Trim(); - } - - yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take); - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Simple.Data.Ado; + +namespace Simple.Data.SqlCe40 +{ + [Export(typeof(IQueryPager))] + public class SqlCe40QueryPager : IQueryPager + { + private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase); + private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*(DISTINCT)?", RegexOptions.IgnoreCase); + + public IEnumerable ApplyLimit(string sql, int take) + { + yield return SelectMatch.Replace(sql, match => match.Value + " TOP(" + take + ") "); + } + + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) + { + if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0) + { + var match = ColumnExtract.Match(sql); + var columns = match.Groups[1].Value.Trim(); + sql += " ORDER BY " + columns.Split(',').First().Trim(); + } + + yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take); + } + } +} diff --git a/Simple.Data.SqlCe40/SqlCe40SchemaProvider.cs b/Simple.Data.SqlCe40/SqlCe40SchemaProvider.cs index 7c37fcaf..0e8944a6 100644 --- a/Simple.Data.SqlCe40/SqlCe40SchemaProvider.cs +++ b/Simple.Data.SqlCe40/SqlCe40SchemaProvider.cs @@ -112,7 +112,7 @@ public string QuoteObjectName(string unquotedName) public string NameParameter(string baseName) { if (baseName == null) throw new ArgumentNullException("baseName"); - if (baseName.Length == 0) throw new ArgumentException("Base name must be provided"); + if (baseName.Length == 0) throw new ArgumentException("Base name must be provided", "baseName"); return (baseName.StartsWith("@")) ? baseName : "@" + baseName; } diff --git a/Simple.Data.SqlCe40Test/ConnectionProviderTest.cs b/Simple.Data.SqlCe40Test/ConnectionProviderTest.cs index 39ee8177..1bef114f 100644 --- a/Simple.Data.SqlCe40Test/ConnectionProviderTest.cs +++ b/Simple.Data.SqlCe40Test/ConnectionProviderTest.cs @@ -5,6 +5,7 @@ namespace Simple.Data.SqlCe40Test { + using System.Diagnostics; using Ado; using NUnit.Framework; using SqlCe40; @@ -27,4 +28,15 @@ public void SqlCeDoesNotSupportCompoundStatements() Assert.IsFalse(target.SupportsCompoundStatements); } } + + [SetUpFixture] + public class Setup + { + [SetUp] + public void ForceLoadOfSimpleDataSqlCe40() + { + var provider = new SqlCe40.SqlCe40ConnectionProvider(); + Trace.Write("Loaded provider."); + } + } } diff --git a/Simple.Data.SqlCe40Test/Northwind.sdf b/Simple.Data.SqlCe40Test/Northwind.sdf index a68bee7e..5b8ef0d3 100644 Binary files a/Simple.Data.SqlCe40Test/Northwind.sdf and b/Simple.Data.SqlCe40Test/Northwind.sdf differ diff --git a/Simple.Data.SqlCe40Test/NorthwindTests.cs b/Simple.Data.SqlCe40Test/NorthwindTests.cs index d6c3333a..63878e95 100644 --- a/Simple.Data.SqlCe40Test/NorthwindTests.cs +++ b/Simple.Data.SqlCe40Test/NorthwindTests.cs @@ -44,5 +44,20 @@ public void DistinctShouldReturnDistinctList() Assert.AreEqual(countries.Distinct().Count(), countries.Count); } + + [Test] + public void NestedFindAllIssue167() + { + var db = Database.OpenFile(DatabasePath); + + List customers = db.Customers.All().ToList(); + + foreach (var customer in customers) + { + customer.Orders = db.Orders.FindAllByCustomerID(customer.CustomerID).ToList(); + } + + Assert.Pass(); + } } } diff --git a/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs b/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs index a38dbcd8..37a83103 100644 --- a/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs +++ b/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs @@ -1,51 +1,63 @@ -using System.Text.RegularExpressions; -using NUnit.Framework; -using Simple.Data.SqlCe40; -using System.Linq; - -namespace Simple.Data.SqlCe40Test -{ - [TestFixture] - public class SqlCe40QueryPagerTest - { - static readonly Regex Normalize = new Regex(@"\s+", RegexOptions.Multiline); - - [Test] - public void ShouldApplyLimitUsingTop() - { - var sql = "select a,b,c from d where a = 1 order by c"; - var expected = new[] { "select top(5) a,b,c from d where a = 1 order by c" }; - - var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - - [Test] - public void ShouldApplyPagingUsingOrderBy() - { - var sql = "select a,b,c from d where a = 1 order by c"; - var expected = new[]{ - "select a,b,c from d where a = 1 order by c offset 5 rows fetch next 10 rows only"}; - - var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, 5, 10); - var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - - [Test] - public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() - { - var sql = "select a,b,c from d where a = 1"; - var expected = new[]{ - "select a,b,c from d where a = 1 order by a offset 10 rows fetch next 20 rows only"}; - - var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, 10, 20); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - } -} +using System.Text.RegularExpressions; +using NUnit.Framework; +using Simple.Data.SqlCe40; +using System.Linq; + +namespace Simple.Data.SqlCe40Test +{ + [TestFixture] + public class SqlCe40QueryPagerTest + { + static readonly Regex Normalize = new Regex(@"\s+", RegexOptions.Multiline); + + [Test] + public void ShouldApplyLimitUsingTop() + { + var sql = "select a,b,c from d where a = 1 order by c"; + var expected = new[] { "select top(5) a,b,c from d where a = 1 order by c" }; + + var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + + [Test] + public void ShouldApplyLimitUsingTopWithDistinct() + { + var sql = "select distinct a,b,c from d where a = 1 order by c"; + var expected = new[] { "select distinct top(5) a,b,c from d where a = 1 order by c" }; + + var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + + [Test] + public void ShouldApplyPagingUsingOrderBy() + { + var sql = "select a,b,c from d where a = 1 order by c"; + var expected = new[]{ + "select a,b,c from d where a = 1 order by c offset 5 rows fetch next 10 rows only"}; + + var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 5, 10); + var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + + [Test] + public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() + { + var sql = "select a,b,c from d where a = 1"; + var expected = new[]{ + "select a,b,c from d where a = 1 order by a offset 10 rows fetch next 20 rows only"}; + + var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 10, 20); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + } +} diff --git a/Simple.Data.SqlServer/DbTypeLookup.cs b/Simple.Data.SqlServer/DbTypeLookup.cs index bac85c50..69f12a7e 100644 --- a/Simple.Data.SqlServer/DbTypeLookup.cs +++ b/Simple.Data.SqlServer/DbTypeLookup.cs @@ -37,11 +37,14 @@ internal static class DbTypeLookup {"varchar", SqlDbType.VarChar}, {"binary", SqlDbType.Binary}, {"char", SqlDbType.Char}, - {"timestamp", SqlDbType.Binary}, + {"timestamp", SqlDbType.Timestamp}, {"nvarchar", SqlDbType.NVarChar}, {"nchar", SqlDbType.NChar}, {"xml", SqlDbType.Xml}, {"image", SqlDbType.Image}, + {"geography", SqlDbType.Udt}, + {"geometry", SqlDbType.Udt}, + {"hierarchyid", SqlDbType.Udt}, }; public static SqlDbType GetSqlDbType(string typeName) diff --git a/Simple.Data.SqlServer/NugetPack.cmd b/Simple.Data.SqlServer/NugetPack.cmd index 9cc540cc..91d020e4 100644 --- a/Simple.Data.SqlServer/NugetPack.cmd +++ b/Simple.Data.SqlServer/NugetPack.cmd @@ -1 +1 @@ -nuget pack -sym Simple.Data.SqlServer.csproj -Properties Configuration=Release +nuget pack -sym Simple.Data.SqlServer.csproj -Properties Configuration=Release;Platform=AnyCPU -Build diff --git a/Simple.Data.SqlServer/Properties/AssemblyInfo.cs b/Simple.Data.SqlServer/Properties/AssemblyInfo.cs index 5df0df13..07e92397 100644 --- a/Simple.Data.SqlServer/Properties/AssemblyInfo.cs +++ b/Simple.Data.SqlServer/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Simple.Data.SqlServer; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -10,3 +11,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("66a1ced9-a6cf-4c5c-8fc1-2f40443ac6bb")] + +[assembly: SqlServerProvider] \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj index 4ff7440f..87873058 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj @@ -59,14 +59,18 @@ Resources.resx + + + + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 429cdef7..a2ec150c 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 1.0.0-beta1 + 0.18.3.1 Mark Rendle Mark Rendle SQL Server ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/SqlBulkInserter.cs b/Simple.Data.SqlServer/SqlBulkInserter.cs index a3dd5995..7738213e 100644 --- a/Simple.Data.SqlServer/SqlBulkInserter.cs +++ b/Simple.Data.SqlServer/SqlBulkInserter.cs @@ -25,31 +25,34 @@ public IEnumerable> Insert(AdoAdapter adapter, strin SqlConnection connection; SqlBulkCopy bulkCopy; + var sqlBulkCopyOptions = BuildBulkCopyOptions(adapter); if (transaction != null) { connection = (SqlConnection) transaction.Connection; - bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, (SqlTransaction)transaction); + bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, (SqlTransaction)transaction); } else { connection = (SqlConnection) adapter.CreateConnection(); - bulkCopy = new SqlBulkCopy(connection); + bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, null); } - bulkCopy.DestinationTableName = adapter.GetSchema().FindTable(tableName).ActualName; + bulkCopy.DestinationTableName = adapter.GetSchema().FindTable(tableName).QualifiedName; using (connection.MaybeDisposable()) using (bulkCopy) { connection.OpenIfClosed(); - foreach (var record in data) + + var dataList = data.ToList(); + foreach (var record in dataList) { if (count == 0) { - dataTable = CreateDataTable(adapter, tableName, record.Keys, bulkCopy); + dataTable = CreateDataTable(adapter, tableName, dataList.SelectMany(r => r.Keys).Distinct(), bulkCopy); } - dataTable.Rows.Add(record.Values.ToArray()); + AddRow(dataTable, record); if (++count%5000 == 0) { @@ -67,7 +70,21 @@ public IEnumerable> Insert(AdoAdapter adapter, strin return null; } - private DataTable CreateDataTable(AdoAdapter adapter, string tableName, ICollection keys, SqlBulkCopy bulkCopy) + private SqlBulkCopyOptions BuildBulkCopyOptions(AdoAdapter adapter) + { + var options = SqlBulkCopyOptions.Default; + + if (adapter.AdoOptions != null) + { + options |= (adapter.AdoOptions.FireTriggersOnBulkInserts + ? SqlBulkCopyOptions.FireTriggers + : SqlBulkCopyOptions.Default); + } + + return options; + } + + private DataTable CreateDataTable(AdoAdapter adapter, string tableName, IEnumerable keys, SqlBulkCopy bulkCopy) { var table = adapter.GetSchema().FindTable(tableName); var dataTable = new DataTable(table.ActualName); @@ -89,5 +106,17 @@ private DataTable CreateDataTable(AdoAdapter adapter, string tableName, ICollect return dataTable; } + + private void AddRow(DataTable dataTable, IDictionary record) + { + var dataRow = dataTable.NewRow(); + foreach (DataColumn column in dataTable.Columns) + { + if (record.ContainsKey(column.ColumnName)) + dataRow[column] = record[column.ColumnName]; + } + dataTable.Rows.Add(dataRow); + } + } } diff --git a/Simple.Data.SqlServer/SqlColumn.cs b/Simple.Data.SqlServer/SqlColumn.cs index ddef1e28..9df17702 100644 --- a/Simple.Data.SqlServer/SqlColumn.cs +++ b/Simple.Data.SqlServer/SqlColumn.cs @@ -39,5 +39,10 @@ public override bool IsBinary SqlDbType == SqlDbType.VarBinary; } } + + public override bool IsWriteable + { + get { return (!IsIdentity) && SqlDbType != SqlDbType.Timestamp; } + } } } \ No newline at end of file diff --git a/Simple.Data.SqlServer/SqlCommandOptimizer.cs b/Simple.Data.SqlServer/SqlCommandOptimizer.cs index 4fedc4d2..f23f6f09 100644 --- a/Simple.Data.SqlServer/SqlCommandOptimizer.cs +++ b/Simple.Data.SqlServer/SqlCommandOptimizer.cs @@ -1,23 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Simple.Data.SqlServer -{ - using System.ComponentModel.Composition; - using System.Data.SqlClient; - using System.Text.RegularExpressions; - using Ado; - - [Export(typeof(CommandOptimizer))] - public class SqlCommandOptimizer : CommandOptimizer - { - public override System.Data.IDbCommand OptimizeFindOne(System.Data.IDbCommand command) - { - command.CommandText = Regex.Replace(command.CommandText, "^SELECT ", "SELECT TOP 1 ", - RegexOptions.IgnoreCase); - return command; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.SqlServer +{ + using System.ComponentModel.Composition; + using System.Data.SqlClient; + using System.Text.RegularExpressions; + using Ado; + + [Export(typeof(CommandOptimizer))] + public class SqlCommandOptimizer : CommandOptimizer + { + public override System.Data.IDbCommand OptimizeFindOne(System.Data.IDbCommand command) + { + command.CommandText = Regex.Replace(command.CommandText, @"^SELECT\s*(DISTINCT)?", "SELECT $1 TOP 1 ", + RegexOptions.IgnoreCase); + return command; + } + } +} diff --git a/Simple.Data.SqlServer/SqlCustomInserter.cs b/Simple.Data.SqlServer/SqlCustomInserter.cs new file mode 100644 index 00000000..c27bb0a1 --- /dev/null +++ b/Simple.Data.SqlServer/SqlCustomInserter.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Data; +using System.Linq; +using System.Text; +using Simple.Data.Ado; +using Simple.Data.Ado.Schema; + +namespace Simple.Data.SqlServer +{ + [Export(typeof(ICustomInserter))] + public class SqlCustomInserter : ICustomInserter + { + public IDictionary Insert(AdoAdapter adapter, string tableName, IDictionary data, IDbTransaction transaction = null, bool resultRequired = false) + { + var table = adapter.GetSchema().FindTable(tableName); + var dataDictionary = BuildDataDictionary(adapter, data, table); + + string columnList = dataDictionary.Keys.Select(c => c.QuotedName).Aggregate((agg, next) => agg + "," + next); + string valueList = dataDictionary.Keys.Select(s => "?").Aggregate((agg, next) => agg + "," + next); + + var insertSql = new StringBuilder(); + bool identityInsert = adapter.AdoOptions != null && adapter.AdoOptions.IdentityInsert; + if (identityInsert) + { + insertSql.AppendFormat("SET IDENTITY_INSERT {0} ON; ", table.QualifiedName); + } + insertSql.AppendFormat("INSERT INTO {0} ({1})", table.QualifiedName, columnList); + insertSql.AppendFormat(" VALUES ({0})", valueList); + + if (identityInsert) + { + insertSql.AppendFormat("; SET IDENTITY_INSERT {0} OFF; ", table.QualifiedName); + } + + if (resultRequired) + { + var identityColumn = table.Columns.FirstOrDefault(c => c.IsIdentity); + if (identityColumn != null) + { + insertSql.AppendFormat(" SELECT * FROM {0} WHERE {1} = SCOPE_IDENTITY()", table.QualifiedName, + identityColumn.QuotedName); + return ExecuteSingletonQuery(adapter, insertSql.ToString(), dataDictionary.Keys, + dataDictionary.Values, transaction); + } + } + Execute(adapter, insertSql.ToString(), dataDictionary.Keys, dataDictionary.Values, transaction); + return null; + } + + private static Dictionary BuildDataDictionary(AdoAdapter adapter, IDictionary data, Table table) + { + Func columnFilter; + if (adapter.AdoOptions != null && adapter.AdoOptions.IdentityInsert) + { + columnFilter = + key => + { + Column column; + if (table.TryFindColumn(key, out column)) + { + return column.IsWriteable || column.IsIdentity; + } + return false; + }; + } + else + { + columnFilter = key => table.HasColumn(key) && table.FindColumn(key).IsWriteable; + } + var dataDictionary = data.Where(kvp => columnFilter(kvp.Key)) + .ToDictionary(kvp => table.FindColumn(kvp.Key), kvp => kvp.Value); + return dataDictionary; + } + + internal IDictionary ExecuteSingletonQuery(AdoAdapter adapter, string sql, IEnumerable columns, IEnumerable values, IDbTransaction transaction) + { + if (transaction != null) + { + var command = new CommandHelper(adapter).CreateInsert(transaction.Connection, sql, columns, values.ToArray()); + command.Transaction = transaction; + return TryExecuteSingletonQuery(command); + } + + var connection = adapter.CreateConnection(); + using (connection.MaybeDisposable()) + { + using (var command = new CommandHelper(adapter).CreateInsert(connection, sql, columns, values.ToArray())) + { + connection.OpenIfClosed(); + return TryExecuteSingletonQuery(command); + } + } + } + + private static IDictionary TryExecuteSingletonQuery(IDbCommand command) + { + using (var reader = command.TryExecuteReader()) + { + if (reader.Read()) + { + return reader.ToDictionary(); + } + } + + return null; + } + + internal int Execute(AdoAdapter adapter, string sql, IEnumerable columns, IEnumerable values, IDbTransaction transaction) + { + if (transaction != null) + { + var command = new CommandHelper(adapter).CreateInsert(transaction.Connection, sql, columns, values.ToArray()); + command.Transaction = transaction; + return command.TryExecuteNonQuery(); + } + var connection = adapter.CreateConnection(); + using (connection.MaybeDisposable()) + { + using (var command = new CommandHelper(adapter).CreateInsert(connection, sql, columns, values.ToArray())) + { + connection.OpenIfClosed(); + return command.TryExecuteNonQuery(); + } + } + } + } +} \ No newline at end of file diff --git a/Simple.Data.SqlServer/SqlCustomQueryBuilder.cs b/Simple.Data.SqlServer/SqlCustomQueryBuilder.cs new file mode 100644 index 00000000..4e6c3c99 --- /dev/null +++ b/Simple.Data.SqlServer/SqlCustomQueryBuilder.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Simple.Data.Ado; + +namespace Simple.Data.SqlServer +{ + + [Export(typeof(ICustomQueryBuilder))] + public class SqlCustomQueryBuilder : ICustomQueryBuilder + { + public ICommandBuilder Build(AdoAdapter adapter, int bulkIndex, SimpleQuery query, out IEnumerable unhandledClauses) + { + return new SqlQueryBuilder(adapter, bulkIndex).Build(query, out unhandledClauses); + } + } +} diff --git a/Simple.Data.SqlServer/SqlDbTypeEx.cs b/Simple.Data.SqlServer/SqlDbTypeEx.cs index 117fddbb..5a5feef4 100644 --- a/Simple.Data.SqlServer/SqlDbTypeEx.cs +++ b/Simple.Data.SqlServer/SqlDbTypeEx.cs @@ -18,7 +18,7 @@ static class SqlDbTypeEx { SqlDbType.Date, typeof(DateTime)}, { SqlDbType.DateTime, typeof(DateTime)}, { SqlDbType.DateTime2, typeof(DateTime)}, - { SqlDbType.DateTimeOffset, typeof(DateTime)}, + { SqlDbType.DateTimeOffset, typeof(DateTimeOffset)}, { SqlDbType.Decimal, typeof(decimal)}, { SqlDbType.Float, typeof(double)}, { SqlDbType.Image, typeof(byte[])}, diff --git a/Simple.Data.SqlServer/SqlQueryBuilder.cs b/Simple.Data.SqlServer/SqlQueryBuilder.cs new file mode 100644 index 00000000..7ea9fee2 --- /dev/null +++ b/Simple.Data.SqlServer/SqlQueryBuilder.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Simple.Data.Ado; + +namespace Simple.Data.SqlServer +{ + public class SqlQueryBuilder : QueryBuilderBase + { + private List _unhandledClauses; + + public SqlQueryBuilder(AdoAdapter adapter, int bulkIndex) + : base(adapter, bulkIndex) + { + } + + public override ICommandBuilder Build(SimpleQuery query, out IEnumerable unhandledClauses) + { + _unhandledClauses = new List(); + SetQueryContext(query); + + HandleJoins(); + HandleQueryCriteria(); + HandleGrouping(); + HandleHavingCriteria(); + HandleOrderBy(); + + unhandledClauses = _unhandledClauses; + return _commandBuilder; + } + + protected override string GetSelectClause(ObjectName tableName) + { + var select = base.GetSelectClause(tableName); + var forUpdateClause = _query.Clauses.OfType().FirstOrDefault(); + if (forUpdateClause != null) + { + var forUpdate = " WITH (UPDLOCK, ROWLOCK"; + if (forUpdateClause.SkipLockedRows) + { + forUpdate += ", READPAST"; + } + forUpdate += ")"; + select += forUpdate; + } + return select; + } + } +} diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 057facbc..b40a971f 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -1,74 +1,102 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Simple.Data.Ado; - -namespace Simple.Data.SqlServer -{ - [Export(typeof(IQueryPager))] - public class SqlQueryPager : IQueryPager - { - private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase); - private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*", RegexOptions.IgnoreCase); - - public IEnumerable ApplyLimit(string sql, int take) - { - yield return SelectMatch.Replace(sql, match => match.Value + " TOP " + take + " "); - } - - public IEnumerable ApplyPaging(string sql, int skip, int take) - { - var builder = new StringBuilder("WITH __Data AS (SELECT "); - - var match = ColumnExtract.Match(sql); - var columns = match.Groups[1].Value.Trim(); - var fromEtc = match.Groups[2].Value.Trim(); - - builder.Append(columns); - - var orderBy = ExtractOrderBy(columns, ref fromEtc); - - builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy); - builder.AppendLine(); - builder.Append(fromEtc); - builder.AppendLine(")"); - builder.AppendFormat("SELECT {0} FROM __Data WHERE [_#_] BETWEEN {1} AND {2}", DequalifyColumns(columns), - skip + 1, skip + take); - - yield return builder.ToString(); - } - - private static string DequalifyColumns(string original) - { - var q = from part in original.Split(',') - select part.Substring(Math.Max(part.LastIndexOf('.') + 1, part.LastIndexOf('['))); - return string.Join(",", q); - } - - private static string ExtractOrderBy(string columns, ref string fromEtc) - { - string orderBy; - int index = fromEtc.IndexOf("ORDER BY", StringComparison.InvariantCultureIgnoreCase); - if (index > -1) - { - orderBy = fromEtc.Substring(index).Trim(); - fromEtc = fromEtc.Remove(index).Trim(); - } - else - { - orderBy = "ORDER BY " + columns.Split(',').First().Trim(); - - var aliasIndex = orderBy.IndexOf(" AS [", StringComparison.InvariantCultureIgnoreCase); - - if (aliasIndex > -1) - { - orderBy = orderBy.Substring(0, aliasIndex); - } - } - return orderBy; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Simple.Data.Ado; + +namespace Simple.Data.SqlServer +{ + [Export(typeof(IQueryPager))] + public class SqlQueryPager : IQueryPager + { + private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(\sFROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase); + private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*(DISTINCT)?", RegexOptions.IgnoreCase); + private static readonly Regex LeftJoinMatch = new Regex(@"\sLEFT JOIN .*? ON \(.*?\)", RegexOptions.Multiline | RegexOptions.IgnoreCase); + + public IEnumerable ApplyLimit(string sql, int take) + { + yield return SelectMatch.Replace(sql, match => match.Value + " TOP " + take + " "); + } + + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) + { + if (keys == null || keys.Length == 0) + { + throw new AdoAdapterException("Cannot apply paging to table with no primary key."); + } + + sql = sql.Replace(Environment.NewLine, " "); + var builder = new StringBuilder("WITH __Data AS (SELECT "); + + var match = ColumnExtract.Match(sql); + var columns = match.Groups[1].Value.Trim(); + var fromEtc = match.Groups[2].Value.Trim(); + + builder.Append(string.Join(",", keys)); + + var orderBy = ExtractOrderBy(columns, keys, ref fromEtc); + + builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy); + builder.AppendLine(); + builder.Append(LeftJoinMatch.Replace(fromEtc, "")); + builder.AppendLine(")"); + builder.AppendFormat("SELECT {0} FROM __Data ", columns); + builder.AppendFormat("JOIN {0} ON ", + keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase))); + if (keys.Length > 1) + builder.AppendFormat(string.Join(" AND ", keys.Select(MakeDataJoin))); + else + builder.AppendFormat(MakeDataJoin(keys[0])); + var groupBy = ExtractGroupBy(ref fromEtc); + var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); + builder.Append(rest); + + builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take); + if (!string.IsNullOrWhiteSpace(groupBy)) + builder.AppendFormat(" {0}", groupBy); + yield return builder.ToString(); + } + + private static string MakeDataJoin(string key) + { + return key + " = __Data" + key.Substring(key.LastIndexOf(".", StringComparison.OrdinalIgnoreCase)); + } + + private static string DequalifyColumns(string original) + { + var q = from part in original.Split(',') + select part.Substring(Math.Max(part.LastIndexOf('.') + 1, part.LastIndexOf('['))); + return string.Join(",", q); + } + + private static string ExtractOrderBy(string columns, string[] keys, ref string fromEtc) + { + string orderBy; + int index = fromEtc.IndexOf("ORDER BY", StringComparison.InvariantCultureIgnoreCase); + if (index > -1) + { + orderBy = fromEtc.Substring(index).Trim(); + fromEtc = fromEtc.Remove(index).Trim(); + } + else + { + orderBy = "ORDER BY " + string.Join(", ", keys); + } + return orderBy; + } + + private static string ExtractGroupBy(ref string fromEtc) + { + string groupBy = string.Empty; + int index = fromEtc.IndexOf("GROUP BY", StringComparison.InvariantCultureIgnoreCase); + if (index > -1) + { + groupBy = fromEtc.Substring(index).Trim(); + fromEtc = fromEtc.Remove(index).Trim(); + } + return groupBy; + } + } +} diff --git a/Simple.Data.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index 3570f6e0..9d022670 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -41,11 +41,13 @@ public IEnumerable GetColumns(Table table) return cols.AsEnumerable().Select(row => SchemaRowToColumn(table, row)); } - - private static Column SchemaRowToColumn(Table table, DataRow row) { - var sqlDbType = DbTypeFromInformationSchemaTypeName((string)row["type_name"]); + SqlDbType sqlDbType = SqlDbType.Udt; + + if (!row.IsNull("type_name")) + sqlDbType = DbTypeFromInformationSchemaTypeName((string)row["type_name"]); + var size = (short)row["max_length"]; switch (sqlDbType) { @@ -107,7 +109,12 @@ public IEnumerable GetParameters(Procedure storedProcedure) public Key GetPrimaryKey(Table table) { if (table == null) throw new ArgumentNullException("table"); - return new Key(GetPrimaryKeys(table.ActualName).AsEnumerable() + var primaryKeys = GetPrimaryKeys(table.ActualName); + if (primaryKeys == null) + { + return new Key(Enumerable.Empty()); + } + return new Key(primaryKeys.AsEnumerable() .Where( row => row["TABLE_SCHEMA"].ToString() == table.Schema && row["TABLE_NAME"].ToString() == table.ActualName) @@ -143,7 +150,7 @@ public string QuoteObjectName(string unquotedName) public string NameParameter(string baseName) { if (baseName == null) throw new ArgumentNullException("baseName"); - if (baseName.Length == 0) throw new ArgumentException("Base name must be provided"); + if (baseName.Length == 0) throw new ArgumentException("Base name must be provided", "baseName"); return (baseName.StartsWith("@")) ? baseName : "@" + baseName; } @@ -154,11 +161,11 @@ public Type DataTypeToClrType(string dataType) private DataTable GetColumnsDataTable(Table table) { - var columnSelect = - string.Format( - "SELECT name, is_identity, type_name(system_type_id) as type_name, max_length from sys.columns where object_id = object_id('{0}.{1}', 'TABLE') or object_id = object_id('{0}.{1}', 'VIEW') order by column_id", - table.Schema, table.ActualName); - return SelectToDataTable(columnSelect); + const string columnSelect = @"SELECT name, is_identity, type_name(system_type_id) as type_name, max_length from sys.columns +where object_id = object_id(@tableName, 'TABLE') or object_id = object_id(@tableName, 'VIEW') order by column_id"; + var @tableName = new SqlParameter("@tableName", SqlDbType.NVarChar, 128); + @tableName.Value = string.Format("[{0}].[{1}]", table.Schema, table.ActualName); + return SelectToDataTable(columnSelect, @tableName); } private DataTable GetPrimaryKeys() @@ -173,10 +180,22 @@ private DataTable GetForeignKeys() private DataTable GetPrimaryKeys(string tableName) { - return GetPrimaryKeys().AsEnumerable() - .Where( - row => row["TABLE_NAME"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)) - .CopyToDataTable(); + var primaryKeys = GetPrimaryKeys(); + try + { + var dataTable = + primaryKeys.AsEnumerable() + .Where( + row => + row["TABLE_NAME"].ToString() + .Equals(tableName, StringComparison.InvariantCultureIgnoreCase)) + .CopyToDataTable(); + return dataTable; + } + catch (InvalidOperationException) + { + return null; + } } private EnumerableRowCollection GetForeignKeys(string tableName) @@ -186,13 +205,14 @@ private EnumerableRowCollection GetForeignKeys(string tableName) row => row["TABLE_NAME"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)); } - private DataTable SelectToDataTable(string sql) + private DataTable SelectToDataTable(string sql, params SqlParameter[] parameters) { var dataTable = new DataTable(); using (var cn = ConnectionProvider.CreateConnection() as SqlConnection) { using (var adapter = new SqlDataAdapter(sql, cn)) { + adapter.SelectCommand.Parameters.AddRange(parameters); adapter.Fill(dataTable); } diff --git a/Simple.Data.SqlServer/SqlServerProviderAttribute.cs b/Simple.Data.SqlServer/SqlServerProviderAttribute.cs new file mode 100644 index 00000000..de766975 --- /dev/null +++ b/Simple.Data.SqlServer/SqlServerProviderAttribute.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using Simple.Data.Ado; + +namespace Simple.Data.SqlServer +{ + public class SqlServerProviderAttribute : ProviderAssemblyAttributeBase + { + public SqlServerProviderAttribute() + : base("System.Data.SqlClient") + { + } + + public override bool TryGetProvider(string connectionString, out IConnectionProvider provider, out Exception exception) + { + try + { + var _ = new SqlConnectionStringBuilder(connectionString); + } + catch (KeyNotFoundException ex) + { + exception = ex; + provider = null; + return false; + } + catch (FormatException ex) + { + exception = ex; + provider = null; + return false; + } + catch (ArgumentException ex) + { + exception = ex; + provider = null; + return false; + } + + provider = new SqlConnectionProvider(connectionString); + exception = null; + return true; + } + } +} \ No newline at end of file diff --git a/Simple.Data.SqlServer/SqlTypeResolver.cs b/Simple.Data.SqlServer/SqlTypeResolver.cs index 15d95399..eabfab5d 100644 --- a/Simple.Data.SqlServer/SqlTypeResolver.cs +++ b/Simple.Data.SqlServer/SqlTypeResolver.cs @@ -16,7 +16,7 @@ static class SqlTypeResolver {"date", typeof (DateTime)}, {"time", typeof (DateTime)}, {"datetime2", typeof (DateTime)}, - {"datetimeoffset", typeof (DateTime)}, + {"datetimeoffset", typeof (DateTimeOffset)}, {"tinyint", typeof (byte)}, {"smallint", typeof (short)}, {"int", typeof (int)}, diff --git a/Simple.Data.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs new file mode 100644 index 00000000..9294ed4a --- /dev/null +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -0,0 +1,157 @@ +using Simple.Data.Ado; + +namespace Simple.Data.SqlTest +{ + using System.Collections.Generic; + using System.Diagnostics; + using NUnit.Framework; + + [TestFixture] + public class BulkInsertTest + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void BulkInsertUsesSchema() + { + var db = DatabaseHelper.Open(); + List list; + Promise count; + using (var tx = db.BeginTransaction()) + { + tx.test.SchemaTable.DeleteAll(); + tx.test.SchemaTable.Insert(GenerateItems()); + + list = tx.test.SchemaTable.All().WithTotalCount(out count).ToList(); + tx.Rollback(); + } + Assert.AreEqual(1000, count.Value); + Assert.AreEqual(1000, list.Count); + } + + [Test] + public void BulkInsertUsesSchemaAndFireTriggers() + { + var db = DatabaseHelper.Open(); + + using (var tx = db.BeginTransaction()) + { + tx.WithOptions(new AdoOptions(commandTimeout: 60000, fireTriggersOnBulkInserts: true)); + tx.test.SchemaTable.DeleteAll(); + tx.test.SchemaTable.Insert(GenerateItems()); + + tx.Commit(); + } + + int rowsWhichWhereUpdatedByTrigger = db.test.SchemaTable.GetCountBy(Optional: "Modified By Trigger"); + + Assert.AreEqual(1000, rowsWhichWhereUpdatedByTrigger); + } + + + [Test] + public void BulkInsertRecordsWithDifferentColumnsProperlyInsertsData() + { + DatabaseHelper.Reset(); + + var db = DatabaseHelper.Open(); + dynamic r1 = new SimpleRecord(); + r1.FirstName = "Bob"; + r1.LastName = "Dole"; + + dynamic r2 = new SimpleRecord(); + r2.FirstName = "Bob"; + r2.MiddleInitial = "L"; + r2.LastName = "Saget"; + + db.OptionalColumnTest.Insert(new[] { r2, r1 }); + + var objs = db.OptionalColumnTest.All().ToList(); + + var expected = new[] {new OptionalColumnTestObject("Bob", "Dole"), new OptionalColumnTestObject("Bob", "Saget", "L"),}; + + Assert.That(objs, Is.EquivalentTo(expected)); + + } + + [Test] + public void BulkInsertRecordsWithDifferentColumnsAndFewerColumnsInFirstRecordProperlyInsertsData() + { + DatabaseHelper.Reset(); + + var db = DatabaseHelper.Open(); + + dynamic r1 = new SimpleRecord(); + r1.FirstName = "Bob"; + r1.LastName = "Dole"; + + dynamic r2 = new SimpleRecord(); + r2.FirstName = "Bob"; + r2.MiddleInitial = "L"; + r2.LastName = "Saget"; + + db.OptionalColumnTest.Insert(new[] { r1, r2 }); + + var objs = db.OptionalColumnTest.All().ToList(); + + var expected = new[] { new OptionalColumnTestObject("Bob", "Dole"), new OptionalColumnTestObject("Bob", "Saget", "L"), }; + + Assert.That(objs, Is.EquivalentTo(expected)); + + } + + + private static IEnumerable GenerateItems() + { + for (int i = 0; i < 1000; i++) + { + yield return new SchemaItem(i, i.ToString()); + } + } + } + + class OptionalColumnTestObject + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string MiddleInitial { get; set; } + + public OptionalColumnTestObject() {} + + public OptionalColumnTestObject(string first, string last, string middle = null) + { + FirstName = first; + LastName = last; + MiddleInitial = middle; + } + + public override string ToString() + { + return string.Format("", FirstName, LastName, MiddleInitial); + } + + public override bool Equals(object obj) + { + var other = obj as OptionalColumnTestObject; + if (other == null) return false; + return other.FirstName == FirstName && other.LastName == LastName && other.MiddleInitial == MiddleInitial; + } + } + + class SchemaItem + { + public SchemaItem(int id, string description) + { + Id = id; + Description = description; + } + + public int Id { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/Simple.Data.SqlTest/DatabaseHelper.cs b/Simple.Data.SqlTest/DatabaseHelper.cs index 225a2cbc..1c1154a8 100644 --- a/Simple.Data.SqlTest/DatabaseHelper.cs +++ b/Simple.Data.SqlTest/DatabaseHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; +using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -10,12 +11,16 @@ namespace Simple.Data.SqlTest { internal static class DatabaseHelper { - public static readonly string ConnectionString = + public static readonly string ConnectionString = GetConnectionString(); + + private static string GetConnectionString() + { #if(MONO) - "Data Source=10.37.129.4;Initial Catalog=SimpleTest;User ID=SimpleUser;Password=SimplePassword"; + return "Data Source=10.37.129.4;Initial Catalog=SimpleTest;User ID=SimpleUser;Password=SimplePassword"; #else - Properties.Settings.Default.ConnectionString; + return Environment.GetEnvironmentVariable("SIMPLETESTDB") ?? Properties.Settings.Default.ConnectionString; #endif + } public static dynamic Open() { @@ -24,16 +29,25 @@ public static dynamic Open() public static void Reset() { - using (var cn = new SqlConnection(ConnectionString)) + try { - cn.Open(); - using (var cmd = cn.CreateCommand()) + var provider = new SqlServer.SqlConnectionProvider(); + using (var cn = new SqlConnection(ConnectionString)) { - cmd.CommandText = "TestReset"; - cmd.CommandType = CommandType.StoredProcedure; - cmd.ExecuteNonQuery(); + cn.Open(); + using (var cmd = cn.CreateCommand()) + { + cmd.CommandText = "TestReset"; + cmd.CommandType = CommandType.StoredProcedure; + cmd.ExecuteNonQuery(); + } } } + catch (Exception ex) + { + Trace.WriteLine(ex.ToString()); + throw; + } } } } diff --git a/Simple.Data.SqlTest/DatabaseOpenerTests.cs b/Simple.Data.SqlTest/DatabaseOpenerTests.cs index 307565ad..ac3dbcb4 100644 --- a/Simple.Data.SqlTest/DatabaseOpenerTests.cs +++ b/Simple.Data.SqlTest/DatabaseOpenerTests.cs @@ -12,9 +12,20 @@ namespace Simple.Data.SqlTest [TestFixture] public class DatabaseOpenerTests { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void OpenNamedConnectionTest() { + if (Environment.GetEnvironmentVariable("SIMPLETESTDB") != null) + { + Assert.Ignore(); + return; + } var db = Database.OpenNamedConnection("Test"); Assert.IsNotNull(db); var user = db.Users.FindById(1); @@ -24,7 +35,7 @@ public void OpenNamedConnectionTest() [Test] public void TestProviderIsSqlProvider() { - var provider = new ProviderHelper().GetProviderByConnectionString(Properties.Settings.Default.ConnectionString); + var provider = new ProviderHelper().GetProviderByConnectionString(DatabaseHelper.ConnectionString); Assert.IsInstanceOf(typeof(SqlConnectionProvider), provider); } @@ -39,7 +50,7 @@ public void TestProviderIsSqlProviderFromOpen() [Test] public void TestProviderIsSqlProviderFromOpenConnection() { - Database db = Database.OpenConnection(Properties.Settings.Default.ConnectionString); + Database db = Database.OpenConnection(DatabaseHelper.ConnectionString); Assert.IsInstanceOf(typeof(AdoAdapter), db.GetAdapter()); Assert.IsInstanceOf(typeof(SqlConnectionProvider), ((AdoAdapter)db.GetAdapter()).ConnectionProvider); } diff --git a/Simple.Data.SqlTest/DeleteTest.cs b/Simple.Data.SqlTest/DeleteTest.cs index 85ff63ef..204e55d4 100644 --- a/Simple.Data.SqlTest/DeleteTest.cs +++ b/Simple.Data.SqlTest/DeleteTest.cs @@ -11,6 +11,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class DeleteTest { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void TestDeleteByColumn() { diff --git a/Simple.Data.SqlTest/FindTests.cs b/Simple.Data.SqlTest/FindTests.cs index 594b875e..89e1bd92 100644 --- a/Simple.Data.SqlTest/FindTests.cs +++ b/Simple.Data.SqlTest/FindTests.cs @@ -56,6 +56,14 @@ public void TestFindAllByName() Assert.AreEqual(1, users.Count()); } + [Test] + public void TestFindAllByNameArray() + { + var db = DatabaseHelper.Open(); + IEnumerable users = db.Users.FindAllByName(new[] { "Bob", "UnknownUser" }).Cast(); + Assert.AreEqual(1, users.Count()); + } + [Test] public void TestFindAllByNameAsIEnumerableOfDynamic() { @@ -122,19 +130,7 @@ public void TestImplicitEnumerableCast() } } - [Test] - public void TestFindWithSchemaQualification() - { - var db = DatabaseHelper.Open(); - - var dboActual = db.dbo.SchemaTable.FindById(1); - var testActual = db.test.SchemaTable.FindById(1); - - Assert.IsNotNull(dboActual); - Assert.AreEqual("Pass", dboActual.Description); - Assert.IsNull(testActual); - } - + [Test] public void TestFindWithCriteriaAndSchemaQualification() { @@ -146,16 +142,6 @@ public void TestFindWithCriteriaAndSchemaQualification() Assert.AreEqual("Pass", dboActual.Description); } - [Test] - public void TestFindAllByIdWithSchemaQualification() - { - var db = DatabaseHelper.Open(); - var dboCount = db.dbo.SchemaTable.FindAllById(1).ToList().Count; - var testCount = db.test.SchemaTable.FindAllById(1).ToList().Count; - Assert.AreEqual(1, dboCount); - Assert.AreEqual(0, testCount); - } - [Test] public void TestFindOnAView() { @@ -212,5 +198,14 @@ public void ExpressionAndWithClauseShouldCastToStaticTypeWithCollection() Assert.AreEqual(1, actual.Orders.Single().OrderId); Assert.AreEqual(new DateTime(2010, 10, 10), actual.Orders.Single().OrderDate); } + + [Test] + public void SelectClauseShouldRestrictColumn() + { + var db = DatabaseHelper.Open(); + var actual = db.Customers.Select(db.Customers.Name).FindByCustomerId(1).ToScalar(); + Assert.AreEqual("Test", actual); + + } } } diff --git a/Simple.Data.SqlTest/FunctionTest.cs b/Simple.Data.SqlTest/FunctionTest.cs new file mode 100644 index 00000000..f502c563 --- /dev/null +++ b/Simple.Data.SqlTest/FunctionTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.SqlTest +{ + using NUnit.Framework; + + [TestFixture] + public class FunctionTest + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void CoalesceFunctionWorks() + { + var db = DatabaseHelper.Open(); + var date = new DateTime(1900, 1, 1); + List q = db.Orders.Query(db.Orders.OrderDate.Coalesce(date) < DateTime.Now).ToList(); + Assert.AreNotEqual(0, q.Count); + } + } +} diff --git a/Simple.Data.SqlTest/GetTests.cs b/Simple.Data.SqlTest/GetTests.cs index a4b2de43..80df8238 100644 --- a/Simple.Data.SqlTest/GetTests.cs +++ b/Simple.Data.SqlTest/GetTests.cs @@ -22,6 +22,14 @@ public void TestGet() Assert.AreEqual(1, user.Id); } + [Test] + public void GetWithNonExistentPrimaryKeyShouldReturnNull() + { + var db = DatabaseHelper.Open(); + var user = db.Users.Get(1138); + Assert.IsNull(user); + } + [Test] public void SelectClauseWithGetScalarShouldLimitQuery() { diff --git a/Simple.Data.SqlTest/InsertTests.cs b/Simple.Data.SqlTest/InsertTests.cs index af3f4ba8..f9826318 100644 --- a/Simple.Data.SqlTest/InsertTests.cs +++ b/Simple.Data.SqlTest/InsertTests.cs @@ -2,10 +2,13 @@ using System.Dynamic; using System.Linq; using NUnit.Framework; +using Simple.Data.Ado; using Simple.Data.SqlTest.Resources; namespace Simple.Data.SqlTest { + using System; + [TestFixture] public class InsertTests { @@ -28,6 +31,27 @@ public void TestInsertWithNamedArguments() Assert.AreEqual(29, user.Age); } + [Test] + public void TestInsertWithIdentityInsertOn() + { + var db = DatabaseHelper.Open().WithOptions(new AdoOptions(identityInsert: true)); + var user = db.Users.Insert(Id: 42, Name: "Arthur", Password: "Tea", Age: 30); + Assert.IsNotNull(user); + Assert.AreEqual(42, user.Id); + } + + [Test] + public void TestInsertWithIdentityInsertOnThenOffAgain() + { + var db = DatabaseHelper.Open().WithOptions(new AdoOptions(identityInsert: true)); + var user = db.Users.Insert(Id: 2267709, Name: "Douglas", Password: "dirk", Age: 49); + Assert.IsNotNull(user); + Assert.AreEqual(2267709, user.Id); + db.ClearOptions(); + user = db.Users.Insert(Name: "Frak", Password: "true", Age: 200); + Assert.Less(2267709, user.Id); + } + [Test] public void TestInsertWithStaticTypeObject() { @@ -262,5 +286,25 @@ public void TestInsertWithVarBinaryMaxColumn() blob = db.Blobs.FindById(1); Assert.IsTrue(image.SequenceEqual(blob.Data)); } + + [Test] + public void TestInsertWithTimestampColumn() + { + var db = DatabaseHelper.Open(); + var row = db.TimestampTest.Insert(Description: "Foo"); + Assert.IsNotNull(row); + Assert.IsInstanceOf(row.Version); + } + + [Test] + public void TestInsertWithDateTimeOffsetColumn() + { + var db = DatabaseHelper.Open(); + dynamic entry = new ExpandoObject(); + var time = DateTimeOffset.Now; + entry.time = time; + var inserted = db.DateTimeOffsetTest.Insert(entry); + Assert.AreEqual(time, inserted.time); + } } } diff --git a/Simple.Data.SqlTest/NaturalJoinTest.cs b/Simple.Data.SqlTest/NaturalJoinTest.cs index 1dc67e3b..3f43d74f 100644 --- a/Simple.Data.SqlTest/NaturalJoinTest.cs +++ b/Simple.Data.SqlTest/NaturalJoinTest.cs @@ -10,6 +10,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class NaturalJoinTest { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void CustomerDotOrdersDotOrderDateShouldReturnOneRow() { diff --git a/Simple.Data.SqlTest/OrderDetailTests.cs b/Simple.Data.SqlTest/OrderDetailTests.cs index 9fa62892..20c19ff3 100644 --- a/Simple.Data.SqlTest/OrderDetailTests.cs +++ b/Simple.Data.SqlTest/OrderDetailTests.cs @@ -10,6 +10,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class OrderDetailTests { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void TestOrderDetail() { diff --git a/Simple.Data.SqlTest/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index fa719258..f8e699a9 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -1,114 +1,171 @@ -using System.Diagnostics; -using NUnit.Framework; - -namespace Simple.Data.SqlTest -{ - using System.Data; - - [TestFixture] - public class ProcedureTest - { - [Test] - public void GetCustomersTest() - { - var db = DatabaseHelper.Open(); - var results = db.GetCustomers(); - var actual = results.First(); - Assert.AreEqual(1, actual.CustomerId); - } - - [Test] - public void GetCustomerCountTest() - { - var db = DatabaseHelper.Open(); - var results = db.GetCustomerCount(); - Assert.AreEqual(1, results.ReturnValue); - } - - [Test] - public void FindGetCustomerCountAndInvokeTest() - { - var db = DatabaseHelper.Open(); - var getCustomerCount = db.GetCustomerCount; - var results = getCustomerCount(); - Assert.AreEqual(1, results.ReturnValue); - } - - [Test] - public void FindGetCustomerCountUsingIndexerAndInvokeTest() - { - var db = DatabaseHelper.Open(); - var getCustomerCount = db["GetCustomerCount"]; - var results = getCustomerCount(); - Assert.AreEqual(1, results.ReturnValue); - } - - [Test] - public void GetCustomerCountSecondCallExecutesNonQueryTest() - { - var listener = new TestTraceListener(); - Trace.Listeners.Add(listener); - var db = DatabaseHelper.Open(); - db.GetCustomerCount(); - Assert.IsFalse(listener.Output.Contains("ExecuteNonQuery")); - db.GetCustomerCount(); - Assert.IsTrue(listener.Output.Contains("ExecuteNonQuery")); - Trace.Listeners.Remove(listener); - } - - [Test] - public void GetCustomerAndOrdersTest() - { - var db = DatabaseHelper.Open(); - var results = db.GetCustomerAndOrders(1); - var customer = results.FirstOrDefault(); - Assert.IsNotNull(customer); - Assert.AreEqual(1, customer.CustomerId); - Assert.IsTrue(results.NextResult()); - var order = results.FirstOrDefault(); - Assert.IsNotNull(order); - Assert.AreEqual(1, order.OrderId); - } - - [Test] - public void GetCustomerAndOrdersStillWorksAfterZeroRecordCallTest() - { - var db = DatabaseHelper.Open(); - db.GetCustomerAndOrders(1000); - var results = db.GetCustomerAndOrders(1); - var customer = results.FirstOrDefault(); - Assert.IsNotNull(customer); - Assert.AreEqual(1, customer.CustomerId); - Assert.IsTrue(results.NextResult()); - var order = results.FirstOrDefault(); - Assert.IsNotNull(order); - Assert.AreEqual(1, order.OrderId); - } - - [Test] - public void ScalarFunctionIsCalledCorrectly() - { - var db = DatabaseHelper.Open(); - var results = db.VarcharAndReturnInt("The answer to everything"); - Assert.AreEqual(42, results.ReturnValue); - } - - [Test] - public void CallProcedureWithDataTable() - { - var db = DatabaseHelper.Open(); - var dataTable = new DataTable(); - dataTable.Columns.Add("Value"); - dataTable.Rows.Add("One"); - dataTable.Rows.Add("Two"); - dataTable.Rows.Add("Three"); - - var actual = db.ReturnStrings(dataTable).ToScalarList(); - - Assert.AreEqual(3, actual.Count); - Assert.Contains("One", actual); - Assert.Contains("Two", actual); - Assert.Contains("Three", actual); - } - } -} +using System.Diagnostics; +using NUnit.Framework; + +namespace Simple.Data.SqlTest +{ + using System.Data; + + [TestFixture] + public class ProcedureTest + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void GetCustomersTest() + { + var db = DatabaseHelper.Open(); + var results = db.GetCustomers(); + var actual = results.First(); + Assert.AreEqual(1, actual.CustomerId); + } + + [Test] + public void GetCustomerCountTest() + { + var db = DatabaseHelper.Open(); + var results = db.GetCustomerCount(); + Assert.AreEqual(1, results.ReturnValue); + } + + [Test] + public void FindGetCustomerCountAndInvokeTest() + { + var db = DatabaseHelper.Open(); + var getCustomerCount = db.GetCustomerCount; + var results = getCustomerCount(); + Assert.AreEqual(1, results.ReturnValue); + } + + [Test] + public void FindGetCustomerCountUsingIndexerAndInvokeTest() + { + var db = DatabaseHelper.Open(); + var getCustomerCount = db["GetCustomerCount"]; + var results = getCustomerCount(); + Assert.AreEqual(1, results.ReturnValue); + } + + [Test] + public void SchemaUnqualifiedProcedureResolutionTest() + { + var db = DatabaseHelper.Open(); + var actual = db.SchemaProc().FirstOrDefault(); + Assert.IsNotNull(actual); + Assert.AreEqual("dbo.SchemaProc", actual.Actual); + } + + [Test] + public void SchemaQualifiedProcedureResolutionTest() + { + var db = DatabaseHelper.Open(); + var actual = db.test.SchemaProc().FirstOrDefault(); + Assert.IsNotNull(actual); + Assert.AreEqual("test.SchemaProc", actual.Actual); + } + + [Test] + public void GetCustomerCountAsOutputTest() + { + var db = DatabaseHelper.Open(); + var actual = db.GetCustomerCountAsOutput(); + Assert.AreEqual(42, actual.OutputValues["Count"]); + } + +#if DEBUG // Trace is only written for DEBUG build + [Test] + public void GetCustomerCountSecondCallExecutesNonQueryTest() + { + SimpleDataTraceSources.TraceSource.Switch.Level = SourceLevels.All; + var listener = new TestTraceListener(); + SimpleDataTraceSources.TraceSource.Listeners.Add(listener); + var db = DatabaseHelper.Open(); + db.GetCustomerCount(); + Assert.IsFalse(listener.Output.Contains("ExecuteNonQuery")); + db.GetCustomerCount(); + Assert.IsTrue(listener.Output.Contains("ExecuteNonQuery")); + SimpleDataTraceSources.TraceSource.Listeners.Remove(listener); + } +#endif + + [Test] + public void GetCustomerAndOrdersTest() + { + var db = DatabaseHelper.Open(); + var results = db.GetCustomerAndOrders(1); + var customer = results.FirstOrDefault(); + Assert.IsNotNull(customer); + Assert.AreEqual(1, customer.CustomerId); + Assert.IsTrue(results.NextResult()); + var order = results.FirstOrDefault(); + Assert.IsNotNull(order); + Assert.AreEqual(1, order.OrderId); + } + + [Test] + public void AddCustomerTest() + { + var db = DatabaseHelper.Open(); + Customer customer; + customer = db.AddCustomer("Peter", "Address").FirstOrDefault(); + Assert.IsNotNull(customer); + customer = db.Customers.FindByCustomerId(customer.CustomerId); + Assert.IsNotNull(customer); + } + + [Test] + public void AddCustomerNullAddressTest() + { + var db = DatabaseHelper.Open(); + Customer customer; + customer = db.AddCustomer("Peter", null).FirstOrDefault(); + Assert.IsNotNull(customer); + customer = db.Customers.FindByCustomerId(customer.CustomerId); + Assert.IsNotNull(customer); + } + + [Test] + public void GetCustomerAndOrdersStillWorksAfterZeroRecordCallTest() + { + var db = DatabaseHelper.Open(); + db.GetCustomerAndOrders(1000); + var results = db.GetCustomerAndOrders(1); + var customer = results.FirstOrDefault(); + Assert.IsNotNull(customer); + Assert.AreEqual(1, customer.CustomerId); + Assert.IsTrue(results.NextResult()); + var order = results.FirstOrDefault(); + Assert.IsNotNull(order); + Assert.AreEqual(1, order.OrderId); + } + + [Test] + public void ScalarFunctionIsCalledCorrectly() + { + var db = DatabaseHelper.Open(); + var results = db.VarcharAndReturnInt("The answer to everything"); + Assert.AreEqual(42, results.ReturnValue); + } + + [Test] + public void CallProcedureWithDataTable() + { + var db = DatabaseHelper.Open(); + var dataTable = new DataTable(); + dataTable.Columns.Add("Value"); + dataTable.Rows.Add("One"); + dataTable.Rows.Add("Two"); + dataTable.Rows.Add("Three"); + + var actual = db.ReturnStrings(dataTable).ToScalarList(); + + Assert.AreEqual(3, actual.Count); + Assert.Contains("One", actual); + Assert.Contains("Two", actual); + Assert.Contains("Three", actual); + } + } +} diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index c4e94787..3b28658a 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Text; using NUnit.Framework; namespace Simple.Data.SqlTest @@ -83,6 +83,14 @@ public void ColumnAliasShouldChangeDynamicPropertyName() Assert.AreEqual("Bob", actual.Alias); } + [Test] + public void MissingColumnShouldHaveColumnNotFoundMessage() + { + var db = DatabaseHelper.Open(); + var actual = db.Users.QueryById(1).Select(db.Users.Name).First(); + Assert.Throws(() => Console.WriteLine(actual.Bobbins), "Column not found."); + } + [Test] public void ShouldSelectFromOneToTen() { @@ -179,7 +187,9 @@ public void WithTotalCountWithExplicitSelectAndOrderByShouldGiveCount() } [Test] +// ReSharper disable InconsistentNaming public void WithTotalCountShouldGiveCount_ObsoleteFutureVersion() +// ReSharper restore InconsistentNaming { Future count; var db = DatabaseHelper.Open(); @@ -295,35 +305,78 @@ public void ToScalarOrDefault() Assert.AreEqual(0, max); } + [Test] - public void QueryWithSchemaQualifiedTableName() + public void WithClauseShouldPreselectDetailTableAsCollection() { var db = DatabaseHelper.Open(); - var result = db.test.SchemaTable.QueryById(2) - .Select(db.test.SchemaTable.Id, - db.test.SchemaTable.Description) - .Single(); - Assert.AreEqual(2, result.Id); - Assert.AreEqual("Pass", result.Description); + var result = db.Customers.FindAllByCustomerId(1).WithOrders().FirstOrDefault() as IDictionary; + Assert.IsNotNull(result); + Assert.Contains("Orders", (ICollection)result.Keys); + var orders = result["Orders"] as IList>; + Assert.IsNotNull(orders); + Assert.AreEqual(1, orders.Count); } [Test] - public void QueryWithSchemaQualifiedTableNameAndAliases() + public void WithClauseShouldPreselectDetailTablesAsCollections() { var db = DatabaseHelper.Open(); - var result = db.test.SchemaTable.QueryById(2) - .Select(db.test.SchemaTable.Id.As("This"), - db.test.SchemaTable.Description.As("That")) - .Single(); - Assert.AreEqual(2, result.This); - Assert.AreEqual("Pass", result.That); + var result = db.Customers.FindAllByCustomerId(1).WithOrders().WithNotes().FirstOrDefault() as IDictionary; + Assert.IsNotNull(result); + Assert.Contains("Orders", (ICollection)result.Keys); + var orders = result["Orders"] as IList>; + Assert.IsNotNull(orders); + Assert.AreEqual(1, orders.Count); + Assert.Contains("Notes", (ICollection)result.Keys); + var notes = result["Notes"] as IList>; + Assert.IsNotNull(notes); + Assert.AreEqual(2, notes.Count); } [Test] - public void WithClauseShouldPreselectDetailTableAsCollection() + public void FindAllWithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollection() + { + var db = DatabaseHelper.Open(); + var result = db.Customers.FindAllByCustomerId(1).With(db.Customers.Orders.OrderItems).FirstOrDefault() as IDictionary; + Assert.IsNotNull(result); + Assert.Contains("OrderItems", (ICollection)result.Keys); + var orderItems = result["OrderItems"] as IList>; + Assert.IsNotNull(orderItems); + Assert.AreEqual(1, orderItems.Count); + } + + [Test, Ignore] + public void FindAllWithClauseWithNestedDetailTable() + { + var db = DatabaseHelper.Open(); + var result = db.Customers.FindAllByCustomerId(1).With(db.Customers.Orders).With(db.Customers.Orders.OrderItems).FirstOrDefault() as IDictionary; + Assert.IsNotNull(result); + Assert.Contains("Orders", result.Keys.ToArray()); + var orders = result["Orders"] as IList>; + Assert.IsNotNull(orders); + Assert.AreEqual(1, orders.Count); + var order = orders[0]; + Assert.Contains("OrderItems", order.Keys.ToArray()); + } + + [Test] + public void GetWithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollection() + { + var db = DatabaseHelper.Open(); + var result = db.Customers.With(db.Customers.Orders.OrderItems).Get(1) as IDictionary; + Assert.IsNotNull(result); + Assert.Contains("OrderItems", (ICollection)result.Keys); + var orderItems = result["OrderItems"] as IList>; + Assert.IsNotNull(orderItems); + Assert.AreEqual(1, orderItems.Count); + } + + [Test] + public void WithClauseWithTwoStepShouldPreselectManyToManyTableAsCollection() { var db = DatabaseHelper.Open(); - var result = db.Customers.FindAllByCustomerId(1).WithOrders().FirstOrDefault() as IDictionary; + var result = db.Customers.FindAll(db.Customers.Order.OrderId == 1).WithOrders().FirstOrDefault() as IDictionary; Assert.IsNotNull(result); Assert.Contains("Orders", (ICollection)result.Keys); var orders = result["Orders"] as IList>; @@ -362,6 +415,32 @@ public void WithClauseShouldCastToStaticTypeWithCollection() Assert.AreEqual(1, actual.Orders.Single().OrderId); Assert.AreEqual(new DateTime(2010,10,10), actual.Orders.Single().OrderDate); } + + [Test] + public void WithClauseShouldCastToStaticTypeWithEmptyCollection() + { + var db = DatabaseHelper.Open(); + var newCustomer = db.Customers.Insert(Name: "No Orders"); + Customer actual = db.Customers.FindAllByCustomerId(newCustomer.CustomerId).WithOrders().FirstOrDefault(); + Assert.IsNotNull(actual); + Assert.IsNotNull(actual.Orders); + Assert.AreEqual(0, actual.Orders.Count); + } + + [Test] + public void WithClauseContainingAliasShouldReturnResults() + { + var db = DatabaseHelper.Open(); + var actual = db.Customers + .With(db.Customers.Orders.As("Orders_1")) + .With(db.Customers.Orders.As("Orders_2")) + .FirstOrDefault(); + Assert.IsNotNull(actual); + Assert.AreEqual(1, actual.Orders_1.Single().OrderId); + Assert.AreEqual(1, actual.Orders_2.Single().OrderId); + Assert.AreEqual(new DateTime(2010, 10, 10), actual.Orders_1.Single().OrderDate); + Assert.AreEqual(new DateTime(2010, 10, 10), actual.Orders_2.Single().OrderDate); + } [Test] public void SelfJoinShouldNotThrowException() @@ -378,6 +457,22 @@ public void SelfJoinShouldNotThrowException() Assert.AreEqual(1, kingsSubordinates.Count); } + + [Test] + public void OrderByOnJoinedColumnShouldUseJoinedColumn() + { + SimpleDataTraceSources.TraceSource.Switch.Level = SourceLevels.All; + var traceListener = new TestTraceListener(); + SimpleDataTraceSources.TraceSource.Listeners.Add(traceListener); + Trace.Listeners.Add(traceListener); + var db = DatabaseHelper.Open(); + + var q = db.Employees.Query().LeftJoin(db.Employees.As("Manager"), Id: db.Employees.ManagerId); + q = q.Select(db.Employees.Name, q.Manager.Name.As("Manager")); + List employees = q.OrderBy(q.Manager.Name).ToList(); + SimpleDataTraceSources.TraceSource.Listeners.Remove(traceListener); + Assert.Greater(traceListener.Output.IndexOf("order by [manager].[name]", StringComparison.OrdinalIgnoreCase), 0); + } [Test] public void CanFetchMoreThanOneHundredRows() @@ -390,5 +485,21 @@ public void CanFetchMoreThanOneHundredRows() Assert.GreaterOrEqual(customers.Count, 200); } + + [Test] + public void QueryWithForUpdateFalseShouldReturnCorrectResult() + { + var db = DatabaseHelper.Open(); + var actual = db.Users.QueryById(1).Select(db.Users.Name).ForUpdate(false).First(); + Assert.AreEqual("Bob", actual.Name); + } + + [Test] + public void QueryWithForUpdateTrueShouldReturnCorrectResult() + { + var db = DatabaseHelper.Open(); + var actual = db.Users.QueryById(1).Select(db.Users.Name).ForUpdate(true).First(); + Assert.AreEqual("Bob", actual.Name); + } } } diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index d23a3371..1315bcc5 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -1,314 +1,491 @@ -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestReset]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[TestReset] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomers]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[GetCustomers] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CreateCustomer]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[CreateCustomer] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomerAndOrders]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[GetCustomerAndOrders] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomerCount]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[GetCustomerCount] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ReturnStrings]') AND type in (N'P', N'PC')) -DROP PROCEDURE [dbo].[ReturnStrings] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[VarcharAndReturnInt]')) -DROP FUNCTION [dbo].[VarcharAndReturnInt] -IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[VwCustomers]')) -DROP VIEW [dbo].[VwCustomers]; -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SchemaTable]') AND type in (N'U')) -DROP TABLE [dbo].[SchemaTable] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[test].[SchemaTable]') AND type in (N'U')) -DROP TABLE [test].[SchemaTable] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GroupTestDetail]') AND type in (N'U')) -DROP TABLE [dbo].[GroupTestDetail] -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GroupTestMaster]') AND type in (N'U')) -DROP TABLE [dbo].[GroupTestMaster] -GO -IF EXISTS (SELECT * FROM sys.schemas WHERE name = N'test') -DROP SCHEMA [test] -GO -IF EXISTS (SELECT * FROM sys.types st JOIN sys.schemas ss ON st.schema_id = ss.schema_id WHERE st.name = N'StringList' AND ss.name = N'dbo') -DROP TYPE [dbo].[StringList] -GO -CREATE TYPE [dbo].[StringList] AS TABLE([Value] nvarchar(50) NOT NULL) -GO -CREATE PROCEDURE TestReset -AS -BEGIN - SET NOCOUNT ON; - - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Employee]') AND type in (N'U')) - DROP TABLE [dbo].[Employee]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Users]') AND type in (N'U')) - DROP TABLE [dbo].[Users]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[UsersWithChar]') AND type in (N'U')) - DROP TABLE [dbo].[UsersWithChar]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderItems]') AND type in (N'U')) - DROP TABLE [dbo].[OrderItems]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U')) - DROP TABLE [dbo].[Orders]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Items]') AND type in (N'U')) - DROP TABLE [dbo].[Items]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Customers]') AND type in (N'U')) - DROP TABLE [dbo].[Customers]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[PagingTest]') AND type in (N'U')) - DROP TABLE [dbo].[PagingTest]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Images]') AND type in (N'U')) - DROP TABLE [dbo].[Images]; - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Blobs]') AND type in (N'U')) - DROP TABLE [dbo].[Blobs] - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[EnumTest]') AND type in (N'U')) - DROP TABLE [dbo].[EnumTest] - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DeleteTest]') AND type in (N'U')) - DROP TABLE [dbo].[DeleteTest] - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DecimalTest]') AND type in (N'U')) - DROP TABLE [dbo].[DecimalTest] - - CREATE TABLE [dbo].[Users] ( - [Id] INT IDENTITY (1, 1) NOT NULL, - [Name] NVARCHAR (100) NOT NULL, - [Password] NVARCHAR (100) NOT NULL, - [Age] INT NOT NULL - ); - - ALTER TABLE [dbo].[Users] - ADD CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[UsersWithChar] ( - [Id] INT IDENTITY (1, 1) NOT NULL, - [Name] NCHAR (100) NOT NULL, - [Password] NCHAR (100) NOT NULL, - [Age] INT NOT NULL - ); - - ALTER TABLE [dbo].[UsersWithChar] - ADD CONSTRAINT [PK_UsersWithChar] PRIMARY KEY CLUSTERED ([Id] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[Employee]( - [Id] [int] IDENTITY(1,1) NOT NULL, - [Name] [nvarchar](50) NOT NULL, - [ManagerId] [int] NULL) - - ALTER TABLE [dbo].[Employee] - ADD CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED ([Id] ASC) - - CREATE TABLE [dbo].[Orders] ( - [OrderId] INT IDENTITY (1, 1) NOT NULL, - [OrderDate] DATETIME NOT NULL, - [CustomerId] INT NOT NULL - ); - - ALTER TABLE [dbo].[Orders] - ADD CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[OrderItems] ( - [OrderItemId] INT IDENTITY (1, 1) NOT NULL, - [OrderId] INT NOT NULL, - [ItemId] INT NOT NULL, - [Quantity] INT NOT NULL - ); - - ALTER TABLE [dbo].[OrderItems] - ADD CONSTRAINT [PK_OrderItems] PRIMARY KEY CLUSTERED ([OrderItemId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[Images]( - [Id] [int] NOT NULL, - [TheImage] [image] NOT NULL - ); - - ALTER TABLE [dbo].[Images] - ADD CONSTRAINT [PK_Images] PRIMARY KEY CLUSTERED ([Id] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[Items] ( - [ItemId] INT IDENTITY (1, 1) NOT NULL, - [Name] NVARCHAR (100) NOT NULL, - [Price] MONEY NOT NULL - ); - - ALTER TABLE [dbo].[Items] - ADD CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED ([ItemId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[Customers] ( - [CustomerId] INT IDENTITY (1, 1) NOT NULL, - [Name] NVARCHAR (100) NOT NULL, - [Address] NVARCHAR (200) NULL - ); - - ALTER TABLE [dbo].[Customers] - ADD CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([CustomerId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - - CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int); - - CREATE TABLE [dbo].[Blobs]( - [Id] [int] NOT NULL, - [Data] [varbinary](max) NULL - ) - - ALTER TABLE [dbo].[Blobs] - ADD CONSTRAINT [PK_Blobs] PRIMARY KEY CLUSTERED ([Id] ASC) - - CREATE TABLE [dbo].[EnumTest]( - [Id] [int] IDENTITY (1, 1) NOT NULL, - [Flag] [int] NOT NULL - ) - - ALTER TABLE [dbo].[EnumTest] - ADD CONSTRAINT [PK_EnumTest] PRIMARY KEY CLUSTERED ([Id] ASC) - - CREATE TABLE [dbo].[DeleteTest]( - [Id] [int] NOT NULL - ) - - ALTER TABLE [dbo].[DeleteTest] - ADD CONSTRAINT [PK_DeleteTest] PRIMARY KEY CLUSTERED ([Id] ASC) - - CREATE TABLE [dbo].[DecimalTest]( - [Id] [int] IDENTITY (1, 1) NOT NULL, - [Value] [decimal](20,6) NOT NULL - ) - - BEGIN TRANSACTION - SET IDENTITY_INSERT [dbo].[Customers] ON - INSERT INTO [dbo].[Customers] ([CustomerId], [Name], [Address]) VALUES (1, N'Test', N'100 Road') - SET IDENTITY_INSERT [dbo].[Customers] OFF - SET IDENTITY_INSERT [dbo].[Orders] ON - INSERT INTO [dbo].[Orders] ([OrderId], [OrderDate], [CustomerId]) VALUES (1, '20101010 00:00:00.000', 1) - SET IDENTITY_INSERT [dbo].[Orders] OFF - SET IDENTITY_INSERT [dbo].[Items] ON - INSERT INTO [dbo].[Items] ([ItemId], [Name], [Price]) VALUES (1, N'Widget', 4.5000) - SET IDENTITY_INSERT [dbo].[Items] OFF - SET IDENTITY_INSERT [dbo].[OrderItems] ON - INSERT INTO [dbo].[OrderItems] ([OrderItemId], [OrderId], [ItemId], [Quantity]) VALUES (1, 1, 1, 10) - SET IDENTITY_INSERT [dbo].[OrderItems] OFF - SET IDENTITY_INSERT [dbo].[Users] ON - INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (1,'Bob','Bob',32) - INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (2,'Charlie','Charlie',49) - INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (3,'Dave','Dave',12) - SET IDENTITY_INSERT [dbo].[Users] OFF - SET IDENTITY_INSERT [dbo].[UsersWithChar] ON - INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (1,'Bob','Bob',32) - INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (2,'Charlie','Charlie',49) - INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (3,'Dave','Dave',12) - SET IDENTITY_INSERT [dbo].[UsersWithChar] OFF - SET IDENTITY_INSERT [dbo].[Employee] ON - INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (1, 'Alice', NULL) - INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (2, 'Bob', 1) - INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (3, 'Charlie', 2) - SET IDENTITY_INSERT [dbo].[Employee] OFF - - DECLARE @PagingId AS INT - SET @PagingId = 1 - WHILE @PagingId <= 100 - BEGIN - INSERT INTO [dbo].[PagingTest] ([Id]) VALUES (@PagingId) - SET @PagingId = @PagingId + 1 - END - - INSERT INTO [dbo].[DecimalTest] ([Value]) VALUES (1.234567) - COMMIT TRANSACTION - - ALTER TABLE [dbo].[Orders] WITH NOCHECK - ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY ([CustomerId]) REFERENCES [dbo].[Customers] ([CustomerId]) ON DELETE NO ACTION ON UPDATE NO ACTION; - - ALTER TABLE [dbo].[OrderItems] WITH NOCHECK - ADD CONSTRAINT [FK_OrderItems_Items] FOREIGN KEY ([ItemId]) REFERENCES [dbo].[Items] ([ItemId]) ON DELETE NO ACTION ON UPDATE NO ACTION; - - ALTER TABLE [dbo].[OrderItems] WITH NOCHECK - ADD CONSTRAINT [FK_OrderItems_Orders] FOREIGN KEY ([OrderId]) REFERENCES [dbo].[Orders] ([OrderId]) ON DELETE NO ACTION ON UPDATE NO ACTION; -END -GO - -EXEC [dbo].[TestReset] -GO - -CREATE VIEW [dbo].[VwCustomers] - AS - SELECT Name, Address, CustomerId - FROM dbo.Customers - WHERE (Name LIKE '%e%') - -GO - -CREATE TABLE [dbo].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null); -GO -INSERT INTO [dbo].[SchemaTable] VALUES (1, 'Pass') -GO - -CREATE SCHEMA [test] AUTHORIZATION [dbo] -GO - -CREATE TABLE [test].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null); -GO -INSERT INTO [test].[SchemaTable] VALUES (2, 'Pass') -GO - -CREATE PROCEDURE [dbo].[GetCustomers] -AS -BEGIN - SET NOCOUNT ON; - SELECT * FROM Customers - ORDER BY CustomerId -END -GO -CREATE PROCEDURE [dbo].[CreateCustomer] -AS -BEGIN - SET NOCOUNT ON; - INSERT INTO Customers (Name, Address) VALUES ('Proccy','ProcAddress'); - SELECT * FROM Customers WHERE CustomerId = SCOPE_IDENTITY() -END -GO -CREATE PROCEDURE [dbo].[GetCustomerAndOrders] (@CustomerId int) -AS -BEGIN - SET NOCOUNT ON; - SELECT * FROM Customers WHERE CustomerId = @CustomerId; - SELECT * FROM Orders WHERE CustomerId = @CustomerId; -END -GO -CREATE PROCEDURE [dbo].[GetCustomerCount] -AS -BEGIN - SET NOCOUNT ON; - RETURN 1 -END -GO -CREATE PROCEDURE ReturnStrings(@Strings AS [dbo].[StringList] READONLY) -AS -SELECT Value FROM @Strings -GO -CREATE FUNCTION [dbo].[VarcharAndReturnInt] (@AValue varchar(50)) RETURNS INT AS BEGIN - IF ISNUMERIC(@AValue) = 1 - BEGIN - RETURN cast (@AValue as int) - END - RETURN 42 -END -GO -CREATE TABLE [dbo].[GroupTestMaster] ( - [Id] INT IDENTITY (1, 1) NOT NULL, - [Name] NVARCHAR (100) NOT NULL - ); - -ALTER TABLE [dbo].[GroupTestMaster] - ADD CONSTRAINT [PK_GroupTestMaster] PRIMARY KEY CLUSTERED ([Id] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); -GO -CREATE TABLE [dbo].[GroupTestDetail] ( - [Id] INT IDENTITY (1, 1) NOT NULL, - [Date] DATETIME NOT NULL, - [Number] INT NOT NULL, - [MasterId] INT NOT NULL - ); - -ALTER TABLE [dbo].[GroupTestDetail] - ADD CONSTRAINT [PK_GroupTestDetail] PRIMARY KEY CLUSTERED ([Id] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - -ALTER TABLE [dbo].[GroupTestDetail] WITH NOCHECK - ADD CONSTRAINT [FK_GroupTestDetail_GroupTestMaster] FOREIGN KEY ([MasterId]) REFERENCES [dbo].[GroupTestMaster] ([Id]) ON DELETE NO ACTION ON UPDATE NO ACTION; -GO -INSERT INTO [dbo].[GroupTestMaster] VALUES ('One') -INSERT INTO [dbo].[GroupTestMaster] VALUES ('Two') -INSERT INTO [dbo].[GroupTestDetail] VALUES ('1999-1-1',1,1) -INSERT INTO [dbo].[GroupTestDetail] VALUES ('2000-1-1',2,1) -INSERT INTO [dbo].[GroupTestDetail] VALUES ('2001-1-1',3,1) -INSERT INTO [dbo].[GroupTestDetail] VALUES ('2010-1-1',2,2) -INSERT INTO [dbo].[GroupTestDetail] VALUES ('2011-1-1',3,2) +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestReset]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[TestReset] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SchemaProc]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[SchemaProc] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[test].[SchemaProc]') AND type in (N'P', N'PC')) +DROP PROCEDURE [test].[SchemaProc] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomers]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[GetCustomers] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CreateCustomer]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[CreateCustomer] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[AddCustomer]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[AddCustomer] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomerAndOrders]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[GetCustomerAndOrders] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomerCount]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[GetCustomerCount] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCustomerCountAsOutput]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[GetCustomerCountAsOutput] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ReturnStrings]') AND type in (N'P', N'PC')) +DROP PROCEDURE [dbo].[ReturnStrings] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[VarcharAndReturnInt]')) +DROP FUNCTION [dbo].[VarcharAndReturnInt] +IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[VwCustomers]')) +DROP VIEW [dbo].[VwCustomers]; +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SchemaTable]') AND type in (N'U')) +DROP TABLE [dbo].[SchemaTable] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[test].[SchemaTable]') AND type in (N'U')) +DROP TABLE [test].[SchemaTable] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GroupTestDetail]') AND type in (N'U')) +DROP TABLE [dbo].[GroupTestDetail] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GroupTestMaster]') AND type in (N'U')) +DROP TABLE [dbo].[GroupTestMaster] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CompoundKeyDetail]') AND type in (N'U')) +DROP TABLE [dbo].[CompoundKeyDetail] +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CompoundKeyMaster]') AND type in (N'U')) +DROP TABLE [dbo].[CompoundKeyMaster] +GO +IF EXISTS (SELECT * FROM sys.schemas WHERE name = N'test') +DROP SCHEMA [test] +GO +IF EXISTS (SELECT * FROM sys.types st JOIN sys.schemas ss ON st.schema_id = ss.schema_id WHERE st.name = N'StringList' AND ss.name = N'dbo') +DROP TYPE [dbo].[StringList] +GO +CREATE TYPE [dbo].[StringList] AS TABLE([Value] nvarchar(50) NOT NULL) +GO +CREATE PROCEDURE TestReset +AS +BEGIN + SET NOCOUNT ON; + + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Employee]') AND type in (N'U')) + DROP TABLE [dbo].[Employee]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Users]') AND type in (N'U')) + DROP TABLE [dbo].[Users]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[UsersWithChar]') AND type in (N'U')) + DROP TABLE [dbo].[UsersWithChar]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderItems]') AND type in (N'U')) + DROP TABLE [dbo].[OrderItems]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U')) + DROP TABLE [dbo].[Orders]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Items]') AND type in (N'U')) + DROP TABLE [dbo].[Items]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Notes]') AND type in (N'U')) + DROP TABLE [dbo].[Notes]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Customers]') AND type in (N'U')) + DROP TABLE [dbo].[Customers]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[PagingTest]') AND type in (N'U')) + DROP TABLE [dbo].[PagingTest]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Images]') AND type in (N'U')) + DROP TABLE [dbo].[Images]; + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Blobs]') AND type in (N'U')) + DROP TABLE [dbo].[Blobs] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[EnumTest]') AND type in (N'U')) + DROP TABLE [dbo].[EnumTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DateTimeOffsetTest]') AND type in (N'U')) + DROP TABLE [dbo].[DateTimeOffsetTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DeleteTest]') AND type in (N'U')) + DROP TABLE [dbo].[DeleteTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DecimalTest]') AND type in (N'U')) + DROP TABLE [dbo].[DecimalTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GeographyTest]') AND type in (N'U')) + DROP TABLE [dbo].[GeographyTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GeometryTest]') AND type in (N'U')) + DROP TABLE [dbo].[GeometryTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[HierarchyIdTest]') AND type in (N'U')) + DROP TABLE [dbo].[HierarchyIdTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TimestampTest]') AND type in (N'U')) + DROP TABLE [dbo].[TimestampTest] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OptionalColumnTest]') AND type in (N'U')) + DROP TABLE [dbo].[OptionalColumnTest] + + CREATE TABLE [dbo].[Users] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [Name] NVARCHAR (100) NOT NULL, + [Password] NVARCHAR (100) NOT NULL, + [Age] INT NOT NULL + ); + + ALTER TABLE [dbo].[Users] + ADD CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC) ; + + CREATE TABLE [dbo].[UsersWithChar] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [Name] NCHAR (100) NOT NULL, + [Password] NCHAR (100) NOT NULL, + [Age] INT NOT NULL + ); + + ALTER TABLE [dbo].[UsersWithChar] + ADD CONSTRAINT [PK_UsersWithChar] PRIMARY KEY CLUSTERED ([Id] ASC) ; + + CREATE TABLE [dbo].[Employee]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Name] [nvarchar](50) NOT NULL, + [ManagerId] [int] NULL) + + ALTER TABLE [dbo].[Employee] + ADD CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED ([Id] ASC) + + CREATE TABLE [dbo].[Orders] ( + [OrderId] INT IDENTITY (1, 1) NOT NULL, + [OrderDate] DATETIME NOT NULL, + [CustomerId] INT NOT NULL + ); + + ALTER TABLE [dbo].[Orders] + ADD CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderId] ASC) ; + + CREATE TABLE [dbo].[OrderItems] ( + [OrderItemId] INT IDENTITY (1, 1) NOT NULL, + [OrderId] INT NOT NULL, + [ItemId] INT NOT NULL, + [Quantity] INT NOT NULL + ); + + ALTER TABLE [dbo].[OrderItems] + ADD CONSTRAINT [PK_OrderItems] PRIMARY KEY CLUSTERED ([OrderItemId] ASC) ; + + CREATE TABLE [dbo].[Images]( + [Id] [int] NOT NULL, + [TheImage] [image] NOT NULL + ); + + ALTER TABLE [dbo].[Images] + ADD CONSTRAINT [PK_Images] PRIMARY KEY CLUSTERED ([Id] ASC) ; + + CREATE TABLE [dbo].[Items] ( + [ItemId] INT IDENTITY (1, 1) NOT NULL, + [Name] NVARCHAR (100) NOT NULL, + [Price] MONEY NOT NULL + ); + + ALTER TABLE [dbo].[Items] + ADD CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED ([ItemId] ASC) ; + + CREATE TABLE [dbo].[Customers] ( + [CustomerId] INT IDENTITY (1, 1) NOT NULL, + [Name] NVARCHAR (100) NOT NULL, + [Address] NVARCHAR (200) NULL + ); + + ALTER TABLE [dbo].[Customers] + ADD CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([CustomerId] ASC) ; + + CREATE TABLE [dbo].[Notes]( + [NoteId] [int] IDENTITY(1,1) NOT NULL, + [CustomerId] [int] NOT NULL, + [Text] [nvarchar](50) NOT NULL, + CONSTRAINT [PK_Notes] PRIMARY KEY CLUSTERED ( [NoteId] ASC) ) + + CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int) + + ALTER TABLE [dbo].[PagingTest] + ADD CONSTRAINT [PK_PagingTest] PRIMARY KEY CLUSTERED ([Id] ASC) ; + + CREATE TABLE [dbo].[Blobs]( + [Id] [int] NOT NULL, + [Data] [varbinary](max) NULL + ) + + ALTER TABLE [dbo].[Blobs] + ADD CONSTRAINT [PK_Blobs] PRIMARY KEY CLUSTERED ([Id] ASC) + + CREATE TABLE [dbo].[EnumTest]( + [Id] [int] IDENTITY (1, 1) NOT NULL, + [Flag] [int] NOT NULL + ) + + ALTER TABLE [dbo].[EnumTest] + ADD CONSTRAINT [PK_EnumTest] PRIMARY KEY CLUSTERED ([Id] ASC) + + CREATE TABLE [dbo].[DeleteTest]( + [Id] [int] NOT NULL + ) + + ALTER TABLE [dbo].[DeleteTest] + ADD CONSTRAINT [PK_DeleteTest] PRIMARY KEY CLUSTERED ([Id] ASC) + + CREATE TABLE [dbo].[DateTimeOffsetTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Time] [datetimeoffset] + ) + + ALTER TABLE [dbo].[DateTimeOffsetTest] + ADD CONSTRAINT [PK_DateTimeOffsetTest] PRIMARY KEY CLUSTERED ([Id] ASC) + + CREATE TABLE [dbo].[DecimalTest]( + [Id] [int] IDENTITY (1, 1) NOT NULL, + [Value] [decimal](20,6) NOT NULL + ) + + CREATE CLUSTERED INDEX [ci_azure_fixup_dbo_DecimalTest] ON [dbo].[DecimalTest] ([Id] ASC) + + CREATE TABLE [dbo].[GeographyTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Description] [nvarchar](50) NOT NULL, + [Place] [geography] NULL, + CONSTRAINT [PK_GeographyTest] PRIMARY KEY CLUSTERED + ( + [Id] ASC + )) + + CREATE TABLE [dbo].[GeometryTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Description] [nvarchar](50) NOT NULL, + [Place] [geography] NULL, + CONSTRAINT [PK_GeometryTest] PRIMARY KEY CLUSTERED + ( + [Id] ASC + )) + + CREATE TABLE [dbo].[HierarchyIdTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Description] [nvarchar](50) NOT NULL, + [Place] [geography] NULL, + CONSTRAINT [PK_HierarchyIdTest] PRIMARY KEY CLUSTERED + ( + [Id] ASC + )) + + CREATE TABLE [dbo].[TimestampTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Description] [nvarchar](50), + [Version] [timestamp], + CONSTRAINT [PK_TimestampTest] PRIMARY KEY CLUSTERED + ( + [Id] ASC + )) + + CREATE TABLE [dbo].[OptionalColumnTest]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [FirstName] [nvarchar](50), + [LastName] [nvarchar](50), + [MiddleInitial] [nvarchar](50), + ) + + ALTER TABLE [dbo].[OptionalColumnTest] + ADD CONSTRAINT [PK_OptionalColumnTest] PRIMARY KEY CLUSTERED ([Id] ASC) + + BEGIN TRANSACTION + SET IDENTITY_INSERT [dbo].[Customers] ON + INSERT INTO [dbo].[Customers] ([CustomerId], [Name], [Address]) VALUES (1, N'Test', N'100 Road') + SET IDENTITY_INSERT [dbo].[Customers] OFF + INSERT INTO [dbo].[Notes] ([CustomerId], [Text]) VALUES (1, 'Note 1'); + INSERT INTO [dbo].[Notes] ([CustomerId], [Text]) VALUES (1, 'Note 2'); + SET IDENTITY_INSERT [dbo].[Orders] ON + INSERT INTO [dbo].[Orders] ([OrderId], [OrderDate], [CustomerId]) VALUES (1, '20101010 00:00:00.000', 1) + SET IDENTITY_INSERT [dbo].[Orders] OFF + SET IDENTITY_INSERT [dbo].[Items] ON + INSERT INTO [dbo].[Items] ([ItemId], [Name], [Price]) VALUES (1, N'Widget', 4.5000) + SET IDENTITY_INSERT [dbo].[Items] OFF + SET IDENTITY_INSERT [dbo].[OrderItems] ON + INSERT INTO [dbo].[OrderItems] ([OrderItemId], [OrderId], [ItemId], [Quantity]) VALUES (1, 1, 1, 10) + SET IDENTITY_INSERT [dbo].[OrderItems] OFF + SET IDENTITY_INSERT [dbo].[Users] ON + INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (1,'Bob','Bob',32) + INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (2,'Charlie','Charlie',49) + INSERT INTO [dbo].[Users] ([Id], [Name], [Password], [Age]) VALUES (3,'Dave','Dave',12) + SET IDENTITY_INSERT [dbo].[Users] OFF + SET IDENTITY_INSERT [dbo].[UsersWithChar] ON + INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (1,'Bob','Bob',32) + INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (2,'Charlie','Charlie',49) + INSERT INTO [dbo].[UsersWithChar] ([Id], [Name], [Password], [Age]) VALUES (3,'Dave','Dave',12) + SET IDENTITY_INSERT [dbo].[UsersWithChar] OFF + SET IDENTITY_INSERT [dbo].[Employee] ON + INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (1, 'Alice', NULL) + INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (2, 'Bob', 1) + INSERT INTO [dbo].[Employee] ([Id], [Name], [ManagerId]) VALUES (3, 'Charlie', 2) + SET IDENTITY_INSERT [dbo].[Employee] OFF + + DECLARE @PagingId AS INT + SET @PagingId = 1 + WHILE @PagingId <= 100 + BEGIN + INSERT INTO [dbo].[PagingTest] ([Id]) VALUES (@PagingId) + SET @PagingId = @PagingId + 1 + END + + INSERT INTO [dbo].[DecimalTest] ([Value]) VALUES (1.234567) + + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SchemaTable]') AND type in (N'U')) + TRUNCATE TABLE [dbo].[SchemaTable] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SchemaTable]') AND type in (N'U')) + INSERT INTO [dbo].[SchemaTable] VALUES (1, 'Pass') + + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[test].[SchemaTable]') AND type in (N'U')) + TRUNCATE TABLE [test].[SchemaTable] + IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[test].[SchemaTable]') AND type in (N'U')) + INSERT INTO [test].[SchemaTable] VALUES (2, 'Pass', null) + + COMMIT TRANSACTION + + ALTER TABLE [dbo].[Notes] WITH NOCHECK + ADD CONSTRAINT [FK_Notes_Customers] FOREIGN KEY ([CustomerId]) REFERENCES [dbo].[Customers] ([CustomerId]) ON DELETE NO ACTION ON UPDATE NO ACTION; + + ALTER TABLE [dbo].[Orders] WITH NOCHECK + ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY ([CustomerId]) REFERENCES [dbo].[Customers] ([CustomerId]) ON DELETE NO ACTION ON UPDATE NO ACTION; + + ALTER TABLE [dbo].[OrderItems] WITH NOCHECK + ADD CONSTRAINT [FK_OrderItems_Items] FOREIGN KEY ([ItemId]) REFERENCES [dbo].[Items] ([ItemId]) ON DELETE NO ACTION ON UPDATE NO ACTION; + + ALTER TABLE [dbo].[OrderItems] WITH NOCHECK + ADD CONSTRAINT [FK_OrderItems_Orders] FOREIGN KEY ([OrderId]) REFERENCES [dbo].[Orders] ([OrderId]) ON DELETE NO ACTION ON UPDATE NO ACTION; + +END +GO + +EXEC [dbo].[TestReset] +GO + +CREATE VIEW [dbo].[VwCustomers] + AS + SELECT Name, Address, CustomerId + FROM dbo.Customers + WHERE (Name LIKE '%e%') + +GO + +CREATE TABLE [dbo].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null); +GO +CREATE CLUSTERED INDEX [ci_azure_fixup_dbo_SchemaTable] ON [dbo].[SchemaTable] ( [Id] ASC) +GO +INSERT INTO [dbo].[SchemaTable] VALUES (1, 'Pass') +GO + +CREATE SCHEMA [test] AUTHORIZATION [dbo] +GO + +CREATE TABLE [test].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null, [Optional] nvarchar(100)); +GO +CREATE CLUSTERED INDEX [ci_azure_fixup_test_SchemaTable] ON [test].[SchemaTable] ( [Id] ASC) +GO +INSERT INTO [test].[SchemaTable] VALUES (2, 'Pass', null) +GO + +CREATE PROCEDURE [dbo].[GetCustomers] +AS +BEGIN + SET NOCOUNT ON; + SELECT * FROM Customers + ORDER BY CustomerId +END +GO +CREATE PROCEDURE [dbo].[CreateCustomer] +AS +BEGIN + SET NOCOUNT ON; + INSERT INTO Customers (Name, Address) VALUES ('Proccy','ProcAddress'); + SELECT * FROM Customers WHERE CustomerId = SCOPE_IDENTITY() +END +GO +CREATE PROCEDURE [dbo].[AddCustomer] (@Name nvarchar(100), @Address nvarchar(200)) +AS +BEGIN + SET NOCOUNT ON; + INSERT INTO Customers (Name, Address) VALUES (@Name, @Address); + SELECT * FROM Customers WHERE CustomerId = SCOPE_IDENTITY() +END +GO + +CREATE PROCEDURE [dbo].[GetCustomerAndOrders] (@CustomerId int) +AS +BEGIN + SET NOCOUNT ON; + SELECT * FROM Customers WHERE CustomerId = @CustomerId; + SELECT * FROM Orders WHERE CustomerId = @CustomerId; +END +GO +CREATE PROCEDURE [dbo].[GetCustomerCount] +AS +BEGIN + SET NOCOUNT ON; + RETURN 1 +END +GO +CREATE PROCEDURE [dbo].[SchemaProc] +AS +BEGIN + SET NOCOUNT ON; + SELECT 'dbo.SchemaProc' AS [Actual] +END +GO +CREATE PROCEDURE [test].[SchemaProc] +AS +BEGIN + SET NOCOUNT ON; + SELECT 'test.SchemaProc' AS [Actual] +END +GO +CREATE PROCEDURE ReturnStrings(@Strings AS [dbo].[StringList] READONLY) +AS +SELECT Value FROM @Strings +GO +CREATE PROCEDURE [dbo].[GetCustomerCountAsOutput](@Count INT = NULL OUTPUT) +AS +BEGIN + SET NOCOUNT ON; + SET @Count = 42 +END +GO +CREATE FUNCTION [dbo].[VarcharAndReturnInt] (@AValue varchar(50)) RETURNS INT AS BEGIN + IF ISNUMERIC(@AValue) = 1 + BEGIN + RETURN cast (@AValue as int) + END + RETURN 42 +END +GO +CREATE TRIGGER [test].trg_schematable_after_insert ON [test].[SchemaTable] AFTER INSERT +AS +BEGIN + SET NOCOUNT ON; + UPDATE [test].[SchemaTable] SET [Optional] = 'Modified By Trigger' WHERE [Id] in (select Id from inserted) +END +GO +CREATE TABLE [dbo].[GroupTestMaster] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [Name] NVARCHAR (100) NOT NULL + ); + +ALTER TABLE [dbo].[GroupTestMaster] + ADD CONSTRAINT [PK_GroupTestMaster] PRIMARY KEY CLUSTERED ([Id] ASC) ; +GO +CREATE TABLE [dbo].[GroupTestDetail] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [Date] DATETIME NOT NULL, + [Number] INT NOT NULL, + [MasterId] INT NOT NULL + ); + +ALTER TABLE [dbo].[GroupTestDetail] + ADD CONSTRAINT [PK_GroupTestDetail] PRIMARY KEY CLUSTERED ([Id] ASC) ; + +ALTER TABLE [dbo].[GroupTestDetail] WITH NOCHECK + ADD CONSTRAINT [FK_GroupTestDetail_GroupTestMaster] FOREIGN KEY ([MasterId]) REFERENCES [dbo].[GroupTestMaster] ([Id]) ON DELETE NO ACTION ON UPDATE NO ACTION; +GO +INSERT INTO [dbo].[GroupTestMaster] VALUES ('One') +INSERT INTO [dbo].[GroupTestMaster] VALUES ('Two') +INSERT INTO [dbo].[GroupTestDetail] VALUES ('1999-1-1',1,1) +INSERT INTO [dbo].[GroupTestDetail] VALUES ('2000-1-1',2,1) +INSERT INTO [dbo].[GroupTestDetail] VALUES ('2001-1-1',3,1) +INSERT INTO [dbo].[GroupTestDetail] VALUES ('2010-1-1',2,2) +INSERT INTO [dbo].[GroupTestDetail] VALUES ('2011-1-1',3,2) + +CREATE TABLE [dbo].[CompoundKeyMaster]( + [IdPart1] [int] NOT NULL, + [IdPart2] [int] NOT NULL, + [Description] [nvarchar](50) NOT NULL, +CONSTRAINT [PK_CompoundKeyMaster] PRIMARY KEY CLUSTERED +( + [IdPart1] ASC, + [IdPart2] ASC +)) + +CREATE TABLE [dbo].[CompoundKeyDetail]( + [Id] [int] NOT NULL, + [MasterIdPart1] [int] NOT NULL, + [MasterIdPart2] [int] NOT NULL, + [Value] [int] NOT NULL, +CONSTRAINT [PK_CompoundKeyDetail] PRIMARY KEY CLUSTERED +( + [Id] ASC +)) + +INSERT INTO [dbo].[CompoundKeyMaster] (IdPart1, IdPart2,[Description]) VALUES (1,1,'Original') +INSERT INTO [dbo].[CompoundKeyDetail] (Id, MasterIdPart1, MasterIdPart2, Value) VALUES (1,1,1,1) + +ALTER TABLE [dbo].[CompoundKeyDetail] WITH CHECK ADD CONSTRAINT [FK_CompoundKeyDetail_CompoundKeyMaster] FOREIGN KEY([MasterIdPart1], [MasterIdPart2]) +REFERENCES [dbo].[CompoundKeyMaster] ([IdPart1], [IdPart2]) + +ALTER TABLE [dbo].[CompoundKeyDetail] CHECK CONSTRAINT [FK_CompoundKeyDetail_CompoundKeyMaster] \ No newline at end of file diff --git a/Simple.Data.SqlTest/SchemaQualifiedTests.cs b/Simple.Data.SqlTest/SchemaQualifiedTests.cs new file mode 100644 index 00000000..34324b5a --- /dev/null +++ b/Simple.Data.SqlTest/SchemaQualifiedTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.SqlTest +{ + using NUnit.Framework; + + [TestFixture] + class SchemaQualifiedTests + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void TestFindAllByIdWithSchemaQualification() + { + var db = DatabaseHelper.Open(); + var dboCount = db.dbo.SchemaTable.FindAllById(1).ToList().Count; + var testCount = db.test.SchemaTable.FindAllById(1).ToList().Count; + Assert.AreEqual(1, dboCount); + Assert.AreEqual(0, testCount); + } + + [Test] + public void TestFindWithSchemaQualification() + { + var db = DatabaseHelper.Open(); + + var dboActual = db.dbo.SchemaTable.FindById(1); + var testActual = db.test.SchemaTable.FindById(1); + + Assert.IsNotNull(dboActual); + Assert.AreEqual("Pass", dboActual.Description); + Assert.IsNull(testActual); + } + + [Test] + public void QueryWithSchemaQualifiedTableName() + { + var db = DatabaseHelper.Open(); + var result = db.test.SchemaTable.QueryById(2) + .Select(db.test.SchemaTable.Id, + db.test.SchemaTable.Description) + .Single(); + Assert.AreEqual(2, result.Id); + Assert.AreEqual("Pass", result.Description); + } + + [Test] + public void QueryWithSchemaQualifiedTableNameAndAliases() + { + var db = DatabaseHelper.Open(); + var result = db.test.SchemaTable.QueryById(2) + .Select(db.test.SchemaTable.Id.As("This"), + db.test.SchemaTable.Description.As("That")) + .Single(); + Assert.AreEqual(2, result.This); + Assert.AreEqual("Pass", result.That); + } + + } +} diff --git a/Simple.Data.SqlTest/SchemaTests/DatabaseSchemaTests.cs b/Simple.Data.SqlTest/SchemaTests/DatabaseSchemaTests.cs index 0466c0fc..6b321da9 100644 --- a/Simple.Data.SqlTest/SchemaTests/DatabaseSchemaTests.cs +++ b/Simple.Data.SqlTest/SchemaTests/DatabaseSchemaTests.cs @@ -78,7 +78,8 @@ public void TestNewTableIsAddedToSchemaAfterReset() cmd.CommandText = @"IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[RuntimeTable]') AND type in (N'U')) DROP TABLE [dbo].[RuntimeTable]"; cmd.ExecuteNonQuery(); - cmd.CommandText = "CREATE TABLE [dbo].[RuntimeTable] ([Id] int)"; + cmd.CommandText = @"CREATE TABLE [dbo].[RuntimeTable] ([Id] int, + CONSTRAINT [PK_RuntimeTable] PRIMARY KEY CLUSTERED ( [Id] ASC))"; cmd.ExecuteNonQuery(); } diff --git a/Simple.Data.SqlTest/SetupFixture.cs b/Simple.Data.SqlTest/SetupFixture.cs index fe5b6b84..3c71b48d 100644 --- a/Simple.Data.SqlTest/SetupFixture.cs +++ b/Simple.Data.SqlTest/SetupFixture.cs @@ -8,13 +8,18 @@ namespace Simple.Data.SqlTest { + using System.Diagnostics; + [SetUpFixture] public class SetupFixture { [SetUp] public void CreateStoredProcedures() { - using (var cn = new SqlConnection(Properties.Settings.Default.ConnectionString)) + var provider = new SqlServer.SqlConnectionProvider(); + Trace.Write("Loaded provider " + provider.GetType().Name); + + using (var cn = new SqlConnection(DatabaseHelper.ConnectionString)) { cn.Open(); using (var cmd = cn.CreateCommand()) diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index f5b82a41..d7c35ebf 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -40,6 +40,11 @@ + + False + ..\Assemblies\Microsoft.SqlServer.Types.dll + True + 3.5 @@ -62,9 +67,13 @@ + + + + diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index 4a490af7..d9820aba 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -4,6 +4,7 @@ using System.Text; using System.Text.RegularExpressions; using NUnit.Framework; +using Simple.Data.Ado; using Simple.Data.SqlServer; namespace Simple.Data.SqlTest @@ -17,7 +18,7 @@ public class SqlQueryPagerTest public void ShouldApplyLimitUsingTop() { var sql = "select a,b,c from d where a = 1 order by c"; - var expected = new[]{ "select top 5 a,b,c from d where a = 1 order by c"}; + var expected = new[] { "select top 5 a,b,c from d where a = 1 order by c" }; var pagedSql = new SqlQueryPager().ApplyLimit(sql, 5); var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); @@ -26,59 +27,101 @@ public void ShouldApplyLimitUsingTop() } [Test] - public void ShouldApplyPagingUsingOrderBy() + public void ShouldApplyLimitUsingTopWithDistinct() { - var sql = "select a,b,c from d where a = 1 order by c"; - var expected = new[]{ - "with __data as (select a,b,c, row_number() over(order by c) as [_#_] from d where a = 1)" - + " select a,b,c from __data where [_#_] between 6 and 15"}; + var sql = "select distinct a,b,c from d where a = 1 order by c"; + var expected = new[] { "select distinct top 5 a,b,c from d where a = 1 order by c" }; - var pagedSql = new SqlQueryPager().ApplyPaging(sql, 5, 10); + var pagedSql = new SqlQueryPager().ApplyLimit(sql, 5); var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); Assert.IsTrue(expected.SequenceEqual(modified)); } [Test] - public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() + public void ShouldApplyPagingUsingOrderBy() { - var sql = "select a,b,c from d where a = 1"; + var sql = "select [dbo].[d].[a],[dbo].[d].[b],[dbo].[d].[c] from [dbo].[d] where [dbo].[d].[a] = 1 order by [dbo].[d].[c]"; var expected = new[]{ - "with __data as (select a,b,c, row_number() over(order by a) as [_#_] from d where a = 1)" - + " select a,b,c from __data where [_#_] between 11 and 30"}; + "with __data as (select [dbo].[d].[a], row_number() over(order by [dbo].[d].[c]) as [_#_] from [dbo].[d] where [dbo].[d].[a] = 1)" + + " select [dbo].[d].[a],[dbo].[d].[b],[dbo].[d].[c] from __data join [dbo].[d] on [dbo].[d].[a] = __data.[a] where [dbo].[d].[a] = 1 and [_#_] between 6 and 15"}; - var pagedSql = new SqlQueryPager().ApplyPaging(sql, 10, 20); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[d].[a]"}, 5, 10); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - Assert.IsTrue(expected.SequenceEqual(modified)); + Assert.AreEqual(expected[0], modified[0]); + } + + [Test] + public void ShouldApplyPagingUsingOrderByKeysIfNotAlreadyOrdered() + { + var sql = "select [dbo].[d].[a],[dbo].[d].[b],[dbo].[d].[c] from [dbo].[d] where [dbo].[d].[a] = 1"; + var expected = new[]{ + "with __data as (select [dbo].[d].[a], row_number() over(order by [dbo].[d].[a]) as [_#_] from [dbo].[d] where [dbo].[d].[a] = 1)" + + " select [dbo].[d].[a],[dbo].[d].[b],[dbo].[d].[c] from __data join [dbo].[d] on [dbo].[d].[a] = __data.[a] where [dbo].[d].[a] = 1 and [_#_] between 11 and 30"}; + + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[d].[a]"}, 10, 20); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); + + Assert.AreEqual(expected[0], modified[0]); } [Test] public void ShouldCopeWithAliasedColumns() { - var sql = "select [a],[b] as [foo],[c] from [d] where [a] = 1"; + var sql = "select [dbo].[d].[a],[dbo].[d].[b] as [foo],[dbo].[d].[c] from [dbo].[d] where [dbo].[d].[a] = 1"; var expected =new[]{ - "with __data as (select [a],[b] as [foo],[c], row_number() over(order by [a]) as [_#_] from [d] where [a] = 1)" - + " select [a],[foo],[c] from __data where [_#_] between 21 and 25"}; + "with __data as (select [dbo].[d].[a], row_number() over(order by [dbo].[d].[a]) as [_#_] from [dbo].[d] where [dbo].[d].[a] = 1)" + + " select [dbo].[d].[a],[dbo].[d].[b] as [foo],[dbo].[d].[c] from __data join [dbo].[d] on [dbo].[d].[a] = __data.[a] where [dbo].[d].[a] = 1 and [_#_] between 21 and 25"}; - var pagedSql = new SqlQueryPager().ApplyPaging(sql, 20, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[]{"[dbo].[d].[a]"}, 20, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - Assert.IsTrue(expected.SequenceEqual(modified)); + Assert.AreEqual(expected[0], modified[0]); } [Test] - public void ShouldCopeWithAliasedDefaultSortColumn() + public void ShouldCopeWithColumnsThatEndInFrom() { - var sql = "select [a] as [foo],[b],[c] from [d] where [a] = 1"; - var expected = new[]{ - "with __data as (select [a] as [foo],[b],[c], row_number() over(order by [a]) as [_#_] from [d] where [a] = 1)" - + " select [foo],[b],[c] from __data where [_#_] between 31 and 40"}; + const string sql = @"SELECT [dbo].[PromoPosts].[Id],[dbo].[PromoPosts].[ActiveFrom],[dbo].[PromoPosts].[ActiveTo],[dbo].[PromoPosts].[Created],[dbo].[PromoPosts].[Updated] + from [dbo].[PromoPosts] + ORDER BY [dbo].[PromoPosts].[ActiveFrom]"; - var pagedSql = new SqlQueryPager().ApplyPaging(sql, 30, 10); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + var expected = @"with __data as (select [dbo].[promoposts].[id], row_number() over(order by [dbo].[promoposts].[activefrom]) as [_#_] from [dbo].[promoposts]) select [dbo].[promoposts].[id],[dbo].[promoposts].[activefrom],[dbo].[promoposts].[activeto],[dbo].[promoposts].[created],[dbo].[promoposts].[updated] from __data join [dbo].[promoposts] on [dbo].[promoposts].[id] = __data.[id] and [_#_] between 1 and 25"; + expected = expected.ToLowerInvariant(); - Assert.IsTrue(expected.SequenceEqual(modified)); + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[PromoPosts].[Id]"}, 0, 25).Single(); + var modified = Normalize.Replace(pagedSql, " ").ToLowerInvariant(); + Assert.AreEqual(expected, modified); + } + + [Test] + public void ShouldExcludeLeftJoinedTablesFromSubSelect() + { + const string sql = @"SELECT [dbo].[MainClass].[ID], + [dbo].[MainClass].[SomeProperty], + [dbo].[MainClass].[SomeProperty2], + [dbo].[MainClass].[SomeProperty3], + [dbo].[MainClass].[SomeProperty4], + [dbo].[ChildClass].[ID] AS [__withn__ChildClass__ID], + [dbo].[ChildClass].[SomeProperty] AS [__withn__ChildClass__SomeProperty], + [dbo].[ChildClass].[SomeProperty2] AS [__withn__ChildClass__SomeProperty2] FROM [dbo].[MainClass] LEFT JOIN [dbo].[JoinTable] ON ([dbo].[MainClass].[ID] = [dbo].[JoinTable].[MainClassID]) LEFT JOIN [dbo].[ChildClass] ON ([dbo].[ChildClass].[ID] = [dbo].[JoinTable].[ChildClassID]) WHERE ([dbo].[MainClass].[SomeProperty] > @p1 AND [dbo].[MainClass].[SomeProperty] <= @p2)"; + + const string expected = @"with __data as (select [dbo].[promoposts].[id], row_number() over(order by [dbo].[promoposts].[id]) as [_#_] from [dbo].[mainclass] where ([dbo].[mainclass].[someproperty] > @p1 and [dbo].[mainclass].[someproperty] <= @p2)) select [dbo].[mainclass].[id], [dbo].[mainclass].[someproperty], [dbo].[mainclass].[someproperty2], [dbo].[mainclass].[someproperty3], [dbo].[mainclass].[someproperty4], [dbo].[childclass].[id] as [__withn__childclass__id], [dbo].[childclass].[someproperty] as [__withn__childclass__someproperty], [dbo].[childclass].[someproperty2] as [__withn__childclass__someproperty2] from __data join [dbo].[promoposts] on [dbo].[promoposts].[id] = __data.[id]from [dbo].[mainclass] left join [dbo].[jointable] on ([dbo].[mainclass].[id] = [dbo].[jointable].[mainclassid]) left join [dbo].[childclass] on ([dbo].[childclass].[id] = [dbo].[jointable].[childclassid]) where ([dbo].[mainclass].[someproperty] > @p1 and [dbo].[mainclass].[someproperty] <= @p2) and [_#_] between 1 and 25"; + + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[PromoPosts].[Id]"}, 0, 25).Single(); + var modified = Normalize.Replace(pagedSql, " ").ToLowerInvariant(); + Assert.AreEqual(expected, modified); + } + + [Test] + public void ShouldThrowIfTableHasNoPrimaryKey([Values(null, new string[0])]string[] keys) + { + var sql = "select [dbo].[d].[a] from [dbo].[b]"; + + Assert.Throws( + () => new SqlQueryPager().ApplyPaging(sql, keys, 5, 10).ToList() + ); } } } diff --git a/Simple.Data.SqlTest/TransactionTests.cs b/Simple.Data.SqlTest/TransactionTests.cs index 3a472447..d25ef1ae 100644 --- a/Simple.Data.SqlTest/TransactionTests.cs +++ b/Simple.Data.SqlTest/TransactionTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using NUnit.Framework; +using Simple.Data.Ado; namespace Simple.Data.SqlTest { @@ -54,6 +55,19 @@ public void TestRollback() Assert.AreEqual(1, db.OrderItems.All().ToList().Count); } + [Test] + public void TestWithOptionsTransaction() + { + var dbWithOptions = DatabaseHelper.Open().WithOptions(new AdoOptions(commandTimeout: 60000)); + using (var tx = dbWithOptions.BeginTransaction()) + { + tx.Orders.Insert(CustomerId: 1, OrderDate: DateTime.Today); + tx.Rollback(); + } + + Assert.Pass(); + } + [Test] public void TestRollbackOnProcedure() { diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index 87b9326c..17de059f 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -1,7 +1,6 @@ using System.Dynamic; using System.Linq; using NUnit.Framework; -using Simple.Data.SqlTest.Resources; namespace Simple.Data.SqlTest { @@ -87,6 +86,33 @@ public void TestUpdateWithVarBinaryMaxColumn() Assert.IsTrue(newData.SequenceEqual(blob.Data)); } + [Test] + public void TestUpdateWithJoinCriteria() + { + var db = DatabaseHelper.Open(); + db.Customers.UpdateAll(db.Customers.Orders.OrderId == 1, Name: "Updated"); + var customer = db.Customers.Get(1); + Assert.AreEqual("Updated", customer.Name); + } + + [Test] + public void TestUpdateAllWithNoMatchingRows() + { + var db = DatabaseHelper.Open(); + db.test.SchemaTable.UpdateAll(db.test.SchemaTable.Id == 1138, Description: "Updated"); + var test = db.test.SchemaTable.FindById(1138); + Assert.IsNull(test); + } + + [Test] + public void TestUpdateWithJoinCriteriaOnCompoundKeyTable() + { + var db = DatabaseHelper.Open(); + db.CompoundKeyMaster.UpdateAll(db.CompoundKeyMaster.CompoundKeyDetail.Value == 1, Description: "Updated"); + var record = db.CompoundKeyMaster.Get(1, 1); + Assert.AreEqual("Updated", record.Description); + } + [Test] public void ToListShouldExecuteQuery() { @@ -97,7 +123,56 @@ public void ToListShouldExecuteQuery() customer.Address = "Updated"; } - db.Customers.Update(customers); + Assert.DoesNotThrow(() => + db.Customers.Update(customers)); + } + + [Test] + public void TestUpdateWithTimestamp() + { + var db = DatabaseHelper.Open(); + var row = db.TimestampTest.Insert(Description: "Inserted"); + row.Description = "Updated"; + db.TimestampTest.Update(row); + row = db.TimestampTest.Get(row.Id); + Assert.AreEqual("Updated", row.Description); + } + + [Test] + public void TestUpdateByInputIsNotMutated() + { + var db = DatabaseHelper.Open(); + var user = new Dictionary + { + {"Id", 0}, + {"Age", 1}, + {"Name", "X"}, + {"Password", "P"} + }; + + user["Id"] = db.Users.Insert(user).Id; + + db.Users.UpdateById(user); + + Assert.AreEqual(4, user.Keys.Count); + } + + [Test] + public void TestUpdatingACriteriaColumn() + { + var db = DatabaseHelper.Open(); + var user = db.Users.Insert(Age: 42, Name: "Z1", Password: "argh"); + db.Users.UpdateAll(db.Users.Name == "Z1", Name: "1Z"); + } + + [Test] + public void TestUpdateWithOriginalUsingAnonymousObjects() + { + var db = DatabaseHelper.Open(); + var user = db.Users.Insert(Age: 54, Name: "YZ1", Password: "argh"); + db.Users.Update(new {Name = "2YZ"}, new {Name = "YZ1"}); + var actual = db.Users.FindById(user.Id); + Assert.AreEqual("2YZ", actual.Name); } } } \ No newline at end of file diff --git a/Simple.Data.SqlTest/UpsertTests.cs b/Simple.Data.SqlTest/UpsertTests.cs index c7fbd316..b62a0bd2 100644 --- a/Simple.Data.SqlTest/UpsertTests.cs +++ b/Simple.Data.SqlTest/UpsertTests.cs @@ -176,6 +176,7 @@ public void TestMultiUpsertWithStaticTypeObjectsAndNoReturn() new User { Name = "Wowbagger", Password = "teatime", Age = int.MaxValue } }; + //IList actuals = db.Users.Upsert(users).ToList(); db.Users.Upsert(users); var slartibartfast = db.Users.FindByName("Slartibartfast"); @@ -401,5 +402,61 @@ public void TestUpsertWithVarBinaryMaxColumn() blob = db.Blobs.FindById(1); Assert.IsTrue(image.SequenceEqual(blob.Data)); } + + [Test] + public void TestUpsertWithSingleArgumentAndExistingObject() + { + var db = DatabaseHelper.Open(); + + var actual = db.Users.UpsertById(Id: 1); + + Assert.IsNotNull(actual); + Assert.AreEqual(1, actual.Id); + Assert.IsNotNull(actual.Name); + } + + [Test] + public void TestUpsertUserBySecondaryField() + { + var db = DatabaseHelper.Open(); + + var id = db.Users.UpsertByName(new User() { Age = 20, Name = "Black sheep", Password = "Bah" }).Id; + User actual = db.Users.FindById(id); + + Assert.AreEqual(id, actual.Id); + Assert.AreEqual("Black sheep", actual.Name); + Assert.AreEqual("Bah", actual.Password); + Assert.AreEqual(20, actual.Age); + } + + [Test] + public void TestUpsertUserByTwoSecondaryFields() + { + var db = DatabaseHelper.Open(); + + var id = db.Users.UpsertByNameAndPassword(new User() { Age = 20, Name = "Black sheep", Password = "Bah" }).Id; + User actual = db.Users.FindById(id); + + Assert.AreEqual(id, actual.Id); + Assert.AreEqual("Black sheep", actual.Name); + Assert.AreEqual("Bah", actual.Password); + Assert.AreEqual(20, actual.Age); + } + + [Test] + public void TestUpsertExisting() + { + var db = DatabaseHelper.Open(); + + var id = db.Users.UpsertByNameAndPassword(new User() { Age = 20, Name = "Black sheep", Password = "Bah" }).Id; + db.Users.UpsertById(new User() { Id = id, Age = 12, Name = "Dog", Password = "Bark" }); + + User actual = db.Users.FindById(id); + + Assert.AreEqual(id, actual.Id); + Assert.AreEqual("Dog", actual.Name); + Assert.AreEqual("Bark", actual.Password); + Assert.AreEqual(12, actual.Age); + } } } \ No newline at end of file diff --git a/Simple.Data.SqlTest/WeirdTypeTest.cs b/Simple.Data.SqlTest/WeirdTypeTest.cs new file mode 100644 index 00000000..f2958544 --- /dev/null +++ b/Simple.Data.SqlTest/WeirdTypeTest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.SqlTest +{ + using NUnit.Framework; + + [TestFixture] + public class WeirdTypeTest + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void TestInsertOnGeography() + { + var db = DatabaseHelper.Open(); + var actual = db.GeographyTest.Insert(Description: "Test"); + Assert.IsNotNull(actual); + } + [Test] + public void TestInsertOnGeometry() + { + var db = DatabaseHelper.Open(); + var actual = db.GeometryTest.Insert(Description: "Test"); + Assert.IsNotNull(actual); + } + [Test] + public void TestInsertOnHierarchyId() + { + var db = DatabaseHelper.Open(); + var actual = db.HierarchyIdTest.Insert(Description: "Test"); + Assert.IsNotNull(actual); + } + } +} diff --git a/Simple.Data.UnitTest/BeginTransactionWithIsolataionLevelTest.cs b/Simple.Data.UnitTest/BeginTransactionWithIsolataionLevelTest.cs new file mode 100644 index 00000000..408b082a --- /dev/null +++ b/Simple.Data.UnitTest/BeginTransactionWithIsolataionLevelTest.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Data; +using NUnit.Framework; + +namespace Simple.Data.UnitTest +{ + [TestFixture] + class BeginTransactionWithIsolataionLevelTest + { + [Test] + public void TransactionsGetUnspecifiedIsolationLevelByDefault() + { + var adapter = new StubAdapterWithTransaction(); + Database db = new Database(adapter); + db.BeginTransaction(); + + Assert.AreEqual(IsolationLevel.Unspecified, adapter.IsolationLevel); + } + + [Test] + public void TransactionsGetExplicitlySetIsolationLevel() + { + var adapter = new StubAdapterWithTransaction(); + Database db = new Database(adapter); + db.BeginTransaction(IsolationLevel.Serializable); + + Assert.AreEqual(IsolationLevel.Serializable, adapter.IsolationLevel); + } + + [Test] + public void NamedTransactionsGetUnspecifiedIsolationLevel() + { + var adapter = new StubAdapterWithTransaction(); + Database db = new Database(adapter); + db.BeginTransaction("tran name"); + + Assert.AreEqual(IsolationLevel.Unspecified, adapter.IsolationLevel); + } + } + + class StubAdapterWithTransaction : StubAdapter, IAdapterWithTransactions + { + public string TransactionName; + public IsolationLevel IsolationLevel; + + public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) + { + this.IsolationLevel = isolationLevel; + return null; + } + + public IAdapterTransaction BeginTransaction(string name, IsolationLevel isolationLevel = IsolationLevel.Unspecified) + { + this.IsolationLevel = isolationLevel; + this.TransactionName = name; + return null; + } + + #region IAdapterWithTransactions - not implementead + + public IEnumerable> Find(string tableName, SimpleExpression criteria, IAdapterTransaction transaction) + { + throw new NotImplementedException(); + } + + public IDictionary Insert(string tableName, IDictionary data, IAdapterTransaction transaction, bool resultRequired) + { + throw new NotImplementedException(); + } + + public IEnumerable> InsertMany(string tableName, IEnumerable> data, IAdapterTransaction transaction, Func, Exception, bool> onError, bool resultRequired) + { + throw new NotImplementedException(); + } + + public int Update(string tableName, IDictionary data, SimpleExpression criteria, IAdapterTransaction transaction) + { + throw new NotImplementedException(); + } + + public int Delete(string tableName, SimpleExpression criteria, IAdapterTransaction transaction) + { + throw new NotImplementedException(); + } + + public int UpdateMany(string tableName, IEnumerable> dataList, IAdapterTransaction adapterTransaction) + { + throw new NotImplementedException(); + } + + public int UpdateMany(string tableName, IEnumerable> dataList, IAdapterTransaction adapterTransaction, IList keyFields) + { + throw new NotImplementedException(); + } + + public int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames, IAdapterTransaction adapterTransaction) + { + throw new NotImplementedException(); + } + + public IDictionary Get(string tableName, IAdapterTransaction transaction, params object[] parameterValues) + { + throw new NotImplementedException(); + } + + public IEnumerable> RunQuery(SimpleQuery query, IAdapterTransaction transaction, out IEnumerable unhandledClauses) + { + throw new NotImplementedException(); + } + + #endregion + } + +} \ No newline at end of file diff --git a/Simple.Data.UnitTest/BufferedEnumerableTest.cs b/Simple.Data.UnitTest/BufferedEnumerableTest.cs deleted file mode 100644 index f8dc3c4c..00000000 --- a/Simple.Data.UnitTest/BufferedEnumerableTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using NUnit.Framework; - -namespace Simple.Data.UnitTest -{ - [TestFixture] - public class BufferedEnumerableTest - { - [Test] - public void ShouldCallCleanup() - { - // Arrange - bool cleanedUp = false; - - var list = BufferedEnumerable.Create("ABC".ToIterator(), () => cleanedUp = true) - .ToList(); - - // Act - Assert.AreEqual(3, list.Count); - - SpinWait.SpinUntil(() => cleanedUp, 1000); - - Assert.True(cleanedUp); - } - } -} diff --git a/Simple.Data.UnitTest/ConcreteTypeCreatorTest.cs b/Simple.Data.UnitTest/ConcreteTypeCreatorTest.cs index 05043697..41f22189 100644 --- a/Simple.Data.UnitTest/ConcreteTypeCreatorTest.cs +++ b/Simple.Data.UnitTest/ConcreteTypeCreatorTest.cs @@ -45,6 +45,18 @@ public void CanConvertInt32ToEnum() Assert.IsInstanceOf(actual); Assert.AreEqual(expected, ((Int32ToEnum)actual).Value); } + + [Test] + public void CanConvertInt32ToNullableEnum() + { + Int32ToNullableEnum.Numbers? expected = Int32ToNullableEnum.Numbers.One; + var source = new Dictionary { { "Value", (int)expected } }; + var target = ConcreteTypeCreator.Get(typeof(Int32ToNullableEnum)); + object actual; + Assert.IsTrue(target.TryCreate(source, out actual)); + Assert.IsInstanceOf(actual); + Assert.AreEqual(expected, ((Int32ToNullableEnum)actual).Value); + } [Test] public void CanConvertStringToEnum() @@ -63,6 +75,16 @@ public class DecimalToDouble public double Value { get; set; } } + public class Int32ToNullableEnum + { + public Numbers? Value { get; set; } + public enum Numbers + { + One = 1, + Two = 2 + } + } + public class DateTimeToNullableDateTime { public DateTime? Value { get; set; } diff --git a/Simple.Data.UnitTest/DictionaryQueryRunnerTest.cs b/Simple.Data.UnitTest/DictionaryQueryRunnerTest.cs index eb1933c9..f16162aa 100644 --- a/Simple.Data.UnitTest/DictionaryQueryRunnerTest.cs +++ b/Simple.Data.UnitTest/DictionaryQueryRunnerTest.cs @@ -14,7 +14,7 @@ public class DictionaryQueryRunnerTest [Test] public void DistinctShouldRemoveDuplicateRows() { - var runner = new DictionaryQueryRunner(DuplicatingSource(), new DistinctClause()); + var runner = new DictionaryQueryRunner("FooTable", DuplicatingSource(), new DistinctClause()); var actual = runner.Run().ToList(); Assert.AreEqual(2, actual.Count); Assert.AreEqual(1, actual.Count(d => d.ContainsKey("Foo") && (string)d["Foo"] == "bar")); @@ -24,7 +24,7 @@ public void DistinctShouldRemoveDuplicateRows() [Test] public void ShouldNotRemoveDistinctRows() { - var runner = new DictionaryQueryRunner(NonDuplicatingSource(), new DistinctClause()); + var runner = new DictionaryQueryRunner("FooTable", NonDuplicatingSource(), new DistinctClause()); var actual = runner.Run().ToList(); Assert.AreEqual(2, actual.Count); Assert.AreEqual(1, actual.Count(d => d.ContainsKey("Foo") && (string)d["Foo"] == "bar")); @@ -34,7 +34,7 @@ public void ShouldNotRemoveDistinctRows() [Test] public void SkipShouldSkip() { - var runner = new DictionaryQueryRunner(SkipTakeSource(), new SkipClause(1)); + var runner = new DictionaryQueryRunner("FooTable", SkipTakeSource(), new SkipClause(1)); var actual = runner.Run().ToList(); Assert.AreEqual(2, actual.Count); Assert.AreEqual(1, actual[0]["Row"]); @@ -44,7 +44,7 @@ public void SkipShouldSkip() [Test] public void TakeShouldTake() { - var runner = new DictionaryQueryRunner(SkipTakeSource(), new TakeClause(2)); + var runner = new DictionaryQueryRunner("FooTable", SkipTakeSource(), new TakeClause(2)); var actual = runner.Run().ToList(); Assert.AreEqual(2, actual.Count); Assert.AreEqual(0, actual[0]["Row"]); @@ -54,7 +54,7 @@ public void TakeShouldTake() [Test] public void SkipAndTakeShouldSkipAndTake() { - var runner = new DictionaryQueryRunner(SkipTakeSource(), new SkipClause(1), new TakeClause(1)); + var runner = new DictionaryQueryRunner("FooTable", SkipTakeSource(), new SkipClause(1), new TakeClause(1)); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual(1, actual[0]["Row"]); @@ -64,7 +64,7 @@ public void SkipAndTakeShouldSkipAndTake() public void SkipAndTakeWithCountShouldSkipAndTakeAndGiveCount() { int count = 0; - var runner = new DictionaryQueryRunner(SkipTakeSource(), new WithCountClause(n => count = n), new SkipClause(1), new TakeClause(1)); + var runner = new DictionaryQueryRunner("FooTable", SkipTakeSource(), new WithCountClause(n => count = n), new SkipClause(1), new TakeClause(1)); var actual = runner.Run().ToList(); Assert.AreEqual(3, count); Assert.AreEqual(1, actual.Count); @@ -76,7 +76,7 @@ public void SelectShouldRestrictColumnList() { var tableRef = new ObjectReference("FooTable"); var selectClause = new SelectClause(new SimpleReference[] { new ObjectReference("Id", tableRef), new ObjectReference("Name", tableRef) }); - var runner = new DictionaryQueryRunner(SelectSource(), selectClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), selectClause); var actual = runner.Run().ToList(); Assert.AreEqual(4, actual.Count); Assert.AreEqual(2, actual[0].Count); @@ -99,7 +99,7 @@ public void SelectLengthShouldUseLengthFunction() var tableRef = new ObjectReference("FooTable"); var function = new FunctionReference("Length", new ObjectReference("Name", tableRef)).As("NameLength"); var selectClause = new SelectClause(new SimpleReference[] { new ObjectReference("Name", tableRef), function }); - var runner = new DictionaryQueryRunner(SelectSource(), selectClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), selectClause); var actual = runner.Run().ToList(); Assert.AreEqual(4, actual.Count); Assert.AreEqual(2, actual[0].Count); @@ -121,7 +121,7 @@ public void BasicWhereEqualShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Name", tableRef) == "Alice"); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Alice", actual[0]["Name"]); @@ -143,7 +143,7 @@ public void WhereNullShouldWorkWhenValueExistsAndIsNull() {"Name", "Dave"}, { "Value", 42 } }, }; - var runner = new DictionaryQueryRunner(data, whereClause); + var runner = new DictionaryQueryRunner("FooTable", data, whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Steve", actual[0]["Name"]); @@ -165,7 +165,7 @@ public void WhereNullShouldWorkWhenValueDoesNotExist() {"Name", "Dave"}, { "Value", 42 } }, }; - var runner = new DictionaryQueryRunner(data, whereClause); + var runner = new DictionaryQueryRunner("FooTable", data, whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Steve", actual[0]["Name"]); @@ -187,7 +187,7 @@ public void WhereEqualWithByteArrayShouldWork() {"Name", "Dave"}, { "Array", new byte[] { 2, 3, 4}} }, }; - var runner = new DictionaryQueryRunner(data, whereClause); + var runner = new DictionaryQueryRunner("FooTable", data, whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Steve", actual[0]["Name"]); @@ -198,7 +198,7 @@ public void BasicWhereNotEqualShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Name", tableRef) != "Alice"); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(3, actual.Count); Assert.False(actual.Any(a => (string)a["Name"] == "Alice")); @@ -220,7 +220,7 @@ public void WhereNotNullShouldWork() {"Name", "Dave"}, { "Value", 42 } }, }; - var runner = new DictionaryQueryRunner(data, whereClause); + var runner = new DictionaryQueryRunner("FooTable", data, whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Dave", actual[0]["Name"]); @@ -242,7 +242,7 @@ public void WhereNotEqualWithByteArrayShouldWork() {"Name", "Dave"}, { "Array", new byte[] { 2, 3, 4}} }, }; - var runner = new DictionaryQueryRunner(data, whereClause); + var runner = new DictionaryQueryRunner("FooTable", data, whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Dave", actual[0]["Name"]); @@ -253,7 +253,7 @@ public void BasicWhereGreaterThanShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Weight", tableRef) > 200M); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("David", actual[0]["Name"]); @@ -264,7 +264,7 @@ public void BasicWhereLessThanShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Weight", tableRef) < 150M); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Alice", actual[0]["Name"]); @@ -275,7 +275,7 @@ public void BasicWhereGreaterThanOrEqualShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Weight", tableRef) >= 250M); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("David", actual[0]["Name"]); @@ -286,7 +286,7 @@ public void BasicWhereLessThanOrEqualShouldWork() { var tableRef = new ObjectReference("FooTable"); var whereClause = new WhereClause(new ObjectReference("Weight", tableRef) <= 100M); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Alice", actual[0]["Name"]); @@ -299,7 +299,7 @@ public void BasicLikeShouldWork() dynamic objRef = new ObjectReference("Name", tableRef); var expression = new SimpleExpression(objRef, new SimpleFunction("like", new[] {"A%"}), SimpleExpressionType.Function); var whereClause = new WhereClause(expression); - var runner = new DictionaryQueryRunner(SelectSource(), whereClause); + var runner = new DictionaryQueryRunner("FooTable", SelectSource(), whereClause); var actual = runner.Run().ToList(); Assert.AreEqual(1, actual.Count); Assert.AreEqual("Alice", actual[0]["Name"]); diff --git a/Simple.Data.UnitTest/DynamicRecordTest.cs b/Simple.Data.UnitTest/DynamicRecordTest.cs index 8cd032ca..dc813546 100644 --- a/Simple.Data.UnitTest/DynamicRecordTest.cs +++ b/Simple.Data.UnitTest/DynamicRecordTest.cs @@ -41,6 +41,17 @@ public void DynamicRecordSetterTest() Assert.AreEqual("Bob", target.Name); } + /// + ///A test for DynamicRecord Constructor + /// + [Test()] + public void DynamicRecordIndexerTest() + { + dynamic target = new SimpleRecord(); + target.Name = "Bob"; + Assert.AreEqual("Bob", target["Name"]); + } + [Test] public void DynamicCastTest() { diff --git a/Simple.Data.UnitTest/PropertySetterBuilderTest.cs b/Simple.Data.UnitTest/PropertySetterBuilderTest.cs new file mode 100644 index 00000000..23c0666b --- /dev/null +++ b/Simple.Data.UnitTest/PropertySetterBuilderTest.cs @@ -0,0 +1,39 @@ +namespace Simple.Data.UnitTest +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class PropertySetterBuilderTest + { + [Test] + public void ConvertsInt32ToNullableEnum() + { + var actual = PropertySetterBuilder.SafeConvertNullable(1); + Assert.True(actual.HasValue); + Assert.AreEqual(TestInt32Enum.One, actual.Value); + } + + [Test] + public void ConvertsByteToNullableEnum() + { + const byte b = 1; + var actual = PropertySetterBuilder.SafeConvertNullable(b); + Assert.True(actual.HasValue); + Assert.AreEqual(TestByteEnum.One, actual.Value); + } + + public enum TestInt32Enum + { + Zero = 0, + One = 1, + Two = 2 + } + public enum TestByteEnum : byte + { + Zero = 0, + One = 1, + Two = 2 + } + } +} diff --git a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj index 801d8bbe..860d6f75 100644 --- a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj +++ b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj @@ -70,8 +70,8 @@ + - @@ -94,6 +94,7 @@ + diff --git a/Simple.Data.UnitTest/SimpleQueryTest.cs b/Simple.Data.UnitTest/SimpleQueryTest.cs index 61861783..31ba421d 100644 --- a/Simple.Data.UnitTest/SimpleQueryTest.cs +++ b/Simple.Data.UnitTest/SimpleQueryTest.cs @@ -168,5 +168,38 @@ public void JoinOnShouldSetAJoin() Assert.AreEqual(quux.foo_id, join.JoinExpression.RightOperand); Assert.AreEqual(SimpleExpressionType.Equal, join.JoinExpression.Type); } + + [Test] + public void ForUpdateShouldAddAClause() + { + var query = new SimpleQuery(null, "foo").ForUpdate(true); + Assert.AreEqual(1, query.Clauses.OfType().Count()); + var forUpdate = query.Clauses.OfType().Single(); + Assert.IsTrue(forUpdate.SkipLockedRows); + } + + [Test] + public void SubsequentCallsToForUpdateShouldReplaceClause() + { + var query = new SimpleQuery(null, "foo").ForUpdate(false); + Assert.AreEqual(1, query.Clauses.OfType().Count()); + var forUpdate = query.Clauses.OfType().Single(); + Assert.IsFalse(forUpdate.SkipLockedRows); + query = query.ForUpdate(true); + Assert.AreEqual(1, query.Clauses.OfType().Count()); + forUpdate = query.Clauses.OfType().Single(); + Assert.IsTrue(forUpdate.SkipLockedRows); + } + + [Test] + public void ClearForUpdateRemovesClause() + { + var query = new SimpleQuery(null, "foo").ForUpdate(false); + Assert.AreEqual(1, query.Clauses.OfType().Count()); + var forUpdate = query.Clauses.OfType().Single(); + Assert.IsFalse(forUpdate.SkipLockedRows); + query = query.ClearForUpdate(); + Assert.AreEqual(0, query.Clauses.OfType().Count()); + } } } diff --git a/Simple.Data.UnitTest/SimpleResultSetTest.cs b/Simple.Data.UnitTest/SimpleResultSetTest.cs index ac2114b7..9680a628 100644 --- a/Simple.Data.UnitTest/SimpleResultSetTest.cs +++ b/Simple.Data.UnitTest/SimpleResultSetTest.cs @@ -392,10 +392,33 @@ public void CastToGenericCreatesTypedList() Assert.AreEqual(1, converted.Count()); Assert.AreEqual("0", converted.First().Data); } + + [Test] + public void CastToGenericCreatesTypedListWithSubTypes() + { + var firstRecord = new SimpleRecord(new Dictionary + { + { "Data", "First" }, + { "List", new List { "First-One", "First-Two" } } + }); + + var list = new SimpleResultSet(new List { firstRecord }); + + var result = list.Cast().First(); + + Assert.AreEqual(2, result.List.Count); + } } class TestType { public string Data { get; set; } } + + class TestType2 + { + public string Data { get; set; } + + public List List { get; set; } + } } \ No newline at end of file diff --git a/Simple.Data.sln b/Simple.Data.sln index 0f7c3cea..d366f8c9 100644 --- a/Simple.Data.sln +++ b/Simple.Data.sln @@ -1,6 +1,6 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simple.Data", "Simple.Data\Simple.Data.csproj", "{148CEE80-2E84-4ABD-B5AB-20415B2BBD21}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10F43335-5672-4BDA-91BB-5311C2BFA409}" diff --git a/Simple.Data.sln.DotSettings b/Simple.Data.sln.DotSettings new file mode 100644 index 00000000..21350d03 --- /dev/null +++ b/Simple.Data.sln.DotSettings @@ -0,0 +1,7 @@ + + NEXT_LINE_SHIFTED_2 + NEXT_LINE_SHIFTED_2 + NEXT_LINE_SHIFTED_2 + NEXT_LINE + False + False \ No newline at end of file diff --git a/Simple.Data/ActionDisposable.cs b/Simple.Data/ActionDisposable.cs index 4cc40011..2c04d6a8 100644 --- a/Simple.Data/ActionDisposable.cs +++ b/Simple.Data/ActionDisposable.cs @@ -5,7 +5,7 @@ namespace Simple.Data { - public class ActionDisposable : IDisposable + public sealed class ActionDisposable : IDisposable { public static readonly IDisposable NoOp = new ActionDisposable(); private readonly Action _action; @@ -19,7 +19,18 @@ public ActionDisposable(Action action) _action = action ?? (() => { }); } + ~ActionDisposable() + { + Dispose(false); + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) { _action(); } diff --git a/Simple.Data/Adapter.cs b/Simple.Data/Adapter.cs index 03299d67..9d3a1f1b 100644 --- a/Simple.Data/Adapter.cs +++ b/Simple.Data/Adapter.cs @@ -49,6 +49,8 @@ public void Setup(IEnumerable> settings) OnSetup(); } + public OptionsBase Options { get; set; } + /// /// Called when the method is called, after the settings have been set. /// @@ -102,6 +104,7 @@ out IEnumerable /// Inserts a record into the specified "table". /// Name of the table. /// The values to insert. + /// true if the insert call uses a return value; otherwise, false. /// If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. public abstract IDictionary Insert(string tableName, IDictionary data, bool resultRequired); @@ -150,7 +153,7 @@ public virtual IDictionary FindOne(string tableName, SimpleExpre /// A Func to call when there is an error. It this func returns true, carry on, otherwise abort. /// true if the result of the insert is used in code; otherwise, false. /// If possible, return the newly inserted rows, including any automatically-set values such as primary keys or timestamps. - /// This method has a default implementation based on the method. + /// This method has a default implementation based on the method. /// You should override this method if your adapter can optimize the operation. public virtual IEnumerable> InsertMany(string tableName, IEnumerable> dataList, @@ -194,7 +197,6 @@ private void InsertManyWithoutReturn(string tableName, IEnumerable inserted; try { Insert(tableName, row, false); @@ -368,7 +370,7 @@ public OptimizingDelegateFactory OptimizingDelegateFactory private OptimizingDelegateFactory CreateOptimizingDelegateFactory() { - return MefHelper.GetAdjacentComponent(this.GetType()) ?? new DefaultOptimizingDelegateFactory(); + return MefHelper.GetAdjacentComponent(GetType()) ?? new DefaultOptimizingDelegateFactory(); } public virtual IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired) @@ -489,7 +491,18 @@ private static void UpsertManyWithoutResults(string tableName, IEnumerable> settings) diff --git a/Simple.Data/AdapterMethodDynamicInvoker.cs b/Simple.Data/AdapterMethodDynamicInvoker.cs new file mode 100644 index 00000000..15af13e9 --- /dev/null +++ b/Simple.Data/AdapterMethodDynamicInvoker.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data +{ + using System.Dynamic; + using System.Reflection; + + internal class AdapterMethodDynamicInvoker + { + private readonly Adapter _adapter; + + public AdapterMethodDynamicInvoker(Adapter adapter) + { + _adapter = adapter; + } + + public bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + var adapterMethods = _adapter.GetType().GetMethods().Where(m => m.Name == binder.Name).ToList(); + + foreach (var method in adapterMethods) + { + var parameters = method.GetParameters().ToArray(); + if (parameters.Any(p => p.RawDefaultValue != DBNull.Value) && binder.CallInfo.ArgumentNames.Any(s => !string.IsNullOrWhiteSpace(s))) + { + if (TryInvokeMemberWithNamedParameters(binder, args, out result, method, parameters)) + { + return true; + } + } + else + { + if (AreCompatible(parameters, args)) + { + result = method.Invoke(_adapter, args); + return true; + } + } + } + + result = null; + return false; + } + + private bool TryInvokeMemberWithNamedParameters(InvokeMemberBinder binder, object[] args, out object result, + MethodInfo method, ParameterInfo[] parameters) + { + var fixedArgs = new List(); + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].RawDefaultValue == DBNull.Value) + { + fixedArgs.Add(args[i]); + } + else + { + var index = binder.CallInfo.ArgumentNames.IndexOf(parameters[i].Name); + if (index > -1) + { + if (!parameters[i].ParameterType.IsInstanceOfType(args[index])) + { + result = null; + return false; + } + } + else + { + fixedArgs.Add(parameters[i].RawDefaultValue); + } + } + } + + result = method.Invoke(_adapter, fixedArgs.ToArray()); + return true; + } + + private static bool AreCompatible(IList parameters, IList args) + { + if (parameters.Count != args.Count) return false; + for (int i = 0; i < parameters.Count; i++) + { + if (ReferenceEquals(args[i], null)) return !parameters[i].ParameterType.IsValueType; + if (!parameters[i].ParameterType.IsInstanceOfType(args[i])) return false; + } + + return true; + } + } +} diff --git a/Simple.Data/Ado/AdoAdapter.cs b/Simple.Data/Ado/AdoAdapter.cs deleted file mode 100644 index d543687d..00000000 --- a/Simple.Data/Ado/AdoAdapter.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Data; -using System.Data.Common; -using System.Dynamic; -using System.Linq; -using System.Text; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - [Export("Ado", typeof(Adapter))] - internal class AdoAdapter : Adapter, IAdapterWithRelation - { - private IConnectionProvider _connectionProvider; - private DatabaseSchema _schema; - private Lazy _relatedFinder; - - public AdoAdapter() - { - - } - - internal AdoAdapter(IConnectionProvider connectionProvider) - { - _connectionProvider = connectionProvider; - _schema = DatabaseSchema.Get(_connectionProvider); - _relatedFinder = new Lazy(CreateRelatedFinder); - } - - protected override void OnSetup() - { - var settingsKeys = ((IDictionary) Settings).Keys; - if (settingsKeys.Contains("ConnectionString")) - { - _connectionProvider = ProviderHelper.GetProviderByConnectionString(Settings.ConnectionString); - } - else if (settingsKeys.Contains("Filename")) - { - _connectionProvider = ProviderHelper.GetProviderByFilename(Settings.Filename); - } - _schema = DatabaseSchema.Get(_connectionProvider); - _relatedFinder = new Lazy(CreateRelatedFinder); - } - - private AdoAdapterRelatedFinder CreateRelatedFinder() - { - return new AdoAdapterRelatedFinder(this); - } - - public override IEnumerable> Find(string tableName, SimpleExpression criteria) - { - return new AdoAdapterFinder(this).Find(tableName, criteria); - } - - public override IDictionary Insert(string tableName, IDictionary data) - { - return new AdoAdapterInserter(this).Insert(tableName, data); - } - - public override int Update(string tableName, IDictionary data, SimpleExpression criteria) - { - var commandBuilder = new UpdateHelper(_schema).GetUpdateCommand(tableName, data, criteria); - return Execute(commandBuilder); - } - - /// - /// Deletes from the specified table. - /// - /// Name of the table. - /// The expression to use as criteria for the delete operation. - /// The number of records which were deleted. - public override int Delete(string tableName, SimpleExpression criteria) - { - var commandBuilder = new DeleteHelper(_schema).GetDeleteCommand(tableName, criteria); - return Execute(commandBuilder); - } - - /// - /// Gets the names of the fields which comprise the unique identifier for the specified table. - /// - /// Name of the table. - /// A list of field names; an empty list if no key is defined. - public override IEnumerable GetKeyFieldNames(string tableName) - { - return _schema.FindTable(tableName).PrimaryKey.AsEnumerable(); - } - - private int Execute(ICommandBuilder commandBuilder) - { - using (var connection = CreateConnection()) - { - using (var command = commandBuilder.GetCommand(connection)) - { - return TryExecute(connection, command); - } - } - } - - private static int TryExecute(DbConnection connection, IDbCommand command) - { - try - { - connection.Open(); - return command.ExecuteNonQuery(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } - } - - internal DbConnection CreateConnection() - { - return _connectionProvider.CreateConnection(); - } - - internal DatabaseSchema GetSchema() - { - return DatabaseSchema.Get(_connectionProvider); - } - - /// - /// Determines whether a relation is valid. - /// - /// Name of the known table. - /// Name of the table to test. - /// - /// true if there is a valid relation; otherwise, false. - /// - public bool IsValidRelation(string tableName, string relatedTableName) - { - return _relatedFinder.Value.IsValidRelation(tableName, relatedTableName); - } - - /// - /// Finds data from a "table" related to the specified "table". - /// - /// Name of the table. - /// - /// - /// The list of records matching the criteria. If no records are found, return an empty list. - /// When implementing the interface, if relationships are not possible, throw a . - public IEnumerable> FindRelated(string tableName, IDictionary row, string relatedTableName) - { - return _relatedFinder.Value.FindRelated(tableName, row, relatedTableName); - } - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/AdoAdapterException.cs b/Simple.Data/Ado/AdoAdapterException.cs deleted file mode 100644 index d751ad52..00000000 --- a/Simple.Data/Ado/AdoAdapterException.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado -{ - public class AdoAdapterException : AdapterException - { - private readonly string _commandText; - private readonly IDictionary _parameters; - - public AdoAdapterException() : base(typeof(AdoAdapter)) - { - } - - public AdoAdapterException(string message, IDbCommand command) : base(message, typeof(AdoAdapter)) - { - _commandText = command.CommandText; - _parameters = command.Parameters.Cast() - .ToDictionary(p => p.ParameterName, p => p.Value); - } - - public AdoAdapterException(string commandText, IEnumerable> parameters) - :base(typeof(AdoAdapter)) - { - _commandText = commandText; - _parameters = parameters.ToDictionary(); - } - - - public AdoAdapterException(string message) : base(message, typeof(AdoAdapter)) - { - } - - public AdoAdapterException(string message, string commandText, IEnumerable> parameters) - :base(message, typeof(AdoAdapter)) - { - _commandText = commandText; - _parameters = parameters.ToDictionary(); - } - - public AdoAdapterException(string message, Exception inner) : base(message, inner, typeof(AdoAdapter)) - { - } - - public AdoAdapterException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - public IDictionary Parameters - { - get { return _parameters; } - } - - public string CommandText - { - get { return _commandText; } - } - } -} diff --git a/Simple.Data/Ado/AdoAdapterFinder.cs b/Simple.Data/Ado/AdoAdapterFinder.cs deleted file mode 100644 index 251d318c..00000000 --- a/Simple.Data/Ado/AdoAdapterFinder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; - -namespace Simple.Data.Ado -{ - class AdoAdapterFinder - { - private readonly AdoAdapter _adapter; - - public AdoAdapterFinder(AdoAdapter adapter) - { - _adapter = adapter; - } - - public IEnumerable> Find(string tableName, SimpleExpression criteria) - { - if (criteria == null) return FindAll(tableName); - - var commandBuilder = new FindHelper(_adapter.GetSchema()).GetFindByCommand(tableName, criteria); - return ExecuteQuery(commandBuilder); - } - - private IEnumerable> FindAll(string tableName) - { - return ExecuteQuery("select * from " + _adapter.GetSchema().FindTable(tableName).ActualName); - } - - private IEnumerable> ExecuteQuery(ICommandBuilder commandBuilder) - { - var connection = _adapter.CreateConnection(); - var command = commandBuilder.GetCommand(connection); - return TryExecuteQuery(connection, command); - } - - private IEnumerable> ExecuteQuery(string sql, params object[] values) - { - var connection = _adapter.CreateConnection(); - var command = CommandHelper.Create(connection, sql, values); - return command.ToAsyncEnumerable(); - } - - private static IEnumerable> TryExecuteQuery(DbConnection connection, IDbCommand command) - { - try - { - return command.ToAsyncEnumerable(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } - } - } -} diff --git a/Simple.Data/Ado/AdoAdapterInserter.cs b/Simple.Data/Ado/AdoAdapterInserter.cs deleted file mode 100644 index 36719e4a..00000000 --- a/Simple.Data/Ado/AdoAdapterInserter.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; - -namespace Simple.Data.Ado -{ - class AdoAdapterInserter - { - private readonly AdoAdapter _adapter; - - public AdoAdapterInserter(AdoAdapter adapter) - { - _adapter = adapter; - } - - public IDictionary Insert(string tableName, IDictionary data) - { - var table = _adapter.GetSchema().FindTable(tableName); - - string columnList = - data.Keys.Select(s => table.FindColumn(s).QuotedName).Aggregate((agg, next) => agg + "," + next); - string valueList = data.Keys.Select(s => "?").Aggregate((agg, next) => agg + "," + next); - - string insertSql = "insert into " + table.QuotedName + " (" + columnList + ") values (" + valueList + ")"; - - var identityColumn = table.Columns.FirstOrDefault(col => col.IsIdentity); - - if (identityColumn != null) - { - insertSql += "; select * from " + table.QuotedName + " where " + identityColumn.QuotedName + - " = scope_identity()"; - return ExecuteSingletonQuery(insertSql, data.Values.ToArray()); - } - - Execute(insertSql, data.Values.ToArray()); - return null; - } - - internal IDictionary ExecuteSingletonQuery(string sql, params object[] values) - { - using (var connection = _adapter.CreateConnection()) - { - using (var command = CommandHelper.Create(connection, sql, values.ToArray())) - { - try - { - connection.Open(); - using (var reader = command.ExecuteReader()) - { - if (reader.Read()) - { - return reader.ToDictionary(); - } - } - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } - } - } - - return null; - } - - internal int Execute(string sql, params object[] values) - { - using (var connection = _adapter.CreateConnection()) - { - using (var command = CommandHelper.Create(connection, sql, values.ToArray())) - { - return TryExecute(connection, command); - } - } - } - - private static int TryExecute(DbConnection connection, IDbCommand command) - { - try - { - connection.Open(); - return command.ExecuteNonQuery(); - } - catch (DbException ex) - { - throw new AdoAdapterException(ex.Message, command); - } - } - } -} diff --git a/Simple.Data/Ado/AdoAdapterRelatedFinder.cs b/Simple.Data/Ado/AdoAdapterRelatedFinder.cs deleted file mode 100644 index 826f3499..00000000 --- a/Simple.Data/Ado/AdoAdapterRelatedFinder.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - class AdoAdapterRelatedFinder - { - private readonly Lazy, TableJoin>> _tableJoins = - new Lazy, TableJoin>>( - () => new ConcurrentDictionary, TableJoin>(), LazyThreadSafetyMode.ExecutionAndPublication - ); - - private readonly AdoAdapter _adapter; - - public AdoAdapterRelatedFinder(AdoAdapter adapter) - { - _adapter = adapter; - } - - public bool IsValidRelation(string tableName, string relatedTableName) - { - return TryJoin(tableName, relatedTableName) != null; - } - - public IEnumerable> FindRelated(string tableName, IDictionary row, string relatedTableName) - { - var join = TryJoin(tableName, relatedTableName); - if (join == null) throw new AdoAdapterException("Could not resolve relationship."); - - return join.Master == _adapter.GetSchema().FindTable(tableName) ? GetDetail(row, join) : GetMaster(row, join); - } - - private TableJoin TryJoin(string tableName, string relatedTableName) - { - return _tableJoins.Value.GetOrAdd(Tuple.Create(tableName, relatedTableName), - t => TryCreateJoin(t.Item1, t.Item2)); - } - - private TableJoin TryCreateJoin(string tableName, string relatedTableName) - { - return TryMasterJoin(tableName, relatedTableName) ?? TryDetailJoin(tableName, relatedTableName); - } - - private TableJoin TryMasterJoin(string tableName, string relatedTableName) - { - return _adapter.GetSchema().FindTable(tableName).GetMaster(relatedTableName); - } - - private TableJoin TryDetailJoin(string tableName, string relatedTableName) - { - return _adapter.GetSchema().FindTable(tableName).GetDetail(relatedTableName); - } - - private IEnumerable> GetMaster(IDictionary row, TableJoin masterJoin) - { - var criteria = new Dictionary { { masterJoin.MasterColumn.ActualName, row[masterJoin.DetailColumn.HomogenizedName] } }; - yield return _adapter.Find(masterJoin.Master.ActualName, - ExpressionHelper.CriteriaDictionaryToExpression(masterJoin.Master.ActualName, - criteria)).FirstOrDefault(); - } - - private IEnumerable> GetDetail(IDictionary row, TableJoin join) - { - var criteria = new Dictionary { { join.DetailColumn.ActualName, row[join.MasterColumn.HomogenizedName] } }; - return _adapter.Find(join.Detail.ActualName, - ExpressionHelper.CriteriaDictionaryToExpression(join.Detail.ActualName, - criteria)); - } - } -} diff --git a/Simple.Data/Ado/CommandBuilder.cs b/Simple.Data/Ado/CommandBuilder.cs deleted file mode 100644 index 9b341894..00000000 --- a/Simple.Data/Ado/CommandBuilder.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Simple.Data.Ado -{ - class CommandBuilder : ICommandBuilder - { - private int _number; - private readonly Dictionary _parameters = new Dictionary(); - private readonly StringBuilder _text; - - public CommandBuilder() - { - _text = new StringBuilder(); - } - - public CommandBuilder(string text) - { - _text = new StringBuilder(text); - } - - public string AddParameter(object value) - { - string name = "@p" + Interlocked.Increment(ref _number); - _parameters.Add(name, value); - return name; - } - - public void Append(string text) - { - _text.Append(text); - } - - public override string ToString() - { - return _text.ToString(); - } - - public IEnumerable> Parameters - { - get { return _parameters.AsEnumerable(); } - } - - public string Text - { - get { return _text.ToString(); } - } - - public IDbCommand GetCommand(IDbConnection connection) - { - var command = connection.CreateCommand(); - command.CommandText = Text; - SetParameters(command); - return command; - } - - private void SetParameters(IDbCommand command) - { - foreach (var pair in _parameters) - { - var parameter = command.CreateParameter(); - parameter.ParameterName = pair.Key; - parameter.Value = pair.Value; - command.Parameters.Add(parameter); - } - } - } -} diff --git a/Simple.Data/Ado/CommandHelper.cs b/Simple.Data/Ado/CommandHelper.cs deleted file mode 100644 index 3a59105a..00000000 --- a/Simple.Data/Ado/CommandHelper.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Text; - -namespace Simple.Data.Ado -{ - internal static class CommandHelper - { - internal static IDbCommand Create(IDbConnection connection, string sql, IList values) - { - var command = connection.CreateCommand(); - - command.CommandText = PrepareCommand(sql, values, command); - - return command; - } - - internal static IDbCommand Create(IDbConnection connection, CommandBuilder commandBuilder) - { - var command = connection.CreateCommand(); - command.CommandText = commandBuilder.Text; - PrepareCommand(commandBuilder, command); - return command; - } - - private static string PrepareCommand(IEnumerable sql, IList values, IDbCommand command) - { - int index = 0; - var sqlBuilder = new StringBuilder(); - foreach (var c in sql) - { - if (c == '?') - { - var parameter = command.CreateParameter(); - parameter.ParameterName = "@p" + index; - parameter.Value = values[index]; - command.Parameters.Add(parameter); - - sqlBuilder.Append(parameter.ParameterName); - index++; - } - else - { - sqlBuilder.Append(c); - } - } - return sqlBuilder.ToString(); - } - - private static void PrepareCommand(CommandBuilder commandBuilder, IDbCommand command) - { - foreach (var pair in commandBuilder.Parameters) - { - var parameter = command.CreateParameter(); - parameter.ParameterName = pair.Key; - parameter.Value = pair.Value; - command.Parameters.Add(parameter); - } - } - } -} diff --git a/Simple.Data/Ado/DataReaderExtensions.cs b/Simple.Data/Ado/DataReaderExtensions.cs deleted file mode 100644 index 3d1f2642..00000000 --- a/Simple.Data/Ado/DataReaderExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Data; - -namespace Simple.Data.Ado -{ - internal static class DataReaderExtensions - { - public static IEnumerable> ToDictionaries(this IDataReader reader) - { - var list = new List>(); - using (reader) - { - while (reader.Read()) - { - list.Add(reader.ToDictionary()); - } - } - - return list.AsEnumerable(); - } - - public static IEnumerable ToDynamicList(this IDataReader reader) - { - var list = new List(); - using (reader) - { - while (reader.Read()) - { - list.Add(reader.ToDynamicRecord()); - } - } - - return list.AsEnumerable(); - } - - public static IEnumerable ToDynamicList(this IDataReader reader, Database database, string tableName) - { - var list = new List(); - using (reader) - { - while (reader.Read()) - { - list.Add(reader.ToDynamicRecord(tableName, database)); - } - } - - return list.AsEnumerable(); - } - } -} diff --git a/Simple.Data/Ado/DataReaderObservableRunner.cs b/Simple.Data/Ado/DataReaderObservableRunner.cs deleted file mode 100644 index bef9019b..00000000 --- a/Simple.Data/Ado/DataReaderObservableRunner.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Simple.Data.Ado -{ - class DataReaderObservableRunner : IDisposable - { - private readonly IDbCommand _command; - private readonly IObserver> _observer; - private bool _disposed; - - private DataReaderObservableRunner(IDbCommand command, IObserver> observer) - { - _command = command; - _observer = observer; - } - - public static DataReaderObservableRunner RunAsync(IDbCommand command, IObserver> observer) - { - var instance = new DataReaderObservableRunner(command, observer); - var asyncAction = new Action(instance.Run); - asyncAction.BeginInvoke(asyncAction.EndInvoke, null); - return instance; - } - - private void Run() - { - try - { - RunQuery(); - SendCompleted(); - } - catch (Exception ex) - { - _observer.OnError(ex); - } - } - - private void RunQuery() - { - using (_command.Connection) - using (_command) - { - _command.Connection.Open(); - RunReader(); - } - } - - private void RunReader() - { - using (var reader = _command.ExecuteReader()) - { - while ((!_disposed) && reader.Read()) - { - _observer.OnNext(reader.ToDictionary()); - } - } - } - - private void SendCompleted() - { - if (!_disposed) - { - _observer.OnCompleted(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - _disposed = true; - } - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/DataRecordExtensions.cs b/Simple.Data/Ado/DataRecordExtensions.cs deleted file mode 100644 index 66b2d9c0..00000000 --- a/Simple.Data/Ado/DataRecordExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Data; - -namespace Simple.Data.Ado -{ - internal static class DataRecordExtensions - { - public static dynamic ToDynamicRecord(this IDataRecord dataRecord) - { - return ToDynamicRecord(dataRecord, null, null); - } - - public static dynamic ToDynamicRecord(this IDataRecord dataRecord, string tableName, Database database) - { - return new DynamicRecord(dataRecord.ToDictionary(), tableName, database); - } - - public static Dictionary ToDictionary(this IDataRecord dataRecord) - { - return dataRecord.GetFieldNames().ToDictionary(fieldName => fieldName, fieldName => dataRecord[fieldName]); - } - - public static IEnumerable GetFieldNames(this IDataRecord dataRecord) - { - for (int i = 0; i < dataRecord.FieldCount; i++) - { - yield return dataRecord.GetName(i); - } - } - } -} diff --git a/Simple.Data/Ado/DbCommandExtensions.cs b/Simple.Data/Ado/DbCommandExtensions.cs deleted file mode 100644 index 30e1b0b5..00000000 --- a/Simple.Data/Ado/DbCommandExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace Simple.Data.Ado -{ - static class DbCommandExtensions - { - public static IEnumerable> ToAsyncEnumerable(this IDbCommand command) - { - if (command.Connection == null) throw new InvalidOperationException("Command has no connection."); - if (command.Connection.State != ConnectionState.Closed) throw new InvalidOperationException(MethodBase.GetCurrentMethod().Name + " must be called with a closed DbConnection."); - return ToObservable(command).ToEnumerable(); - } - - public static IObservable> ToObservable(this IDbCommand command) - { - if (command.Connection.State != ConnectionState.Closed) throw new InvalidOperationException(MethodBase.GetCurrentMethod().Name + " must be called with a closed DbConnection."); - return new ObservableDataReader(command); - } - } -} diff --git a/Simple.Data/Ado/DbCommandObservableEx.cs b/Simple.Data/Ado/DbCommandObservableEx.cs deleted file mode 100644 index a61ce6ce..00000000 --- a/Simple.Data/Ado/DbCommandObservableEx.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Reflection; - -namespace Simple.Data.Ado -{ - public static class DbCommandObservableEx - { - public static IEnumerable> ToAsyncEnumerable(this DbCommand command) - { - if (command.Connection.State != ConnectionState.Closed) throw new InvalidOperationException(MethodBase.GetCurrentMethod().Name + " must be called with a closed DbConnection."); - return ToObservable(command).ToEnumerable(); - } - - public static IObservable> ToObservable(this DbCommand command) - { - if (command.Connection.State != ConnectionState.Closed) throw new InvalidOperationException(MethodBase.GetCurrentMethod().Name + " must be called with a closed DbConnection."); - return new ObservableDataReader(command); - } - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/DeleteHelper.cs b/Simple.Data/Ado/DeleteHelper.cs deleted file mode 100644 index d125b4b0..00000000 --- a/Simple.Data/Ado/DeleteHelper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - internal class DeleteHelper - { - private readonly DatabaseSchema _schema; - private readonly ICommandBuilder _commandBuilder; - private readonly IExpressionFormatter _expressionFormatter; - - public DeleteHelper(DatabaseSchema schema) - { - _schema = schema; - _commandBuilder = new CommandBuilder(); - _expressionFormatter = new ExpressionFormatter(_commandBuilder, _schema); - } - - public ICommandBuilder GetDeleteCommand(string tableName, SimpleExpression criteria) - { - _commandBuilder.Append(GetDeleteClause(tableName)); - - if (criteria != null) - { - _commandBuilder.Append(" where "); - _commandBuilder.Append(_expressionFormatter.Format(criteria)); - } - - return _commandBuilder; - } - - private string GetDeleteClause(string tableName) - { - var table = _schema.FindTable(tableName); - return string.Concat("delete from ", table.QuotedName); - } - } -} diff --git a/Simple.Data/Ado/ExpressionFormatter.cs b/Simple.Data/Ado/ExpressionFormatter.cs deleted file mode 100644 index de9077f0..00000000 --- a/Simple.Data/Ado/ExpressionFormatter.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - class ExpressionFormatter : IExpressionFormatter - { - private readonly ICommandBuilder _commandBuilder; - private readonly DatabaseSchema _schema; - private readonly Dictionary> _expressionFormatters; - - public ExpressionFormatter(ICommandBuilder commandBuilder, DatabaseSchema schema) - { - _commandBuilder = commandBuilder; - _schema = schema; - _expressionFormatters = new Dictionary> - { - {SimpleExpressionType.And, LogicalExpressionToWhereClause}, - {SimpleExpressionType.Or, LogicalExpressionToWhereClause}, - {SimpleExpressionType.Equal, EqualExpressionToWhereClause}, - {SimpleExpressionType.NotEqual, NotEqualExpressionToWhereClause}, - {SimpleExpressionType.GreaterThan, expr => BinaryExpressionToWhereClause(expr, ">")}, - {SimpleExpressionType.GreaterThanOrEqual, expr => BinaryExpressionToWhereClause(expr, ">=")}, - {SimpleExpressionType.LessThan, expr => BinaryExpressionToWhereClause(expr, "<")}, - {SimpleExpressionType.LessThanOrEqual, expr => BinaryExpressionToWhereClause(expr, "<=")}, - }; - } - - public string Format(SimpleExpression expression) - { - Func formatter; - - if (_expressionFormatters.TryGetValue(expression.Type, out formatter)) - { - return formatter(expression); - } - - return string.Empty; - } - - private string LogicalExpressionToWhereClause(SimpleExpression expression) - { - return string.Format("({0} {1} {2})", - Format((SimpleExpression)expression.LeftOperand), - expression.Type.ToString().ToUpperInvariant(), - Format((SimpleExpression)expression.RightOperand)); - } - - private string EqualExpressionToWhereClause(SimpleExpression expression) - { - return string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand), - expression.RightOperand is string ? "LIKE" : "=", - FormatObject(expression.RightOperand)); - } - - private string NotEqualExpressionToWhereClause(SimpleExpression expression) - { - return string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand), - expression.RightOperand is string ? "NOT LIKE" : "!=", - FormatObject(expression.RightOperand)); - } - - private string BinaryExpressionToWhereClause(SimpleExpression expression, string comparisonOperator) - { - return string.Format("{0} {1} {2}", FormatObject(expression.LeftOperand), - comparisonOperator, - FormatObject(expression.RightOperand)); - } - - private string FormatObject(object value) - { - var reference = value as DynamicReference; - if (!ReferenceEquals(reference, null)) - { - var table = _schema.FindTable(reference.GetOwner().GetName()); - return table.QuotedName + "." + table.FindColumn(reference.GetName()).QuotedName; - } - - return _commandBuilder.AddParameter(value); - } - } -} diff --git a/Simple.Data/Ado/FindHelper.cs b/Simple.Data/Ado/FindHelper.cs deleted file mode 100644 index ac123daa..00000000 --- a/Simple.Data/Ado/FindHelper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - internal class FindHelper - { - private readonly DatabaseSchema _schema; - private readonly ICommandBuilder _commandBuilder; - private readonly IExpressionFormatter _expressionFormatter; - - public FindHelper(DatabaseSchema schema) - { - _schema = schema; - _commandBuilder = new CommandBuilder(); - _expressionFormatter = new ExpressionFormatter(_commandBuilder, _schema); - } - - public ICommandBuilder GetFindByCommand(string tableName, SimpleExpression criteria) - { - _commandBuilder.Append(GetSelectClause(tableName)); - _commandBuilder.Append(" "); - _commandBuilder.Append(new Joiner(JoinType.Inner, _schema).GetJoinClauses(tableName, criteria)); - - if (criteria != null) - { - _commandBuilder.Append(" where "); - _commandBuilder.Append(_expressionFormatter.Format(criteria)); - } - - return _commandBuilder; - } - - private string GetSelectClause(string tableName) - { - return string.Format("select {0}.* from {0}", _schema.FindTable(tableName).QuotedName); - } - } -} diff --git a/Simple.Data/Ado/ICommandBuilder.cs b/Simple.Data/Ado/ICommandBuilder.cs deleted file mode 100644 index 103644d9..00000000 --- a/Simple.Data/Ado/ICommandBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Data; - -namespace Simple.Data.Ado -{ - public interface ICommandBuilder - { - string AddParameter(object value); - void Append(string text); - IDbCommand GetCommand(IDbConnection connection); - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/IConnectionProvider.cs b/Simple.Data/Ado/IConnectionProvider.cs deleted file mode 100644 index dc62e1c7..00000000 --- a/Simple.Data/Ado/IConnectionProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Data.Common; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - public interface IConnectionProvider - { - void SetConnectionString(string connectionString); - DbConnection CreateConnection(); - ISchemaProvider GetSchemaProvider(); - string ConnectionString { get; } - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/IExpressionFormatter.cs b/Simple.Data/Ado/IExpressionFormatter.cs deleted file mode 100644 index 2dab0e59..00000000 --- a/Simple.Data/Ado/IExpressionFormatter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Simple.Data.Ado -{ - public interface IExpressionFormatter - { - string Format(SimpleExpression expression); - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/JoinType.cs b/Simple.Data/Ado/JoinType.cs deleted file mode 100644 index 8c8b004b..00000000 --- a/Simple.Data/Ado/JoinType.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Simple.Data.Ado -{ - enum JoinType - { - Inner, - Outer - } -} diff --git a/Simple.Data/Ado/Joiner.cs b/Simple.Data/Ado/Joiner.cs deleted file mode 100644 index c8c546b2..00000000 --- a/Simple.Data/Ado/Joiner.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Simple.Data.Ado.Schema; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado -{ - class Joiner - { - private readonly JoinType _joinType; - private readonly DatabaseSchema _schema; - private readonly ConcurrentDictionary _done = new ConcurrentDictionary(); - - public Joiner(JoinType joinType, DatabaseSchema schema) - { - if (schema == null) throw new ArgumentNullException("schema"); - _joinType = joinType; - _schema = schema; - } - - public string GetJoinClauses(string mainTableName, SimpleExpression expression) - { - _done.AddOrUpdate(mainTableName, string.Empty, (s, o) => string.Empty); - var tablePairs = GetTablePairs(expression); - foreach (var tablePair in tablePairs) - { - AddJoin(tablePair.Item1, tablePair.Item2); - } - return string.Join(" ", tablePairs.Select(tp => _done[tp.Item2])); - } - - private void AddJoin(string table1Name, string table2Name) - { - var table1 = _schema.FindTable(table1Name); - var table2 = _schema.FindTable(table2Name); - - var foreignKey = - table2.ForeignKeys.SingleOrDefault(fk => fk.MasterTable == table1.ActualName) - ?? - table1.ForeignKeys.SingleOrDefault(fk => fk.MasterTable == table2.ActualName); - - if (foreignKey == null) throw new SchemaResolutionException( - string.Format("Could not join '{0}' and '{1}'", table1.ActualName, table2.ActualName)); - - _done.GetOrAdd(table2Name, s => MakeJoinText(table2, foreignKey)); - } - - private string MakeJoinText(Table rightTable, ForeignKey foreignKey) - { - var builder = new StringBuilder(JoinKeyword); - builder.AppendFormat(" JOIN {0} ON (", rightTable.QuotedName); - builder.Append(FormatJoinExpression(foreignKey, 0)); - - for (int i = 1; i < foreignKey.Columns.Length; i++) - { - builder.Append(" AND "); - builder.Append(FormatJoinExpression(foreignKey, i)); - } - builder.Append(")"); - return builder.ToString(); - } - - private string FormatJoinExpression(ForeignKey foreignKey, int columnIndex) - { - return string.Format("{0}.{1} = {2}.{3}", _schema.QuoteObjectName(foreignKey.MasterTable), _schema.QuoteObjectName(foreignKey.UniqueColumns[columnIndex]), - _schema.QuoteObjectName(foreignKey.DetailTable), _schema.QuoteObjectName(foreignKey.Columns[columnIndex])); - } - - private string JoinKeyword - { - get { return _joinType == JoinType.Inner ? string.Empty : "LEFT"; } - } - - private static IEnumerable> GetTablePairs(SimpleExpression expression) - { - return GetReferencesFromExpression(expression) - .SelectMany(dr => dr.GetAllObjectNames().SkipLast().ToTuplePairs()) - .Distinct(); - } - - private static IEnumerable GetReferencesFromExpression(SimpleExpression expression) - { - if (expression.Type == SimpleExpressionType.And || expression.Type == SimpleExpressionType.Or) - { - return GetReferencesFromExpression((SimpleExpression) expression.LeftOperand) - .Concat(GetReferencesFromExpression((SimpleExpression) expression.LeftOperand)); - } - - var result = Enumerable.Empty(); - - if (expression.LeftOperand is DynamicReference) - { - result = result.Concat(new[] {(DynamicReference) expression.LeftOperand}); - } - if (expression.RightOperand is DynamicReference) - { - result = result.Concat(new[] { (DynamicReference)expression.RightOperand }); - } - - return result; - } - } -} diff --git a/Simple.Data/Ado/ObservableDataReader.cs b/Simple.Data/Ado/ObservableDataReader.cs deleted file mode 100644 index e8e57633..00000000 --- a/Simple.Data/Ado/ObservableDataReader.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Simple.Data.Ado -{ - class ObservableDataReader : IObservable> - { - private readonly IDbCommand _command; - - public ObservableDataReader(IDbCommand command) - { - _command = command; - } - - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// - /// The observer's interface that enables resources to be disposed. - /// - /// The object that is to receive notifications. - public IDisposable Subscribe(IObserver> observer) - { - return DataReaderObservableRunner.RunAsync(_command, observer); - } - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/ProviderHelper.cs b/Simple.Data/Ado/ProviderHelper.cs deleted file mode 100644 index 1802d8f1..00000000 --- a/Simple.Data/Ado/ProviderHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.ComponentModel.Composition.Hosting; -using System.Reflection; -using System.IO; - -namespace Simple.Data.Ado -{ - class ProviderHelper - { - private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); - - public static IConnectionProvider GetProviderByConnectionString(string connectionString) - { - return Cache.GetOrAdd(connectionString, LoadProviderByConnectionString); - } - - public static IConnectionProvider GetProviderByFilename(string filename) - { - return Cache.GetOrAdd(filename, LoadProviderByFilename); - } - - private static IConnectionProvider LoadProviderByConnectionString(string connectionString) - { - var provider = ComposeProvider("sql"); - - provider.SetConnectionString(connectionString); - return provider; - } - - private static IConnectionProvider LoadProviderByFilename(string filename) - { - string extension = GetFileExtension(filename); - - var provider = ComposeProvider(extension); - - provider.SetConnectionString(string.Format("data source={0}", filename)); - return provider; - } - - private static string GetFileExtension(string filename) - { - var extension = Path.GetExtension(filename); - - if (extension == null) throw new ArgumentException("Unrecognised file."); - return extension.TrimStart('.').ToLower(); - } - - private static IConnectionProvider ComposeProvider(string extension) - { - return MefHelper.Compose(extension); - } - } -} diff --git a/Simple.Data/Ado/Schema/AmbiguousObjectNameException.cs b/Simple.Data/Ado/Schema/AmbiguousObjectNameException.cs deleted file mode 100644 index 8ff72f0d..00000000 --- a/Simple.Data/Ado/Schema/AmbiguousObjectNameException.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Simple.Data.Ado.Schema -{ - [Serializable] - public class AmbiguousObjectNameException : Exception - { - private readonly string _name; - - public AmbiguousObjectNameException() - { - } - - public AmbiguousObjectNameException(string name) - { - _name = name; - } - - public AmbiguousObjectNameException(string name, string message) : base(message) - { - _name = name; - } - - public AmbiguousObjectNameException(string name, string message, Exception inner) : base(message, inner) - { - _name = name; - } - - protected AmbiguousObjectNameException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - _name = info.GetString("_name"); - } - - public string Name - { - get { return _name; } - } - - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("_name", _name); - } - } -} diff --git a/Simple.Data/Ado/Schema/Column.cs b/Simple.Data/Ado/Schema/Column.cs deleted file mode 100644 index f5c048b2..00000000 --- a/Simple.Data/Ado/Schema/Column.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado.Schema -{ - public class Column : IEquatable - { - private readonly string _actualName; - private readonly Table _table; - private readonly bool _isIdentity; - - public Column(string actualName, Table table) : this(actualName, table, false) - { - } - - public Column(string actualName, Table table, bool isIdentity) - { - _actualName = actualName; - _isIdentity = isIdentity; - _table = table; - } - - public string HomogenizedName - { - get { return ActualName.Homogenize(); } - } - - public string ActualName - { - get { return _actualName; } - } - - public string QuotedName - { - get { return _table.DatabaseSchema.QuoteObjectName(_actualName); } - } - - public bool IsIdentity - { - get { return _isIdentity; } - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(Column other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other._actualName, _actualName) && Equals(other._table, _table); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The to compare with the current . 2 - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof (Column)) return false; - return Equals((Column) obj); - } - - /// - /// Serves as a hash function for a particular type. - /// - /// - /// A hash code for the current . - /// - /// 2 - public override int GetHashCode() - { - unchecked - { - return ((_actualName != null ? _actualName.GetHashCode() : 0)*397) ^ (_table != null ? _table.GetHashCode() : 0); - } - } - } -} diff --git a/Simple.Data/Ado/Schema/ColumnCollection.cs b/Simple.Data/Ado/Schema/ColumnCollection.cs deleted file mode 100644 index c330267c..00000000 --- a/Simple.Data/Ado/Schema/ColumnCollection.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado.Schema -{ - class ColumnCollection : Collection - { - public ColumnCollection() - { - - } - - public ColumnCollection(IEnumerable columns) : base(columns.ToList()) - { - - } - /// - /// Finds the column with a name most closely matching the specified column name. - /// This method will try an exact match first, then a case-insensitve search, then a pluralized or singular version. - /// - /// Name of the column. - /// A if a match is found; otherwise, null. - public Column Find(string columnName) - { - return FindColumnWithName(columnName); - } - - private Column FindColumnWithName(string columnName) - { - columnName = columnName.Homogenize(); - return this - .Where(c => c.HomogenizedName.Equals(columnName)) - .SingleOrDefault(); - } - } -} diff --git a/Simple.Data/Ado/Schema/DatabaseSchema.cs b/Simple.Data/Ado/Schema/DatabaseSchema.cs deleted file mode 100644 index e966c3d8..00000000 --- a/Simple.Data/Ado/Schema/DatabaseSchema.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.Linq; - -namespace Simple.Data.Ado.Schema -{ - internal class DatabaseSchema - { - private static readonly ConcurrentDictionary Instances = new ConcurrentDictionary(); - - private readonly ISchemaProvider _schemaProvider; - private readonly Lazy _lazyTables; - - private DatabaseSchema(ISchemaProvider schemaProvider) - { - _lazyTables = new Lazy(CreateTableCollection); - _schemaProvider = schemaProvider; - } - - public ISchemaProvider SchemaProvider - { - get { return _schemaProvider; } - } - - public bool IsAvailable - { - get { return _schemaProvider != null; } - } - - public IEnumerable
Tables - { - get { return _lazyTables.Value.AsEnumerable(); } - } - - public Table FindTable(string tableName) - { - return _lazyTables.Value.Find(tableName); - } - - private TableCollection CreateTableCollection() - { - return new TableCollection(_schemaProvider.GetTables() - .Select(table => new Table(table.ActualName, table.Schema, table.Type, this))); - } - - public string QuoteObjectName(string unquotedName) - { - return _schemaProvider.QuoteObjectName(unquotedName); - } - - public static DatabaseSchema Get(IConnectionProvider connectionProvider) - { - return Instances.GetOrAdd(connectionProvider.ConnectionString, - sp => new DatabaseSchema(connectionProvider.GetSchemaProvider())); - } - } -} diff --git a/Simple.Data/Ado/Schema/ForeignKey.cs b/Simple.Data/Ado/Schema/ForeignKey.cs deleted file mode 100644 index a52f74b2..00000000 --- a/Simple.Data/Ado/Schema/ForeignKey.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; - -namespace Simple.Data.Ado.Schema -{ - public sealed class ForeignKey - { - private readonly Key _columns; - private readonly string _detailTable; - private readonly string _masterTable; - private readonly Key _masterColumns; - - public ForeignKey(string detailTable, IEnumerable columns, string masterTable, IEnumerable masterColumns) - { - _columns = new Key(columns); - _detailTable = detailTable; - _masterTable = masterTable; - _masterColumns = new Key(masterColumns); - } - - public string DetailTable - { - get { return _detailTable; } - } - - public Key UniqueColumns - { - get { return _masterColumns; } - } - - public Key Columns - { - get { return _columns; } - } - - public string MasterTable - { - get { return _masterTable; } - } - } -} diff --git a/Simple.Data/Ado/Schema/ForeignKeyCollection.cs b/Simple.Data/Ado/Schema/ForeignKeyCollection.cs deleted file mode 100644 index 84cdbcfb..00000000 --- a/Simple.Data/Ado/Schema/ForeignKeyCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.ObjectModel; - -namespace Simple.Data.Ado.Schema -{ - class ForeignKeyCollection : KeyedCollection - { - /// - /// When implemented in a derived class, extracts the key from the specified element. - /// - /// - /// The key for the specified element. - /// - /// The element from which to extract the key. - protected override string GetKeyForItem(ForeignKey item) - { - return item.MasterTable; - } - } -} diff --git a/Simple.Data/Ado/Schema/ISchemaProvider.cs b/Simple.Data/Ado/Schema/ISchemaProvider.cs deleted file mode 100644 index 058effb4..00000000 --- a/Simple.Data/Ado/Schema/ISchemaProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Data; - -namespace Simple.Data.Ado.Schema -{ - public interface ISchemaProvider - { - IEnumerable
GetTables(); - IEnumerable GetColumns(Table table); - Key GetPrimaryKey(Table table); - IEnumerable GetForeignKeys(Table table); - string QuoteObjectName(string unquotedName); - } -} \ No newline at end of file diff --git a/Simple.Data/Ado/Schema/Key.cs b/Simple.Data/Ado/Schema/Key.cs deleted file mode 100644 index e348c4f6..00000000 --- a/Simple.Data/Ado/Schema/Key.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Simple.Data.Ado.Schema -{ - public sealed class Key - { - private readonly string[] _columns; - public Key(IEnumerable columns) - { - _columns = columns.ToArray(); - } - - public string this[int index] - { - get { return _columns[index]; } - } - - public int Length - { - get { return _columns.Length; } - } - - public IEnumerable AsEnumerable() - { - return _columns.AsEnumerable(); - } - } -} diff --git a/Simple.Data/Ado/Schema/SchemaSpecificStringExtensions.cs b/Simple.Data/Ado/Schema/SchemaSpecificStringExtensions.cs deleted file mode 100644 index a8ee9f61..00000000 --- a/Simple.Data/Ado/Schema/SchemaSpecificStringExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Simple.Data.Ado.Schema -{ - static class SchemaSpecificStringExtensions - { - } -} diff --git a/Simple.Data/Ado/Schema/Table.cs b/Simple.Data/Ado/Schema/Table.cs deleted file mode 100644 index 7643d300..00000000 --- a/Simple.Data/Ado/Schema/Table.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado.Schema -{ - public class Table : IEquatable
- { - private readonly string _actualName; - private readonly string _schema; - private readonly TableType _type; - private readonly DatabaseSchema _databaseSchema; - private readonly Lazy _lazyColumns; - private readonly Lazy _lazyPrimaryKey; - private readonly Lazy _lazyForeignKeys; - - public Table(string name, string schema, TableType type) - { - _actualName = name; - _schema = schema; - _type = type; - } - - internal Table(string name, string schema, TableType type, DatabaseSchema databaseSchema) - { - _actualName = name; - _databaseSchema = databaseSchema; - _schema = schema; - _type = type; - _lazyColumns = new Lazy(GetColumns); - _lazyPrimaryKey = new Lazy(GetPrimaryKey); - _lazyForeignKeys = new Lazy(GetForeignKeys); - } - - public TableType Type - { - get { return _type; } - } - - internal string HomogenizedName - { - get { return ActualName.Homogenize(); } - } - - internal DatabaseSchema DatabaseSchema - { - get { return _databaseSchema; } - } - - public string Schema - { - get { return _schema; } - } - - public string ActualName - { - get { return _actualName; } - } - - internal string QuotedName - { - get { return _databaseSchema.QuoteObjectName(_actualName); } - } - - internal IEnumerable Columns - { - get { return _lazyColumns.Value.AsEnumerable(); } - } - - internal Column FindColumn(string columnName) - { - var columns = _lazyColumns.Value; - return columns.Find(columnName); - } - - internal Key PrimaryKey - { - get { return _lazyPrimaryKey.Value; } - } - - internal ForeignKeyCollection ForeignKeys - { - get { return _lazyForeignKeys.Value; } - } - - private ColumnCollection GetColumns() - { - return new ColumnCollection(_databaseSchema.SchemaProvider.GetColumns(this)); - } - - private Key GetPrimaryKey() - { - return _databaseSchema.SchemaProvider.GetPrimaryKey(this); - //var columns = _databaseSchema.SchemaProvider.GetSchema("PRIMARY_KEYS", ActualName).AsEnumerable() - // .OrderBy(row => (int) row["ORDINAL_POSITION"]) - // .Select(row => row["COLUMN_NAME"].ToString()) - // .ToArray(); - - //return new Key(columns); - } - - private ForeignKeyCollection GetForeignKeys() - { - var collection = new ForeignKeyCollection(); - - //var keys = _databaseSchema.SchemaProvider.GetSchema("FOREIGN_KEYS", ActualName).AsEnumerable() - // .GroupBy(row => row["UNIQUE_TABLE_NAME"].ToString()); - - foreach (var key in _databaseSchema.SchemaProvider.GetForeignKeys(this)) - { - //var columns = key.OrderBy(row => (int)row["ORDINAL_POSITION"]).Select(row => row["COLUMN_NAME"].ToString()).ToArray(); - //var uniqueColumns = key.OrderBy(row => (int)row["ORDINAL_POSITION"]).Select(row => row["UNIQUE_COLUMN_NAME"].ToString()).ToArray(); - collection.Add(key); - } - - return collection; - } - - internal TableJoin GetMaster(string name) - { - var master = DatabaseSchema.FindTable(name); - if (master != null) - { - string commonColumnName = GetCommonColumnName(master); - - if (commonColumnName != null) - { - return new TableJoin(master, master.FindColumn(commonColumnName), this, FindColumn(commonColumnName)); - } - } - return null; - } - - private string GetCommonColumnName(Table other) - { - return other.Columns - .Select(c => c.HomogenizedName) - .Intersect(Columns.Select(c => c.HomogenizedName)) - .SingleOrDefault(); - } - - internal TableJoin GetDetail(string name) - { - var detail = DatabaseSchema.FindTable(name); - string commonColumnName = GetCommonColumnName(detail); - if (detail.Columns.Select(c => c.HomogenizedName).Intersect(Columns.Select(c => c.HomogenizedName)).Count() == 1) - { - return new TableJoin(this, FindColumn(commonColumnName), detail, detail.FindColumn(commonColumnName)); - } - return null; - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(Table other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other._actualName, _actualName) && Equals(other._schema, _schema) && Equals(other._type, _type); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The to compare with the current . 2 - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof (Table)) return false; - return Equals((Table) obj); - } - - /// - /// Serves as a hash function for a particular type. - /// - /// - /// A hash code for the current . - /// - /// 2 - public override int GetHashCode() - { - unchecked - { - int result = (_actualName != null ? _actualName.GetHashCode() : 0); - result = (result*397) ^ (_schema != null ? _schema.GetHashCode() : 0); - result = (result*397) ^ _type.GetHashCode(); - return result; - } - } - } -} diff --git a/Simple.Data/Ado/Schema/TableCollection.cs b/Simple.Data/Ado/Schema/TableCollection.cs deleted file mode 100644 index a8db8e0f..00000000 --- a/Simple.Data/Ado/Schema/TableCollection.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Simple.Data.Extensions; - -namespace Simple.Data.Ado.Schema -{ - class TableCollection : Collection
- { - public TableCollection() - { - } - - public TableCollection(IEnumerable
tables) - : base(tables.ToList()) - { - } - - /// - /// Finds the Table with a name most closely matching the specified table name. - /// This method will try an exact match first, then a case-insensitve search, then a pluralized or singular version. - /// - /// Name of the table. - /// A if a match is found; otherwise, null. - public Table Find(string tableName) - { - var table = FindTableWithName(tableName.Homogenize()) - ?? FindTableWithPluralName(tableName.Homogenize()) - ?? FindTableWithSingularName(tableName.Homogenize()); - - if (table == null) - { - throw new UnresolvableObjectException(tableName, "No matching table found, or insufficient permissions."); - } - - return table; - } - - private Table FindTableWithName(string tableName) - { - return this - .Where(t => t.HomogenizedName.Equals(tableName)) - .SingleOrDefault(); - } - - private Table FindTableWithPluralName(string tableName) - { - return FindTableWithName(tableName.Pluralize()); - } - - private Table FindTableWithSingularName(string tableName) - { - if (tableName.IsPlural()) - { - return FindTableWithName(tableName.Singularize()); - } - - return null; - } - } -} diff --git a/Simple.Data/Ado/Schema/TableJoin.cs b/Simple.Data/Ado/Schema/TableJoin.cs deleted file mode 100644 index 10b75176..00000000 --- a/Simple.Data/Ado/Schema/TableJoin.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Simple.Data.Ado.Schema -{ - class TableJoin - { - private readonly Table _master; - private readonly Column _masterColumn; - private readonly Table _detail; - private readonly Column _detailColumn; - - public TableJoin(Table master, Column masterColumn, Table detail, Column detailColumn) - { - _master = master; - _masterColumn = masterColumn; - _detailColumn = detailColumn; - _detail = detail; - } - - public Column MasterColumn - { - get { return _masterColumn; } - } - - public Table Detail - { - get { return _detail; } - } - - public Table Master - { - get { return _master; } - } - - public Column DetailColumn - { - get { - return _detailColumn; - } - } - } -} diff --git a/Simple.Data/Ado/Schema/TableType.cs b/Simple.Data/Ado/Schema/TableType.cs deleted file mode 100644 index 524922f4..00000000 --- a/Simple.Data/Ado/Schema/TableType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Simple.Data.Ado.Schema -{ - public enum TableType - { - Table, - View - } -} diff --git a/Simple.Data/Ado/SchemaResolutionException.cs b/Simple.Data/Ado/SchemaResolutionException.cs deleted file mode 100644 index 0f6f4595..00000000 --- a/Simple.Data/Ado/SchemaResolutionException.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; - -namespace Simple.Data.Ado -{ - [Serializable] - public class SchemaResolutionException : SimpleDataException - { - public SchemaResolutionException() : base() - { - } - - public SchemaResolutionException(string message) - : base(message) - { - } - - public SchemaResolutionException(string message, Exception inner) - : base(message, inner) - { - } - - protected SchemaResolutionException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } - } -} diff --git a/Simple.Data/Ado/UpdateHelper.cs b/Simple.Data/Ado/UpdateHelper.cs deleted file mode 100644 index 689882c2..00000000 --- a/Simple.Data/Ado/UpdateHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Simple.Data.Ado.Schema; - -namespace Simple.Data.Ado -{ - internal class UpdateHelper - { - private readonly DatabaseSchema _schema; - private readonly ICommandBuilder _commandBuilder; - private readonly IExpressionFormatter _expressionFormatter; - - public UpdateHelper(DatabaseSchema schema) - { - _schema = schema; - _commandBuilder = new CommandBuilder(); - _expressionFormatter = new ExpressionFormatter(_commandBuilder, _schema); - } - - public ICommandBuilder GetUpdateCommand(string tableName, IDictionary data, SimpleExpression criteria) - { - _commandBuilder.Append(GetUpdateClause(tableName, data)); - - if (criteria != null) - { - _commandBuilder.Append(" where "); - _commandBuilder.Append(_expressionFormatter.Format(criteria)); - } - - return _commandBuilder; - } - - private string GetUpdateClause(string tableName, IDictionary data) - { - var table = _schema.FindTable(tableName); - var setClause = string.Join(", ", - data.Select( - kvp => - string.Format("{0} = {1}", table.FindColumn(kvp.Key).QuotedName, _commandBuilder.AddParameter(kvp.Value)))); - return string.Format("update {0} set {1}", table.QuotedName, setClause); - } - } -} diff --git a/Simple.Data/AdoCompatibleComparer.cs b/Simple.Data/AdoCompatibleComparer.cs new file mode 100644 index 00000000..912971c2 --- /dev/null +++ b/Simple.Data/AdoCompatibleComparer.cs @@ -0,0 +1,22 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + using Extensions; + + public class AdoCompatibleComparer : IEqualityComparer + { + public static readonly HomogenizedEqualityComparer DefaultInstance = new HomogenizedEqualityComparer(); + + public bool Equals(string x, string y) + { + return ReferenceEquals(x, y.Homogenize()) + || x.Homogenize() == y.Homogenize() + || x.Homogenize().Singularize() == y.Homogenize().Singularize(); + } + + public int GetHashCode(string obj) + { + return obj.Homogenize().Singularize().GetHashCode(); + } + } +} \ No newline at end of file diff --git a/Simple.Data/AssemblyEx.cs b/Simple.Data/AssemblyEx.cs new file mode 100644 index 00000000..067a4900 --- /dev/null +++ b/Simple.Data/AssemblyEx.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; + +namespace Simple.Data +{ + public static class AssemblyEx + { + public static string GetFullName(this Assembly assembly) + { + return assembly.FullName.Substring(0, assembly.FullName.IndexOf(",", StringComparison.OrdinalIgnoreCase)); + } + } +} \ No newline at end of file diff --git a/Simple.Data/BadExpressionException.cs b/Simple.Data/BadExpressionException.cs new file mode 100644 index 00000000..59af45d8 --- /dev/null +++ b/Simple.Data/BadExpressionException.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.Serialization; + +namespace Simple.Data +{ + [Serializable] + public class BadExpressionException : ArgumentException + { + public BadExpressionException() + {} + + public BadExpressionException(string message) : base(message) + {} + + public BadExpressionException(string message, Exception inner) : base(message, inner) + {} + + protected BadExpressionException(SerializationInfo info, StreamingContext context) : base(info, context) + {} + } + + [Serializable] + public class BadJoinExpressionException : BadExpressionException + { + public BadJoinExpressionException() + {} + + public BadJoinExpressionException(string message) : base(message) + {} + + public BadJoinExpressionException(string message, Exception inner) : base(message, inner) + {} + + protected BadJoinExpressionException(SerializationInfo info, StreamingContext context) : base(info, context) + {} + } +} \ No newline at end of file diff --git a/Simple.Data/BinderHelper.cs b/Simple.Data/BinderHelper.cs index 273ce7ba..e41e6303 100644 --- a/Simple.Data/BinderHelper.cs +++ b/Simple.Data/BinderHelper.cs @@ -1,55 +1,55 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Text; -using Simple.Data.Extensions; - -namespace Simple.Data -{ - static class BinderHelper - { - private static IDictionary NamedArgumentsToDictionary(IEnumerable argumentNames, IEnumerable args) - { - return argumentNames - .Reverse() - .Zip(args.Reverse(), (k, v) => new KeyValuePair(k, v)) - .Reverse() - .ToDictionary(); - } - - private static IDictionary ArgumentsToDictionary(IEnumerable argumentNames, IEnumerable args) - { - var argsArray = args.ToArray(); - if (argsArray.Length == 1 && argsArray[0] is IDictionary) - return (IDictionary)argsArray[0]; - - return argsArray.Reverse() - .Zip(argumentNames.Reverse().ExtendInfinite(), (v, k) => new KeyValuePair(k, v)) - .Reverse() - .Select((kvp, i) => kvp.Key == null ? new KeyValuePair("_" + i.ToString(), kvp.Value) : kvp) - .ToDictionary(); - } - - internal static IDictionary NamedArgumentsToDictionary(this InvokeMemberBinder binder, IEnumerable args) - { - return NamedArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); - } - - public static IDictionary ArgumentsToDictionary(this InvokeMemberBinder binder, IEnumerable args) - { - return ArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); - } - - internal static IDictionary NamedArgumentsToDictionary(this InvokeBinder binder, IEnumerable args) - { - return NamedArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); - } - - public static IDictionary ArgumentsToDictionary(this InvokeBinder binder, IEnumerable args) - { - return ArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Text; +using Simple.Data.Extensions; + +namespace Simple.Data +{ + static class BinderHelper + { + private static IDictionary NamedArgumentsToDictionary(IEnumerable argumentNames, IEnumerable args) + { + return argumentNames + .Reverse() + .Zip(args.Reverse(), (k, v) => new KeyValuePair(k, v)) + .Reverse() + .ToDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + private static IDictionary ArgumentsToDictionary(IEnumerable argumentNames, IEnumerable args) + { + var argsArray = args.ToArray(); + if (argsArray.Length == 1 && argsArray[0] is IDictionary) + return (IDictionary)argsArray[0]; + + return argsArray.Reverse() + .Zip(argumentNames.Reverse().ExtendInfinite(), (v, k) => new KeyValuePair(k, v)) + .Reverse() + .Select((kvp, i) => kvp.Key == null ? new KeyValuePair("_" + i.ToString(), kvp.Value ?? DBNull.Value) : kvp) + .ToDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + internal static IDictionary NamedArgumentsToDictionary(this InvokeMemberBinder binder, IEnumerable args) + { + return NamedArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); + } + + public static IDictionary ArgumentsToDictionary(this InvokeMemberBinder binder, IEnumerable args) + { + return ArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); + } + + internal static IDictionary NamedArgumentsToDictionary(this InvokeBinder binder, IEnumerable args) + { + return NamedArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); + } + + public static IDictionary ArgumentsToDictionary(this InvokeBinder binder, IEnumerable args) + { + return ArgumentsToDictionary(binder.CallInfo.ArgumentNames, args); + } + } +} diff --git a/Simple.Data/BufferedEnumerable.cs b/Simple.Data/BufferedEnumerable.cs deleted file mode 100644 index 7af1de2f..00000000 --- a/Simple.Data/BufferedEnumerable.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Simple.Data -{ - public static class BufferedEnumerable - { - public static IEnumerable Create(Func> iterator, Action cleanup) - { - var enumerable = new BufferedEnumerable(); - var task = new Task(() => enumerable.Iterate(iterator)); - task.ContinueWith(t => cleanup()); - task.Start(); - return enumerable; - } - } -} \ No newline at end of file diff --git a/Simple.Data/BufferedEnumerable1.cs b/Simple.Data/BufferedEnumerable1.cs deleted file mode 100644 index 633d74cf..00000000 --- a/Simple.Data/BufferedEnumerable1.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; - -namespace Simple.Data -{ - sealed class BufferedEnumerable : IEnumerable - { - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly List _buffer = new List(); - private bool _done; - - internal void Iterate(Func> iterator) - { - Maybe maybe; - while ((maybe = iterator()).HasValue) - { - Add(maybe.Value); - } - SetDone(); - } - - private void SetDone() - { - _lock.EnterWriteLock(); - try - { - _done = true; - } - finally - { - _lock.ExitWriteLock(); - } - } - - private void Add(T item) - { - _lock.EnterWriteLock(); - try - { - _buffer.Add(item); - } - finally - { - _lock.ExitWriteLock(); - } - } - - private T this[int index] - { - get - { - _lock.EnterReadLock(); - try - { - return _buffer[index]; - } - finally - { - _lock.ExitReadLock(); - } - } - } - - private int Count - { - get - { - _lock.EnterReadLock(); - try - { - return _buffer.Count; - } - finally - { - _lock.ExitReadLock(); - } - } - } - - private bool Done - { - get - { - _lock.EnterReadLock(); - try - { - return _done; - } - finally - { - _lock.ExitReadLock(); - } - } - } - - class BufferedEnumerator : IEnumerator - { - private readonly BufferedEnumerable _bufferedEnumerable; - - public BufferedEnumerator(BufferedEnumerable bufferedEnumerable) - { - _bufferedEnumerable = bufferedEnumerable; - } - - private int _current = -1; - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - - } - - /// - /// Advances the enumerator to the next element of the collection. - /// - /// - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// - /// The collection was modified after the enumerator was created. 2 - public bool MoveNext() - { - ++_current; - - if (_bufferedEnumerable._done) - { - return _current < _bufferedEnumerable.Count; - } - - WaitForBuffer(); - return _current < _bufferedEnumerable.Count; - } - - private void WaitForBuffer() - { - // Block until next item delivered or iterator is done. - SpinWait.SpinUntil(() => _bufferedEnumerable.Done || _bufferedEnumerable.Count > _current); - } - - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - /// The collection was modified after the enumerator was created. 2 - public void Reset() - { - _current = -1; - } - - /// - /// Gets the element in the collection at the current position of the enumerator. - /// - /// - /// The element in the collection at the current position of the enumerator. - /// - public T Current - { - get - { - if (_current >= _bufferedEnumerable.Count) throw new InvalidOperationException(); - return _bufferedEnumerable[_current]; - } - } - - /// - /// Gets the current element in the collection. - /// - /// - /// The current element in the collection. - /// - /// The enumerator is positioned before the first element of the collection or after the last element.2 - object IEnumerator.Current - { - get { return Current; } - } - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public IEnumerator GetEnumerator() - { - return new BufferedEnumerator(this); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/Simple.Data/Commands/AllCommand.cs b/Simple.Data/Commands/AllCommand.cs index 9c8a591d..8186c83f 100644 --- a/Simple.Data/Commands/AllCommand.cs +++ b/Simple.Data/Commands/AllCommand.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; -using System.Text; namespace Simple.Data.Commands { @@ -31,18 +28,6 @@ public bool IsCommandFor(string method) public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { return new SimpleQuery(dataStrategy, table.GetQualifiedName()); - //return new SimpleResultSet(dataStrategy.Find(table.GetQualifiedName(), null) - // .Select(dict => new SimpleRecord(dict, table.GetQualifiedName(), dataStrategy))); - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); } } } diff --git a/Simple.Data/Commands/ArgumentHelper.cs b/Simple.Data/Commands/ArgumentHelper.cs new file mode 100644 index 00000000..492eaa5f --- /dev/null +++ b/Simple.Data/Commands/ArgumentHelper.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.Commands +{ + using System.Dynamic; + using Extensions; + + internal static class ArgumentHelper + { + internal static void CheckFindArgs(object[] args, InvokeMemberBinder binder) + { + if (args.Length == 0) throw new ArgumentException(binder.Name + "requires arguments."); + if (args.Length == 1) + { + if (ReferenceEquals(args[0], null) && binder.CallInfo.ArgumentNames.Count == 0) + throw new ArgumentException(binder.Name + " does not accept unnamed null argument."); + } + } + + internal static IEnumerable> CreateCriteriaDictionary(InvokeMemberBinder binder, IList args, params string[] exactNames) + { + IDictionary criteriaDictionary = null; + if (exactNames.Contains(binder.Name)) + { + if (binder.CallInfo.ArgumentNames != null && binder.CallInfo.ArgumentNames.Count > 0) + { + criteriaDictionary = binder.NamedArgumentsToDictionary(args); + } + else if (args.Count == 1) + { + if (ReferenceEquals(args[0], null)) throw new ArgumentException("FindBy does not accept unnamed null argument."); + criteriaDictionary = args[0].ObjectToDictionary(); + } + } + else + { + criteriaDictionary = MethodNameParser.ParseFromBinder(binder, args); + } + + if (criteriaDictionary == null || criteriaDictionary.Count == 0) + { + throw new ArgumentException(binder.Name + " requires an equal number of column names and values to filter data by."); + } + return criteriaDictionary; + } + } +} diff --git a/Simple.Data/Commands/DeleteAllCommand.cs b/Simple.Data/Commands/DeleteAllCommand.cs index f3ec86f5..856c4a2f 100644 --- a/Simple.Data/Commands/DeleteAllCommand.cs +++ b/Simple.Data/Commands/DeleteAllCommand.cs @@ -17,25 +17,15 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe if (args.Length == 0) { - deletedCount = dataStrategy.Delete(table.GetQualifiedName(), new SimpleEmptyExpression()); + deletedCount = dataStrategy.Run.Delete(table.GetQualifiedName(), new SimpleEmptyExpression()); } if (args.Length == 1 && args[0] is SimpleExpression) { - deletedCount = dataStrategy.Delete(table.GetQualifiedName(), (SimpleExpression)args[0]); + deletedCount = dataStrategy.Run.Delete(table.GetQualifiedName(), (SimpleExpression)args[0]); } return deletedCount.ResultSetFromModifiedRowCount(); } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } } } diff --git a/Simple.Data/Commands/DeleteByCommand.cs b/Simple.Data/Commands/DeleteByCommand.cs index d9a7056a..2167c5bc 100644 --- a/Simple.Data/Commands/DeleteByCommand.cs +++ b/Simple.Data/Commands/DeleteByCommand.cs @@ -16,17 +16,7 @@ public bool IsCommandFor(string method) public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { SimpleExpression criteriaExpression = GetCriteriaExpression(binder, args, table); - return dataStrategy.Delete(table.GetQualifiedName(), criteriaExpression); - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + return dataStrategy.Run.Delete(table.GetQualifiedName(), criteriaExpression); } private static SimpleExpression GetCriteriaExpression(InvokeMemberBinder binder, object[] args, DynamicTable table) diff --git a/Simple.Data/Commands/ExecuteFunctionCommand.cs b/Simple.Data/Commands/ExecuteFunctionCommand.cs index 56eb610d..dcb62e28 100644 --- a/Simple.Data/Commands/ExecuteFunctionCommand.cs +++ b/Simple.Data/Commands/ExecuteFunctionCommand.cs @@ -9,12 +9,12 @@ namespace Simple.Data.Commands { public class ExecuteFunctionCommand { - private readonly Database _database; + private readonly DataStrategy _database; private readonly IAdapterWithFunctions _adapter; private readonly string _functionName; private readonly IDictionary _arguments; - public ExecuteFunctionCommand(Database database, IAdapterWithFunctions adapter, string functionName, IDictionary arguments) + public ExecuteFunctionCommand(DataStrategy database, IAdapterWithFunctions adapter, string functionName, IDictionary arguments) { _database = database; _adapter = adapter; diff --git a/Simple.Data/Commands/ExistsByCommand.cs b/Simple.Data/Commands/ExistsByCommand.cs index edfeb681..daf31a78 100644 --- a/Simple.Data/Commands/ExistsByCommand.cs +++ b/Simple.Data/Commands/ExistsByCommand.cs @@ -30,18 +30,11 @@ public bool IsCommandFor(string method) /// public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { - var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); - return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).Exists(); - } + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "ExistsBy", "exists_by", "AnyBy", "any_by"); + if (criteriaDictionary == null) return null; - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), criteriaDictionary); + return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).Exists(); } } } \ No newline at end of file diff --git a/Simple.Data/Commands/ExistsCommand.cs b/Simple.Data/Commands/ExistsCommand.cs index f180c4c1..d94398b8 100644 --- a/Simple.Data/Commands/ExistsCommand.cs +++ b/Simple.Data/Commands/ExistsCommand.cs @@ -29,22 +29,24 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe { var query = new SimpleQuery(dataStrategy, table.GetQualifiedName()); - if (args.Length == 1 && args[0] is SimpleExpression) + if (args.Length == 1) { - query = query.Where((SimpleExpression)args[0]); + var criteria = args[0] as SimpleExpression; + if (criteria != null) + { + query = query.Where(criteria); + } + else + { + throw new BadExpressionException(binder.Name + " requires an expression."); + } + } + else if (args.Length != 0) + { + throw new BadExpressionException(binder.Name + " requires an expression."); } return query.Exists(); } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } } } \ No newline at end of file diff --git a/Simple.Data/Commands/FindAllByCommand.cs b/Simple.Data/Commands/FindAllByCommand.cs index 9be6f9ef..b7678a84 100644 --- a/Simple.Data/Commands/FindAllByCommand.cs +++ b/Simple.Data/Commands/FindAllByCommand.cs @@ -14,26 +14,15 @@ public bool IsCommandFor(string method) public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { - SimpleExpression criteriaExpression; if (binder.Name.Equals("FindAllBy") || binder.Name.Equals("find_all_by")) { - criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), binder.NamedArgumentsToDictionary(args)); + ArgumentHelper.CheckFindArgs(args, binder); } - else - { - criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); - } - return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteriaExpression); - } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "FindAllBy", "find_all_by"); + var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), + criteriaDictionary); + return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteriaExpression); } } } diff --git a/Simple.Data/Commands/FindAllCommand.cs b/Simple.Data/Commands/FindAllCommand.cs index b374c2d4..1bbe0f2a 100644 --- a/Simple.Data/Commands/FindAllCommand.cs +++ b/Simple.Data/Commands/FindAllCommand.cs @@ -35,17 +35,7 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where((SimpleExpression)args[0]); } - return null; - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + throw new BadExpressionException("FindAll only accepts a criteria expression."); } } } diff --git a/Simple.Data/Commands/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index 736a413b..04bbdfd6 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -6,9 +6,10 @@ namespace Simple.Data.Commands { + using System.Reflection; using Extensions; - class FindByCommand : ICommand + class FindByCommand : ICommand, ICreateDelegate, IQueryCompatibleCommand { public bool IsCommandFor(string method) { @@ -19,7 +20,13 @@ public Func CreateDelegate(DataStrategy dataStrategy, DynamicT { if (dataStrategy is SimpleTransaction) return null; - var criteriaDictionary = CreateCriteriaDictionary(binder, args); + if (binder.Name.Equals("FindBy") || binder.Name.Equals("find_by")) + { + ArgumentHelper.CheckFindArgs(args, binder); + if (args.Length == 1 && args[0].IsAnonymous()) return null; + } + + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "FindBy", "find_by"); if (criteriaDictionary == null) return null; var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), criteriaDictionary); @@ -41,15 +48,21 @@ public Func CreateDelegate(DataStrategy dataStrategy, DynamicT } } - private static IDictionary CreateCriteriaDictionary(InvokeMemberBinder binder, IList args) + private static IEnumerable> CreateCriteriaDictionary(InvokeMemberBinder binder, IList args) { IDictionary criteriaDictionary = null; if (binder.Name.Equals("FindBy") || binder.Name.Equals("find_by")) { + if (args.Count == 0) throw new ArgumentException("FindBy requires arguments."); if (binder.CallInfo.ArgumentNames != null && binder.CallInfo.ArgumentNames.Count > 0) { criteriaDictionary = binder.NamedArgumentsToDictionary(args); } + else if (args.Count == 1) + { + if (ReferenceEquals(args[0], null)) throw new ArgumentException("FindBy does not accept unnamed null argument."); + criteriaDictionary = args[0].ObjectToDictionary(); + } } else { @@ -60,8 +73,10 @@ private static IDictionary CreateCriteriaDictionary(InvokeMember public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { - var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); - var data = dataStrategy.FindOne(table.GetQualifiedName(), criteriaExpression); + var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), + CreateCriteriaDictionary(binder, + args)); + var data = dataStrategy.Run.FindOne(table.GetQualifiedName(), criteriaExpression); return data != null ? new SimpleRecord(data, table.GetQualifiedName(), dataStrategy) : null; } diff --git a/Simple.Data/Commands/FindCommand.cs b/Simple.Data/Commands/FindCommand.cs index 8c38aed5..c5bf450e 100644 --- a/Simple.Data/Commands/FindCommand.cs +++ b/Simple.Data/Commands/FindCommand.cs @@ -6,7 +6,7 @@ namespace Simple.Data.Commands { - class FindCommand : ICommand + class FindCommand : ICommand, IQueryCompatibleCommand { /// /// Determines whether the instance is able to handle the specified method. @@ -32,21 +32,16 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe { if (args.Length == 1 && args[0] is SimpleExpression) { - var data = dataStrategy.FindOne(table.GetQualifiedName(), (SimpleExpression)args[0]); + var data = dataStrategy.Run.FindOne(table.GetQualifiedName(), (SimpleExpression)args[0]); return data != null ? new SimpleRecord(data, table.GetQualifiedName(), dataStrategy) : null; } - return null; + throw new BadExpressionException("Find only accepts a criteria expression."); } public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) { return query.Where((SimpleExpression) args[0]).Take(1).FirstOrDefault(); } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } } } diff --git a/Simple.Data/Commands/GetCommand.cs b/Simple.Data/Commands/GetCommand.cs index 8377d449..e30547e7 100644 --- a/Simple.Data/Commands/GetCommand.cs +++ b/Simple.Data/Commands/GetCommand.cs @@ -8,7 +8,7 @@ namespace Simple.Data.Commands { using Extensions; - public class GetCommand : ICommand + public class GetCommand : ICommand, ICreateDelegate, IQueryCompatibleCommand { public bool IsCommandFor(string method) { @@ -17,7 +17,7 @@ public bool IsCommandFor(string method) public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { - var result = dataStrategy.Get(table.GetName(), args); + var result = dataStrategy.Run.Get(table.GetName(), args); if (result == null || result.Count == 0) return null; return binder.Name.Equals("get", StringComparison.OrdinalIgnoreCase) ? new SimpleRecord(result, table.GetQualifiedName(), dataStrategy) @@ -39,7 +39,7 @@ public Func CreateDelegate(DataStrategy dataStrategy, DynamicT if (dataStrategy is SimpleTransaction) return null; var func = dataStrategy.GetAdapter().OptimizingDelegateFactory.CreateGetDelegate(dataStrategy.GetAdapter(), - table.GetName(), args); + table.GetQualifiedName(), args); return a => { var data = func(a); diff --git a/Simple.Data/Commands/GetCountByCommand.cs b/Simple.Data/Commands/GetCountByCommand.cs index 248258d7..0bd4a245 100644 --- a/Simple.Data/Commands/GetCountByCommand.cs +++ b/Simple.Data/Commands/GetCountByCommand.cs @@ -27,18 +27,15 @@ public bool IsCommandFor(string method) /// public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { - var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); - return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).Count(); - } + if (binder.Name.Equals("GetCountBy") || binder.Name.Equals("get_count_by")) + { + ArgumentHelper.CheckFindArgs(args, binder); + } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "GetCountBy", "get_count_by"); + var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), + criteriaDictionary); + return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).Count(); } } } \ No newline at end of file diff --git a/Simple.Data/Commands/GetCountCommand.cs b/Simple.Data/Commands/GetCountCommand.cs index 5c9ca9a8..947148d0 100644 --- a/Simple.Data/Commands/GetCountCommand.cs +++ b/Simple.Data/Commands/GetCountCommand.cs @@ -32,22 +32,24 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe { var query = new SimpleQuery(dataStrategy, table.GetQualifiedName()); - if (args.Length == 1 && args[0] is SimpleExpression) + if (args.Length == 1) { - query = query.Where((SimpleExpression)args[0]); + var expression = args[0] as SimpleExpression; + if (expression != null) + { + return query.Where(expression).Count(); + } + else + { + throw new BadExpressionException("GetCount expects an expression or no parameters."); + } + } + else if (args.Length == 0) + { + return query.Count(); } - return query.Count(); - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + throw new ArgumentException("GetCount expects an expression or no parameters."); } } } diff --git a/Simple.Data/Commands/ICommand.cs b/Simple.Data/Commands/ICommand.cs index 46175c23..d620971c 100644 --- a/Simple.Data/Commands/ICommand.cs +++ b/Simple.Data/Commands/ICommand.cs @@ -26,9 +26,15 @@ interface ICommand /// The arguments from the method invocation. /// object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args); + } + interface IQueryCompatibleCommand + { object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args); + } + interface ICreateDelegate + { Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args); } diff --git a/Simple.Data/Commands/InsertCommand.cs b/Simple.Data/Commands/InsertCommand.cs index 7d709133..c4fbf673 100644 --- a/Simple.Data/Commands/InsertCommand.cs +++ b/Simple.Data/Commands/InsertCommand.cs @@ -23,16 +23,6 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return ResultHelper.TypeResult(result, table, dataStrategy); } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - private static object DoInsert(InvokeMemberBinder binder, object[] args, DataStrategy dataStrategy, string tableName) { if (binder.HasSingleUnnamedArgument()) @@ -51,20 +41,20 @@ private static object DoInsert(InvokeMemberBinder binder, object[] args, DataStr return InsertDictionary(binder, args, dataStrategy, tableName); } - private static object InsertDictionary(InvokeMemberBinder binder, object[] args, DataStrategy dataStrategy, string tableName) + private static object InsertDictionary(InvokeMemberBinder binder, IEnumerable args, DataStrategy dataStrategy, string tableName) { - return dataStrategy.Insert(tableName, binder.NamedArgumentsToDictionary(args), !binder.IsResultDiscarded()); + return dataStrategy.Run.Insert(tableName, binder.NamedArgumentsToDictionary(args), !binder.IsResultDiscarded()); } private static object InsertEntity(object entity, DataStrategy dataStrategy, string tableName, ErrorCallback onError, bool resultRequired) { var dictionary = entity as IDictionary; if (dictionary != null) - return dataStrategy.Insert(tableName, dictionary, resultRequired); + return dataStrategy.Run.Insert(tableName, dictionary, resultRequired); var list = entity as IEnumerable>; if (list != null) - return dataStrategy.InsertMany(tableName, list, onError, resultRequired); + return dataStrategy.Run.InsertMany(tableName, list, onError, resultRequired); var entityList = entity as IEnumerable; if (entityList != null) @@ -81,13 +71,13 @@ private static object InsertEntity(object entity, DataStrategy dataStrategy, str rows.Add(dictionary); } - return dataStrategy.InsertMany(tableName, rows, onError, resultRequired); + return dataStrategy.Run.InsertMany(tableName, rows, onError, resultRequired); } dictionary = entity.ObjectToDictionary(); if (dictionary.Count == 0) throw new SimpleDataException("Could not discover data in object."); - return dataStrategy.Insert(tableName, dictionary, resultRequired); + return dataStrategy.Run.Insert(tableName, dictionary, resultRequired); } } } diff --git a/Simple.Data/Commands/QueryByCommand.cs b/Simple.Data/Commands/QueryByCommand.cs index 65996c93..2d36cca1 100644 --- a/Simple.Data/Commands/QueryByCommand.cs +++ b/Simple.Data/Commands/QueryByCommand.cs @@ -10,16 +10,6 @@ public bool IsCommandFor(string method) return method.StartsWith("QueryBy") || method.StartsWith("query_by_", StringComparison.InvariantCultureIgnoreCase); } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) { return CreateSimpleQuery(table, binder, args, dataStrategy); @@ -31,27 +21,4 @@ private static object CreateSimpleQuery(DynamicTable table, InvokeMemberBinder b return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteriaExpression); } } - - class QueryCommand : ICommand - { - public bool IsCommandFor(string method) - { - return method.Equals("query", StringComparison.InvariantCultureIgnoreCase); - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - return a => new SimpleQuery(dataStrategy, table.GetQualifiedName()); - } - - public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - return new SimpleQuery(dataStrategy, table.GetQualifiedName()); - } - } } \ No newline at end of file diff --git a/Simple.Data/Commands/QueryCommand.cs b/Simple.Data/Commands/QueryCommand.cs new file mode 100644 index 00000000..02187341 --- /dev/null +++ b/Simple.Data/Commands/QueryCommand.cs @@ -0,0 +1,28 @@ +namespace Simple.Data.Commands +{ + using System; + using System.Dynamic; + + class QueryCommand : ICommand, ICreateDelegate + { + public bool IsCommandFor(string method) + { + return method.Equals("query", StringComparison.InvariantCultureIgnoreCase); + } + + public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) + { + throw new NotImplementedException(); + } + + public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) + { + return a => new SimpleQuery(dataStrategy, table.GetQualifiedName()); + } + + public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) + { + return new SimpleQuery(dataStrategy, table.GetQualifiedName()); + } + } +} \ No newline at end of file diff --git a/Simple.Data/Commands/UpdateAllCommand.cs b/Simple.Data/Commands/UpdateAllCommand.cs index db029479..e485743f 100644 --- a/Simple.Data/Commands/UpdateAllCommand.cs +++ b/Simple.Data/Commands/UpdateAllCommand.cs @@ -28,19 +28,9 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe throw new SimpleDataException("Could not resolve data."); } - var updatedCount = dataStrategy.Update(table.GetQualifiedName(), data, criteria); + var updatedCount = dataStrategy.Run.Update(table.GetQualifiedName(), data, criteria); return updatedCount.ResultSetFromModifiedRowCount(); } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } } } diff --git a/Simple.Data/Commands/UpdateByCommand.cs b/Simple.Data/Commands/UpdateByCommand.cs index 10186331..3949e839 100644 --- a/Simple.Data/Commands/UpdateByCommand.cs +++ b/Simple.Data/Commands/UpdateByCommand.cs @@ -27,29 +27,19 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe var data = binder.NamedArgumentsToDictionary(args) .Where(kvp => !criteria.ContainsKey(kvp.Key)) .ToDictionary(); - return dataStrategy.Update(table.GetQualifiedName(), data, criteriaExpression); - } - - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + return dataStrategy.Run.Update(table.GetQualifiedName(), data, criteriaExpression); } internal static object UpdateByKeyFields(string tableName, DataStrategy dataStrategy, object entity, IEnumerable keyFieldNames) { var record = UpdateCommand.ObjectToDictionary(entity); var list = record as IList>; - if (list != null) return dataStrategy.UpdateMany(tableName, list, keyFieldNames); + if (list != null) return dataStrategy.Run.UpdateMany(tableName, list, keyFieldNames); var dict = record as IDictionary; var criteria = GetCriteria(keyFieldNames, dict); var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteria); - return dataStrategy.Update(tableName, dict, criteriaExpression); + return dataStrategy.Run.Update(tableName, dict, criteriaExpression); } private static IEnumerable> GetCriteria(IEnumerable keyFieldNames, IDictionary record) @@ -59,7 +49,7 @@ private static IEnumerable> GetCriteria(IEnumerable foreach (var keyFieldName in keyFieldNames) { var name = keyFieldName; - var keyValuePair = record.Where(kvp => kvp.Key.Homogenize().Equals(name.Homogenize())).SingleOrDefault(); + var keyValuePair = record.SingleOrDefault(kvp => kvp.Key.Homogenize().Equals(name.Homogenize())); if (string.IsNullOrWhiteSpace(keyValuePair.Key)) { throw new InvalidOperationException("Key field value not set."); diff --git a/Simple.Data/Commands/UpdateCommand.cs b/Simple.Data/Commands/UpdateCommand.cs index c641385c..9a2881e4 100644 --- a/Simple.Data/Commands/UpdateCommand.cs +++ b/Simple.Data/Commands/UpdateCommand.cs @@ -29,11 +29,6 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return UpdateUsingOriginalValues(dataStrategy, table, args); } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - private static object UpdateUsingOriginalValues(DataStrategy dataStrategy, DynamicTable table, object[] args) { var newValues = ObjectToDictionary(args[0]); @@ -42,32 +37,27 @@ private static object UpdateUsingOriginalValues(DataStrategy dataStrategy, Dynam { var originalValuesList = ObjectToDictionary(args[1]) as IList>; if (originalValuesList == null) throw new InvalidOperationException("Parameter type mismatch; both parameters to Update should be same type."); - return dataStrategy.UpdateMany(table.GetQualifiedName(), newValuesList, originalValuesList); + return dataStrategy.Run.UpdateMany(table.GetQualifiedName(), newValuesList, originalValuesList); } var newValuesDict = newValues as IDictionary; var originalValuesDict = ObjectToDictionary(args[1]) as IDictionary; if (originalValuesDict == null) throw new InvalidOperationException("Parameter type mismatch; both parameters to Update should be same type."); - return dataStrategy.Update(table.GetQualifiedName(), newValuesDict, originalValuesDict); + return dataStrategy.Run.Update(table.GetQualifiedName(), newValuesDict, originalValuesDict); } private static object UpdateUsingKeys(DataStrategy dataStrategy, DynamicTable table, object[] args) { var record = ObjectToDictionary(args[0]); var list = record as IList>; - if (list != null) return dataStrategy.UpdateMany(table.GetQualifiedName(), list); + if (list != null) return dataStrategy.Run.UpdateMany(table.GetQualifiedName(), list); var dict = record as IDictionary; if (dict == null) throw new InvalidOperationException("Could not resolve data from passed object."); var key = dataStrategy.GetAdapter().GetKey(table.GetQualifiedName(), dict); dict = dict.Where(kvp => key.All(keyKvp => keyKvp.Key.Homogenize() != kvp.Key.Homogenize())).ToDictionary(); var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), key); - return dataStrategy.Update(table.GetQualifiedName(), dict, criteria); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + return dataStrategy.Run.Update(table.GetQualifiedName(), dict, criteria); } internal static object ObjectToDictionary(object obj) @@ -81,7 +71,7 @@ internal static object ObjectToDictionary(object obj) var dictionary = obj as IDictionary; if (dictionary != null) { - return dictionary; + return new Dictionary(dictionary, HomogenizedEqualityComparer.DefaultInstance); } var list = obj as IEnumerable; diff --git a/Simple.Data/Commands/UpsertByCommand.cs b/Simple.Data/Commands/UpsertByCommand.cs index e1f52f60..428f5cc2 100644 --- a/Simple.Data/Commands/UpsertByCommand.cs +++ b/Simple.Data/Commands/UpsertByCommand.cs @@ -30,32 +30,22 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), criteria); var data = binder.NamedArgumentsToDictionary(args); - result = dataStrategy.Upsert(table.GetQualifiedName(), data, criteriaExpression, !binder.IsResultDiscarded()); + result = dataStrategy.Run.Upsert(table.GetQualifiedName(), data, criteriaExpression, !binder.IsResultDiscarded()); } return ResultHelper.TypeResult(result, table, dataStrategy); } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - internal static object UpsertByKeyFields(string tableName, DataStrategy dataStrategy, object entity, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) { var record = UpdateCommand.ObjectToDictionary(entity); var list = record as IList>; - if (list != null) return dataStrategy.UpsertMany(tableName, list, keyFieldNames, isResultRequired, errorCallback); + if (list != null) return dataStrategy.Run.UpsertMany(tableName, list, keyFieldNames, isResultRequired, errorCallback); var dict = record as IDictionary; var criteria = GetCriteria(keyFieldNames, dict); var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteria); - return dataStrategy.Upsert(tableName, dict, criteriaExpression, isResultRequired); + return dataStrategy.Run.Upsert(tableName, dict, criteriaExpression, isResultRequired); } private static IEnumerable> GetCriteria(IEnumerable keyFieldNames, IDictionary record) @@ -72,7 +62,6 @@ private static IEnumerable> GetCriteria(IEnumerable } criteria.Add(keyFieldName, keyValuePair.Value); - record.Remove(keyValuePair); } return criteria; } diff --git a/Simple.Data/Commands/UpsertCommand.cs b/Simple.Data/Commands/UpsertCommand.cs index 53e93f82..b6555d47 100644 --- a/Simple.Data/Commands/UpsertCommand.cs +++ b/Simple.Data/Commands/UpsertCommand.cs @@ -35,11 +35,6 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return ResultHelper.TypeResult(result, table, dataStrategy); } - public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable table, object[] args, bool isResultRequired) { var record = ObjectToDictionary(args[0]); @@ -47,21 +42,17 @@ private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable ta if (list != null) { ErrorCallback errorCallback = (args.Length == 2 ? args[1] as ErrorCallback : null) ?? - ((item, exception) => false); - return dataStrategy.UpsertMany(table.GetQualifiedName(), list, isResultRequired, errorCallback); + ((item, exception) => false); + return dataStrategy.Run.UpsertMany(table.GetQualifiedName(), list, isResultRequired, errorCallback); } var dict = record as IDictionary; if (dict == null) throw new InvalidOperationException("Could not resolve data from passed object."); var key = dataStrategy.GetAdapter().GetKey(table.GetQualifiedName(), dict); - + if (key == null) throw new InvalidOperationException(string.Format("No key columns defined for table \"{0}\"",table.GetQualifiedName())); + if (key.Count == 0) return dataStrategy.Run.Insert(table.GetQualifiedName(), dict, isResultRequired); var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), key); - return dataStrategy.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); - } - - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); + return dataStrategy.Run.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); } internal static object ObjectToDictionary(object obj) diff --git a/Simple.Data/Composer.cs b/Simple.Data/Composer.cs index e6c5d376..ebe04bcd 100644 --- a/Simple.Data/Composer.cs +++ b/Simple.Data/Composer.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; +using System.Security; using System.Text; namespace Simple.Data { public abstract class Composer { + protected static readonly Assembly ThisAssembly = typeof (Composer).Assembly; private static Composer _composer = new MefHelper(); public static Composer Default @@ -22,5 +26,53 @@ internal static void SetDefault(Composer composer) { _composer = composer; } + + public static string GetSimpleDataAssemblyPath() + { + var path = ThisAssembly.CodeBase.Replace("file:///", "").Replace("file://", "//"); + path = Path.GetDirectoryName(path); + if (path == null) throw new ArgumentException("Unrecognised assemblyFile."); + if (!Path.IsPathRooted(path)) + { + path = Path.DirectorySeparatorChar + path; + } + return path; + } + + public static bool TryLoadAssembly(string assemblyFile, out Assembly assembly) + { + if (assemblyFile == null) throw new ArgumentNullException("assemblyFile"); + if (assemblyFile.Length == 0) throw new ArgumentException("Assembly file name is empty.", "assemblyFile"); + try + { + assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile); + return true; + } + catch (FileNotFoundException) + { + assembly = null; + return false; + } + catch(FileLoadException) + { + assembly = null; + return false; + } + catch (BadImageFormatException) + { + assembly = null; + return false; + } + catch(SecurityException) + { + assembly = null; + return false; + } + catch (PathTooLongException) + { + assembly = null; + return false; + } + } } } diff --git a/Simple.Data/ConcreteTypeCreator.cs b/Simple.Data/ConcreteTypeCreator.cs index 4399b989..214b5fe1 100644 --- a/Simple.Data/ConcreteTypeCreator.cs +++ b/Simple.Data/ConcreteTypeCreator.cs @@ -31,9 +31,10 @@ private ConcreteTypeCreator(Lazy, object>> func public object Create(IDictionary source) { - return _func.Value(source); + var func = _func.Value; + return func(source); } - + public bool TryCreate(IDictionary source, out object result) { try diff --git a/Simple.Data/DataStrategy.cs b/Simple.Data/DataStrategy.cs index 246da312..e1f2990b 100644 --- a/Simple.Data/DataStrategy.cs +++ b/Simple.Data/DataStrategy.cs @@ -8,13 +8,22 @@ namespace Simple.Data { + using System.Reflection; + /// /// This class supports the Simple.Data framework internally and should not be used in your code. /// - public abstract class DataStrategy : DynamicObject + public abstract class DataStrategy : DynamicObject, ICloneable { private readonly ConcurrentDictionary _members = new ConcurrentDictionary(); + protected DataStrategy() {} + + protected DataStrategy(DataStrategy copy) + { + _members = copy._members; + } + public abstract Adapter GetAdapter(); /// @@ -41,27 +50,31 @@ private dynamic GetOrAddDynamicReference(string name) return _members.GetOrAdd(name, CreateDynamicReference); } - internal bool TryInvokeFunction(String functionName, Func> getFunctionArguments, out object result) + internal bool TryInvokeFunction(String functionName, Func> getFunctionArguments, + out object result) { var adapterWithFunctions = GetAdapter() as IAdapterWithFunctions; if (adapterWithFunctions != null && adapterWithFunctions.IsValidFunction(functionName)) { - var command = new ExecuteFunctionCommand(GetDatabase(), adapterWithFunctions, functionName, getFunctionArguments()); + var command = new ExecuteFunctionCommand(GetDatabase(), adapterWithFunctions, functionName, + getFunctionArguments()); return ExecuteFunction(out result, command); } result = null; return false; } - protected abstract bool ExecuteFunction(out object result, ExecuteFunctionCommand command); + protected internal abstract bool ExecuteFunction(out object result, ExecuteFunctionCommand command); public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { - if (this.TryInvokeFunction(binder.Name, () => binder.ArgumentsToDictionary(args), out result)) return true; + if (TryInvokeFunction(binder.Name, () => binder.ArgumentsToDictionary(args), out result)) return true; + + if (new AdapterMethodDynamicInvoker(GetAdapter()).TryInvokeMember(binder, args, out result)) return true; return base.TryInvokeMember(binder, args, out result); } - + public dynamic this[string name] { get { return GetOrAddDynamicReference(name); } @@ -76,98 +89,54 @@ internal DynamicTable SetMemberAsTable(ObjectReference reference) { if (reference == null) throw new ArgumentNullException("reference"); _members.TryUpdate(reference.GetName(), new DynamicTable(reference.GetName(), this), reference); - return (DynamicTable)_members[reference.GetName()]; + return (DynamicTable) _members[reference.GetName()]; } internal DynamicTable SetMemberAsTable(ObjectReference reference, DynamicTable table) { if (reference == null) throw new ArgumentNullException("reference"); _members.TryUpdate(reference.GetName(), table, reference); - return (DynamicTable)_members[reference.GetName()]; + return (DynamicTable) _members[reference.GetName()]; } internal DynamicSchema SetMemberAsSchema(ObjectReference reference) { - if (reference == null) throw new ArgumentNullException("reference"); + if (ReferenceEquals(reference, null)) throw new ArgumentNullException("reference"); _members.TryUpdate(reference.GetName(), new DynamicSchema(reference.GetName(), this), reference); - return (DynamicSchema)_members[reference.GetName()]; + return (DynamicSchema) _members[reference.GetName()]; } internal DynamicSchema SetMemberAsSchema(ObjectReference reference, DynamicSchema schema) { if (reference == null) throw new ArgumentNullException("reference"); _members.TryUpdate(reference.GetName(), schema, reference); - return (DynamicSchema)_members[reference.GetName()]; + return (DynamicSchema) _members[reference.GetName()]; } - protected internal abstract Database GetDatabase(); - - /// - /// Finds data from the specified "table". - /// - /// Name of the table. - /// The criteria. This may be null, in which case all records should be returned. - /// The list of records matching the criteria. If no records are found, return an empty list. - internal abstract IEnumerable> Find(string tableName, SimpleExpression criteria); - - /// - /// Inserts a record into the specified "table". - /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. - internal abstract IEnumerable> InsertMany(string tableName, IEnumerable> enumerable, ErrorCallback onError, bool resultRequired); - - /// - /// Inserts many records into the specified "table". - /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. - internal abstract IDictionary Insert(string tableName, IDictionary data, bool resultRequired); - - /// - /// Updates the specified "table" according to specified criteria. - /// Name of the table.The new values.The expression to use as criteria for the update operation.The number of records affected by the update operation. - internal abstract int Update(string tableName, IDictionary data, SimpleExpression criteria); - - /// - /// Deletes from the specified table. - /// Name of the table.The expression to use as criteria for the delete operation.The number of records which were deleted. - internal abstract int Delete(string tableName, SimpleExpression criteria); - - internal abstract IDictionary FindOne(string getQualifiedName, SimpleExpression criteriaExpression); - internal abstract int UpdateMany(string tableName, IList> dataList); + protected internal abstract DataStrategy GetDatabase(); internal bool IsExpressionFunction(string name, object[] args) { return GetAdapter().IsExpressionFunction(name, args); } - internal abstract int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames); + internal abstract RunStrategy Run { get; } - internal abstract int UpdateMany(string tableName, IList> newValuesList, - IList> originalValuesList); + object ICloneable.Clone() + { + return Clone(); + } - public abstract int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict); + protected internal abstract DataStrategy Clone(); - protected static SimpleExpression CreateCriteriaFromOriginalValues(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) + public dynamic WithOptions(OptionsBase options) { - var criteriaValues = originalValuesDict - .Where(originalKvp => newValuesDict.ContainsKey(originalKvp.Key) && !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value))); - - return ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteriaValues); + return new DataStrategyWithOptions(this, options); } - - protected static Dictionary CreateChangedValuesDict(IEnumerable> newValuesDict, IDictionary originalValuesDict) + + public virtual dynamic ClearOptions() { - var changedValuesDict = - newValuesDict.Where( - kvp => - (!originalValuesDict.ContainsKey(kvp.Key)) || !(Equals(kvp.Value, originalValuesDict[kvp.Key]))) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - return changedValuesDict; + return this; } - - public abstract IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback); - - public abstract IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired); - - public abstract IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback); - public abstract IDictionary Get(string tableName, object[] args); } } diff --git a/Simple.Data/DataStrategyWithOptions.cs b/Simple.Data/DataStrategyWithOptions.cs new file mode 100644 index 00000000..82aef797 --- /dev/null +++ b/Simple.Data/DataStrategyWithOptions.cs @@ -0,0 +1,66 @@ +using System.Data; +using Simple.Data.Commands; + +namespace Simple.Data +{ + internal class DataStrategyWithOptions : DataStrategy + { + private readonly DataStrategy _wrappedStrategy; + private readonly OptionsBase _options; + + public DataStrategyWithOptions(DataStrategy wrappedStrategy, OptionsBase options) + { + _options = options; + _wrappedStrategy = wrappedStrategy.Clone(); + _wrappedStrategy.GetAdapter().Options = options; + } + + public override Adapter GetAdapter() + { + var adapter = _wrappedStrategy.GetAdapter(); + adapter.Options = _options; + return adapter; + } + + public SimpleTransaction BeginTransaction() + { + return SimpleTransaction.Begin(this); + } + + public SimpleTransaction BeginTransaction(string name) + { + return SimpleTransaction.Begin(this, name); + } + + public SimpleTransaction BeginTransaction(IsolationLevel isolationLevel) + { + return SimpleTransaction.Begin(this, isolationLevel); + } + + protected internal override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) + { + return _wrappedStrategy.ExecuteFunction(out result, command); + } + + protected internal override DataStrategy GetDatabase() + { + return _wrappedStrategy.GetDatabase(); + } + + internal override RunStrategy Run + { + get { return _wrappedStrategy.Run; } + } + + protected internal override DataStrategy Clone() + { + return new DataStrategyWithOptions(_wrappedStrategy, _options); + } + + public override dynamic ClearOptions() + { + _wrappedStrategy.GetAdapter().Options = null; + return _wrappedStrategy; + } + } +} \ No newline at end of file diff --git a/Simple.Data/Database.Open.cs b/Simple.Data/Database.Open.cs index c3a1310d..bead9fb5 100644 --- a/Simple.Data/Database.Open.cs +++ b/Simple.Data/Database.Open.cs @@ -9,9 +9,9 @@ public partial class Database /// specified in the 'Simple.Data.Properties.Settings.ConnectionString' config ConnectionStrings setting. /// /// A object as a dynamic type. - public static dynamic Open() + public static dynamic Open(string schemaName = null) { - return DatabaseOpener.OpenDefault(); + return DatabaseOpener.OpenDefault(schemaName); } /// @@ -36,9 +36,19 @@ public static dynamic OpenFile(string filename) return DatabaseOpener.OpenFile(filename); } - public static dynamic OpenNamedConnection(string connectionName) + public static dynamic OpenNamedConnection(string connectionName, string schemaName = null) { - return DatabaseOpener.OpenNamedConnection(connectionName); + return DatabaseOpener.OpenNamedConnection(connectionName, schemaName); + } + + public static dynamic OpenConnection(string connectionString,string providerName) + { + return DatabaseOpener.OpenConnection(connectionString, providerName); + } + + public static dynamic OpenConnection(string connectionString, string providerName, string schemaName) + { + return DatabaseOpener.OpenConnection(connectionString, providerName, schemaName); } } } \ No newline at end of file diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 0085f24d..15a66768 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -1,34 +1,83 @@ using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Text; -using System.Threading; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using Simple.Data.Commands; +using Simple.Data.Extensions; namespace Simple.Data { - using System.Configuration; - using System.Diagnostics; - /// /// The entry class for Simple.Data. Provides static methods for opening databases, /// and implements runtime dynamic functionality for resolving database-level objects. /// - public partial class Database : DataStrategy + public sealed partial class Database : DataStrategy { - private static readonly SimpleDataConfigurationSection Configuration; + private static SimpleDataConfigurationSection _configuration; private static readonly IDatabaseOpener DatabaseOpener; private static IPluralizer _pluralizer; + private static bool _loadedConfig; private readonly Adapter _adapter; + private readonly DatabaseRunner _databaseRunner; static Database() { DatabaseOpener = new DatabaseOpener(); - Configuration = - (SimpleDataConfigurationSection) ConfigurationManager.GetSection("simpleData/simpleDataConfiguration") - ?? new SimpleDataConfigurationSection(); - TraceLevel = Configuration.TraceLevel; + } + + private static void EnsureLoadedTraceLevelFromConfig() + { + if (_loadedConfig) + return; + _loadedConfig = true; + _configuration = (SimpleDataConfigurationSection)ConfigurationManager.GetSection("simpleData/simpleDataConfiguration"); + if (_configuration != null) + { + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Warning, SimpleDataTraceSources.ObsoleteWarningMessageId, + "SimpleDataConfiguration section is obsolete; use system.diagnostics switches instead."); + SimpleDataTraceSources.TraceSource.Switch.Level = TraceLevelToSourceLevels(_configuration.TraceLevel); + } + } + + private static SourceLevels TraceLevelToSourceLevels(TraceLevel traceLevel) + { + switch (traceLevel) + { + case TraceLevel.Off: + return SourceLevels.Off; + case TraceLevel.Error: + return SourceLevels.Error; + case TraceLevel.Warning: + return SourceLevels.Warning; + case TraceLevel.Info: + return SourceLevels.Information; + case TraceLevel.Verbose: + return SourceLevels.Verbose; + default: + throw new ArgumentOutOfRangeException("traceLevel"); + } + } + + private static TraceLevel SourceLevelsToTraceLevel(SourceLevels sourceLevels) + { + switch (sourceLevels) + { + case SourceLevels.Off: + return TraceLevel.Off; + case SourceLevels.Critical: + case SourceLevels.Error: + return TraceLevel.Error; + case SourceLevels.Warning: + return TraceLevel.Warning; + case SourceLevels.Information: + return TraceLevel.Info; + case SourceLevels.Verbose: + case SourceLevels.All: + return TraceLevel.Verbose; + default: + return TraceLevel.Off; // happens if SourceLevel is assigned and TraceLevel is read, which is very unlikely + } } /// @@ -38,6 +87,13 @@ static Database() internal Database(Adapter adapter) { _adapter = adapter; + _databaseRunner = new DatabaseRunner(_adapter); + } + + private Database(Database copy) : base(copy) + { + _adapter = copy._adapter; + _databaseRunner = copy._databaseRunner; } public override Adapter GetAdapter() @@ -60,101 +116,6 @@ public static dynamic Default get { return DatabaseOpener.OpenDefault(); } } - internal override IDictionary FindOne(string tableName, SimpleExpression criteria) - { - return _adapter.FindOne(tableName, criteria); - } - - internal override int UpdateMany(string tableName, IList> dataList) - { - return _adapter.UpdateMany(tableName, dataList); - } - - internal override int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames) - { - return _adapter.UpdateMany(tableName, dataList, criteriaFieldNames); - } - - internal override int UpdateMany(string tableName, IList> newValuesList, IList> originalValuesList) - { - int count = 0; - for (int i = 0; i < newValuesList.Count; i++) - { - count += Update(tableName, newValuesList[i], originalValuesList[i]); - } - return count; - } - - public override int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) - { - SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); - var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); - return _adapter.Update(tableName, changedValuesDict, criteria); - } - - /// - /// Finds data from the specified "table". - /// - /// Name of the table. - /// The criteria. This may be null, in which case all records should be returned. - /// The list of records matching the criteria. If no records are found, return an empty list. - internal override IEnumerable> Find(string tableName, SimpleExpression criteria) - { - return _adapter.Find(tableName, criteria); - } - - /// - /// Inserts a record into the specified "table". - /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. - internal override IDictionary Insert(string tableName, IDictionary data, bool resultRequired) - { - return _adapter.Insert(tableName, data, resultRequired); - } - - /// - /// Inserts a record into the specified "table". - /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. - internal override IEnumerable> InsertMany(string tableName, IEnumerable> data, ErrorCallback onError, bool resultRequired) - { - return _adapter.InsertMany(tableName, data, (dict, exception) => onError(new SimpleRecord(dict), exception), resultRequired); - } - - /// - /// Updates the specified "table" according to specified criteria. - /// Name of the table.The new values.The expression to use as criteria for the update operation.The number of records affected by the update operation. - internal override int Update(string tableName, IDictionary data, SimpleExpression criteria) - { - return _adapter.Update(tableName, data, criteria); - } - - public override IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired) - { - return _adapter.Upsert(tableName, dict, criteriaExpression, isResultRequired); - } - - public override IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback) - { - return _adapter.UpsertMany(tableName, list, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); - } - - public override IDictionary Get(string tableName, object[] args) - { - return _adapter.Get(tableName, args); - } - - public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) - { - return _adapter.UpsertMany(tableName, list, keyFieldNames, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); - } - - /// - /// Deletes from the specified table. - /// Name of the table.The expression to use as criteria for the delete operation.The number of records which were deleted. - internal override int Delete(string tableName, SimpleExpression criteria) - { - return _adapter.Delete(tableName, criteria); - } - public SimpleTransaction BeginTransaction() { return SimpleTransaction.Begin(this); @@ -165,12 +126,17 @@ public SimpleTransaction BeginTransaction(string name) return SimpleTransaction.Begin(this, name); } - protected override bool ExecuteFunction(out object result, Commands.ExecuteFunctionCommand command) + public SimpleTransaction BeginTransaction(IsolationLevel isolationLevel) + { + return SimpleTransaction.Begin(this, isolationLevel); + } + + protected internal override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) { return command.Execute(out result); } - protected internal override Database GetDatabase() + protected internal override DataStrategy GetDatabase() { return this; } @@ -183,7 +149,7 @@ public static IPluralizer GetPluralizer() public static void SetPluralizer(IPluralizer pluralizer) { _pluralizer = pluralizer; - Extensions.StringExtensions.SetPluralizer(pluralizer); + StringExtensions.SetPluralizer(pluralizer); } public static void ClearAdapterCache() @@ -201,11 +167,30 @@ public static void UseMockAdapter(Func mockAdapterCreator) Data.DatabaseOpener.UseMockAdapter(mockAdapterCreator()); } - private static TraceLevel? _traceLevel; + public static void StopUsingMockAdapter() + { + Data.DatabaseOpener.StopUsingMock(); + } + + [Obsolete("Use SimpleDataTraceSources.TraceSource.Switch.Level instead.")] public static TraceLevel TraceLevel { - get { return _traceLevel ?? Configuration.TraceLevel; } - set { _traceLevel = value; } + get + { + EnsureLoadedTraceLevelFromConfig(); + return SourceLevelsToTraceLevel(SimpleDataTraceSources.TraceSource.Switch.Level); + } + set { SimpleDataTraceSources.TraceSource.Switch.Level = TraceLevelToSourceLevels(value); } + } + + internal override RunStrategy Run + { + get { return _databaseRunner; } + } + + protected internal override DataStrategy Clone() + { + return new Database(this); } } } \ No newline at end of file diff --git a/Simple.Data/DatabaseOpener.cs b/Simple.Data/DatabaseOpener.cs index d82fd5bc..c196e8f5 100644 --- a/Simple.Data/DatabaseOpener.cs +++ b/Simple.Data/DatabaseOpener.cs @@ -13,9 +13,9 @@ protected static DatabaseOpenerMethods OpenMethods get { return LocalOpenMethods.Value; } } - public dynamic OpenDefault() + public dynamic OpenDefault(string schemaName = null) { - return OpenMethods.OpenDefaultImpl(); + return OpenMethods.OpenDefaultImpl(schemaName); } public dynamic OpenFile(string filename) @@ -33,6 +33,11 @@ public dynamic OpenConnection(string connectionString, string providerName) return OpenMethods.OpenConnectionWithProviderImpl(connectionString, providerName); } + public dynamic OpenConnection(string connectionString, string providerName, string schemaName) + { + return OpenMethods.OpenConnectionWithProviderAndSchemaImpl(connectionString, providerName, schemaName); + } + public dynamic Open(string adapterName, object settings) { return OpenMethods.OpenImpl(adapterName, settings); @@ -43,6 +48,11 @@ public dynamic OpenNamedConnection(string connectionName) return OpenMethods.OpenNamedConnectionImpl(connectionName); } + public dynamic OpenNamedConnection(string connectionName, string schemaName) + { + return OpenMethods.OpenNamedConnectionAndSchemaImpl(connectionName, schemaName); + } + public void ClearAdapterCache() { ((CachingAdapterFactory) AdapterFactory).Reset(); @@ -68,9 +78,9 @@ public static void UseMockAdapter(Func adapterCreator) OpenMethods.UseMockAdapter(adapterCreator); } - internal static Database OpenDefaultMethod() + internal static Database OpenDefaultMethod(string schemaName = null) { - return new Database(AdapterFactory.Create("Ado", new { ConnectionName = "Simple.Data.Properties.Settings.DefaultConnectionString" })); + return new Database(AdapterFactory.Create("Ado", new { ConnectionName = "Simple.Data.Properties.Settings.DefaultConnectionString", SchemaName = schemaName })); } internal static Database OpenFileMethod(string filename) @@ -88,14 +98,29 @@ internal static Database OpenConnectionMethod(string connectionString, string pr return new Database(AdapterFactory.Create("Ado", new { ConnectionString = connectionString, ProviderName = providerName })); } + internal static Database OpenConnectionAndSchemaMethod(string connectionString, string providerName, string schemaName) + { + return new Database(AdapterFactory.Create("Ado", new { ConnectionString = connectionString, ProviderName = providerName, SchemaName = schemaName })); + } + internal static Database OpenNamedConnectionMethod(string connectionName) { return new Database(AdapterFactory.Create("Ado", new { ConnectionName = connectionName })); } + internal static Database OpenNamedConnectionAndSchemaMethod(string connectionName, string schemaName) + { + return new Database(AdapterFactory.Create("Ado", new { ConnectionName = connectionName, SchemaName = schemaName })); + } + internal static Database OpenMethod(string adapterName, object settings) { return new Database(AdapterFactory.Create(adapterName, settings)); } + + public static void StopUsingMock() + { + OpenMethods.StopUsingMock(); + } } } diff --git a/Simple.Data/DatabaseOpenerMethods.cs b/Simple.Data/DatabaseOpenerMethods.cs index a61279ac..ef620c41 100644 --- a/Simple.Data/DatabaseOpenerMethods.cs +++ b/Simple.Data/DatabaseOpenerMethods.cs @@ -4,14 +4,16 @@ namespace Simple.Data internal class DatabaseOpenerMethods { - private Func _openDefault; + private Func _openDefault; private Func _openFile; private Func _openConnection; private Func _openConnectionWithProvider; + private Func _openConnectionWithProviderAndSchema; private Func _openNamedConnection; + private Func _openNamedConnectionAndSchema; private Func _open; - public Func OpenDefaultImpl + public Func OpenDefaultImpl { get { return _openDefault ?? DatabaseOpener.OpenDefaultMethod; } } @@ -31,11 +33,21 @@ public Func OpenConnectionWithProviderImpl get { return _openConnectionWithProvider ?? DatabaseOpener.OpenConnectionMethod; } } + public Func OpenConnectionWithProviderAndSchemaImpl + { + get { return _openConnectionWithProviderAndSchema ?? DatabaseOpener.OpenConnectionAndSchemaMethod; } + } + public Func OpenNamedConnectionImpl { get { return _openNamedConnection ?? DatabaseOpener.OpenNamedConnectionMethod; } } + public Func OpenNamedConnectionAndSchemaImpl + { + get { return _openNamedConnectionAndSchema ?? DatabaseOpener.OpenNamedConnectionAndSchemaMethod; } + } + public Func OpenImpl { get { return _open ?? DatabaseOpener.OpenMethod; } @@ -43,34 +55,42 @@ public Func OpenImpl public void UseMockDatabase(Database database) { - _openDefault = () => database; - _openFile = _openConnection = _openNamedConnection = ignore => database; + _openDefault = _openFile = _openConnection = _openNamedConnection = ignore => database; _open = (ignore1, ignore2) => database; - _openConnectionWithProvider = (ignore1, ignore2) => database; + _openNamedConnectionAndSchema = _openConnectionWithProvider = (ignore1, ignore2) => database; + _openConnectionWithProviderAndSchema = (ignore1, ignore2, ignore3) => database; } public void UseMockAdapter(Adapter adapter) { - _openDefault = () => new Database(adapter); - _openFile = _openConnection = _openNamedConnection = ignore => new Database(adapter); + _openDefault = _openFile = _openConnection = _openNamedConnection = ignore => new Database(adapter); _open = (ignore1, ignore2) => new Database(adapter); - _openConnectionWithProvider = (ignore1, ignore2) => new Database(adapter); + _openNamedConnectionAndSchema = _openConnectionWithProvider = (ignore1, ignore2) => new Database(adapter); + _openConnectionWithProviderAndSchema = (ignore1, ignore2, ignore3) => new Database(adapter); } public void UseMockDatabase(Func databaseCreator) { - _openDefault = databaseCreator; - _openFile = _openConnection = _openNamedConnection = ignore => databaseCreator(); + _openDefault = _openFile = _openConnection = _openNamedConnection = ignore => databaseCreator(); _open = (ignore1, ignore2) => databaseCreator(); - _openConnectionWithProvider = (ignore1, ignore2) => databaseCreator(); + _openNamedConnectionAndSchema = _openConnectionWithProvider = (ignore1, ignore2) => databaseCreator(); + _openConnectionWithProviderAndSchema = (ignore1, ignore2, ignore3) => databaseCreator(); } public void UseMockAdapter(Func adapterCreator) { - _openDefault = () => new Database(adapterCreator()); - _openFile = _openConnection = _openNamedConnection = ignore => new Database(adapterCreator()); + _openDefault = _openFile = _openConnection = _openNamedConnection = ignore => new Database(adapterCreator()); _open = (ignore1, ignore2) => new Database(adapterCreator()); - _openConnectionWithProvider = (ignore1, ignore2) => new Database(adapterCreator()); + _openNamedConnectionAndSchema = _openConnectionWithProvider = (ignore1, ignore2) => new Database(adapterCreator()); + _openConnectionWithProviderAndSchema = (ignore1, ignore2, ignore3) => new Database(adapterCreator()); + } + + public void StopUsingMock() + { + _openDefault = _openFile = _openConnection = _openNamedConnection = null; + _open = null; + _openNamedConnectionAndSchema = _openConnectionWithProvider = null; + _openConnectionWithProviderAndSchema = null; } } } \ No newline at end of file diff --git a/Simple.Data/DatabaseRunner.cs b/Simple.Data/DatabaseRunner.cs new file mode 100644 index 00000000..9c386a85 --- /dev/null +++ b/Simple.Data/DatabaseRunner.cs @@ -0,0 +1,119 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + + internal class DatabaseRunner : RunStrategy + { + private readonly Adapter _adapter; + public DatabaseRunner(Adapter adapter) + { + _adapter = adapter; + } + + protected override Adapter Adapter + { + get { return _adapter; } + } + + internal override IDictionary FindOne(string tableName, SimpleExpression criteria) + { + return _adapter.FindOne(tableName, criteria); + } + + internal override int UpdateMany(string tableName, IList> dataList) + { + return _adapter.UpdateMany(tableName, dataList); + } + + internal override int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames) + { + return _adapter.UpdateMany(tableName, dataList, criteriaFieldNames); + } + + internal override int UpdateMany(string tableName, IList> newValuesList, IList> originalValuesList) + { + int count = 0; + for (int i = 0; i < newValuesList.Count; i++) + { + count += Update(tableName, newValuesList[i], originalValuesList[i]); + } + return count; + } + + internal override int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) + { + SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); + var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); + if (changedValuesDict.Count == 0) return 0; + return _adapter.Update(tableName, changedValuesDict, criteria); + } + + /// + /// Finds data from the specified "table". + /// + /// Name of the table. + /// The criteria. This may be null, in which case all records should be returned. + /// The list of records matching the criteria. If no records are found, return an empty list. + internal override IEnumerable> Find(string tableName, SimpleExpression criteria) + { + return _adapter.Find(tableName, criteria); + } + + /// + /// Inserts a record into the specified "table". + /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. + internal override IDictionary Insert(string tableName, IDictionary data, bool resultRequired) + { + return _adapter.Insert(tableName, data, resultRequired); + } + + /// + /// Inserts a record into the specified "table". + /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. + internal override IEnumerable> InsertMany(string tableName, IEnumerable> data, ErrorCallback onError, bool resultRequired) + { + return _adapter.InsertMany(tableName, data, (dict, exception) => onError(new SimpleRecord(dict), exception), resultRequired); + } + + /// + /// Updates the specified "table" according to specified criteria. + /// Name of the table.The new values.The expression to use as criteria for the update operation.The number of records affected by the update operation. + internal override int Update(string tableName, IDictionary data, SimpleExpression criteria) + { + return _adapter.Update(tableName, data, criteria); + } + + public override IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired) + { + return _adapter.Upsert(tableName, dict, criteriaExpression, isResultRequired); + } + + public override IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback) + { + return _adapter.UpsertMany(tableName, list, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); + } + + public override IDictionary Get(string tableName, object[] args) + { + return _adapter.Get(tableName, args); + } + + public override IEnumerable> RunQuery(SimpleQuery query, out IEnumerable unhandledClauses) + { + return _adapter.RunQuery(query, out unhandledClauses); + } + + public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) + { + return _adapter.UpsertMany(tableName, list, keyFieldNames, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); + } + + /// + /// Deletes from the specified table. + /// Name of the table.The expression to use as criteria for the delete operation.The number of records which were deleted. + internal override int Delete(string tableName, SimpleExpression criteria) + { + return _adapter.Delete(tableName, criteria); + } + } +} \ No newline at end of file diff --git a/Simple.Data/DictionaryCloner.cs b/Simple.Data/DictionaryCloner.cs index 9f0b7867..1ef1c94f 100644 --- a/Simple.Data/DictionaryCloner.cs +++ b/Simple.Data/DictionaryCloner.cs @@ -38,7 +38,7 @@ private IDictionary CloneSystemDictionary(Dictionary dictionary, IDictionary clone) + private void CopyDictionaryAndCloneNestedDictionaries(IEnumerable> dictionary, IDictionary clone) { foreach (var keyValuePair in dictionary) { diff --git a/Simple.Data/DynamicSchema.cs b/Simple.Data/DynamicSchema.cs index 93baccc9..4809cb1a 100644 --- a/Simple.Data/DynamicSchema.cs +++ b/Simple.Data/DynamicSchema.cs @@ -38,7 +38,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { var adapterWithFunctions = _dataStrategy.GetAdapter() as IAdapterWithFunctions; - if (adapterWithFunctions != null && adapterWithFunctions.IsValidFunction(binder.Name)) + if (adapterWithFunctions != null && adapterWithFunctions.IsValidFunction(string.Format("{0}.{1}", _name, binder.Name))) { var command = new ExecuteFunctionCommand(_dataStrategy.GetDatabase(), adapterWithFunctions, string.Format("{0}.{1}", _name, binder.Name), binder.ArgumentsToDictionary(args)); diff --git a/Simple.Data/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 5a85fff7..4c98eb90 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -100,16 +100,18 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o return true; } - return base.TryInvokeMember(binder, args, out result); + if (base.TryInvokeMember(binder, args, out result)) return true; + + throw new InvalidOperationException(string.Format("Method {0} not recognised", binder.Name)); } private Func CreateMemberDelegate(string signature, InvokeMemberBinder binder, object[] args) { try { - var command = CommandFactory.GetCommandFor(binder.Name); - if (command == null) return null; - return command.CreateDelegate(_dataStrategy, this, binder, args); + var delegateCreatingCommand = CommandFactory.GetCommandFor(binder.Name) as ICreateDelegate; + if (delegateCreatingCommand == null) return null; + return delegateCreatingCommand.CreateDelegate(_dataStrategy, this, binder, args); } catch (NotImplementedException) { @@ -129,7 +131,8 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) { if (binder.Name == "All") { - Trace.WriteLine("The dynamic 'All' property is deprecated; use the 'All()' method instead."); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Warning, SimpleDataTraceSources.ObsoleteWarningMessageId, + "The dynamic 'All' property is deprecated; use the 'All()' method instead."); result = GetAll().ToList(); return true; } @@ -137,6 +140,15 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) return true; } + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (base.TrySetMember(binder, value)) + { + return true; + } + throw new BadExpressionException("Cannot assign values to table columns."); + } + public ObjectReference As(string alias) { return new ObjectReference(_tableName, (_schema != null ? new ObjectReference(_schema.GetName()) : null), _dataStrategy, alias); @@ -159,7 +171,24 @@ internal string GetQualifiedName() private IEnumerable GetAll() { - return _dataStrategy.Find(_tableName, null).Select(dict => new SimpleRecord(dict, _tableName, _dataStrategy)); + return _dataStrategy.Run.Find(_tableName, null).Select(dict => new SimpleRecord(dict, _tableName, _dataStrategy)); + } + + public AllColumnsSpecialReference AllColumns() + { + return new AllColumnsSpecialReference(this.ToObjectReference()); + } + + public AllColumnsSpecialReference Star() + { + return AllColumns(); + } + + internal ObjectReference ToObjectReference() + { + if (_schema == null) return new ObjectReference(_tableName, _dataStrategy); + var schemaReference = new ObjectReference(_schema.GetName(), _dataStrategy); + return new ObjectReference(_tableName, schemaReference, _dataStrategy); } } } diff --git a/Simple.Data/Extensions/HomogenizeEx.cs b/Simple.Data/Extensions/HomogenizeEx.cs index cf8571b8..3e51e59d 100644 --- a/Simple.Data/Extensions/HomogenizeEx.cs +++ b/Simple.Data/Extensions/HomogenizeEx.cs @@ -8,7 +8,7 @@ public static class HomogenizeEx { private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - private static readonly Regex HomogenizeRegex = new Regex("[^a-z0-9]"); + private static Regex _homogenizeRegex = new Regex("[^a-z0-9]"); /// /// Downshift a string and remove all non-alphanumeric characters. @@ -22,7 +22,17 @@ public static string Homogenize(this string source) private static string HomogenizeImpl(string source) { - return string.Intern(HomogenizeRegex.Replace(source.ToLowerInvariant(), string.Empty)); + return string.Intern(_homogenizeRegex.Replace(source.ToLowerInvariant(), string.Empty)); + } + + /// + /// Sets the regular expression to be used for homogenizing object names. + /// + /// A regular expression matching all non-comparing characters. The default is "[^a-z0-9]". + /// Homogenized strings are always forced to lower-case. + public static void SetRegularExpression(Regex regex) + { + _homogenizeRegex = regex; } } } \ No newline at end of file diff --git a/Simple.Data/Extensions/ObjectEx.cs b/Simple.Data/Extensions/ObjectEx.cs index 06f7a739..5ca912d1 100644 --- a/Simple.Data/Extensions/ObjectEx.cs +++ b/Simple.Data/Extensions/ObjectEx.cs @@ -14,10 +14,22 @@ public static class ObjectEx private static readonly ConcurrentDictionary>> Converters = new ConcurrentDictionary>>(); + public static IDictionary AnonymousObjectToDictionary(this object obj) + { + return obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj, null)); + } + public static IDictionary ObjectToDictionary(this object obj) { if (obj == null) return new Dictionary(); + var alreadyADictionary = obj as IDictionary; + + if (alreadyADictionary != null) + { + return new Dictionary(alreadyADictionary); + } + return Converters.GetOrAdd(obj.GetType(), MakeToDictionaryFunc)(obj); } @@ -26,7 +38,8 @@ private static Func> MakeToDictionaryFunc(Ty var param = Expression.Parameter(typeof(object)); var typed = Expression.Variable(type); var newDict = Expression.New(typeof(Dictionary)); - var listInit = Expression.ListInit(newDict, GetElementInitsForType(type, typed)); + var elementInitsForType = GetElementInitsForType(type, typed); + var listInit = Expression.ListInit(newDict, elementInitsForType); var block = Expression.Block(new[] { typed }, Expression.Assign(typed, Expression.Convert(param, type)), @@ -50,5 +63,11 @@ static ElementInit PropertyToElementInit(PropertyInfo propertyInfo, Expression i Expression.Constant(propertyInfo.Name), Expression.Convert(Expression.Property(instance, propertyInfo), typeof(object))); } + + internal static bool IsAnonymous(this object obj) + { + if (obj == null) return false; + return obj.GetType().Namespace == null; + } } } diff --git a/Simple.Data/ForUpdateClause.cs b/Simple.Data/ForUpdateClause.cs new file mode 100644 index 00000000..7ec8e855 --- /dev/null +++ b/Simple.Data/ForUpdateClause.cs @@ -0,0 +1,12 @@ +namespace Simple.Data +{ + public class ForUpdateClause : SimpleQueryClauseBase + { + public bool SkipLockedRows { get; private set; } + + public ForUpdateClause(bool skipLockedRows) + { + SkipLockedRows = skipLockedRows; + } + } +} diff --git a/Simple.Data/FunctionReference.cs b/Simple.Data/FunctionReference.cs index c66186d1..55e0481c 100644 --- a/Simple.Data/FunctionReference.cs +++ b/Simple.Data/FunctionReference.cs @@ -8,14 +8,9 @@ namespace Simple.Data public class FunctionReference : SimpleReference, IEquatable { - private static readonly HashSet KnownFunctionNames = new HashSet - { - "min", "max", "average", "length", "sum", "count", - }; - private static readonly HashSet AggregateFunctionNames = new HashSet { - "min", "max", "average", "sum", "count", + "min", "max", "average", "sum", "count", "countdistinct" }; private readonly string _name; private readonly SimpleReference _argument; @@ -68,18 +63,6 @@ public SimpleReference Argument get { return _argument; } } - public static bool TryCreate(string name, SimpleReference argument, out object functionReference) - { - if (!KnownFunctionNames.Contains(name.ToLowerInvariant())) - { - functionReference = null; - return false; - } - - functionReference = new FunctionReference(name, argument); - return true; - } - /// /// Implements the operator == to create a with the type . /// diff --git a/Simple.Data/FunctionSignature.cs b/Simple.Data/FunctionSignature.cs index 094ac246..45f0744d 100644 --- a/Simple.Data/FunctionSignature.cs +++ b/Simple.Data/FunctionSignature.cs @@ -98,7 +98,7 @@ public bool Equals(Parameter other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other._type, _type) && Equals(other._name, _name) && other._size == _size; + return other._type == _type && Equals(other._name, _name) && other._size == _size; } public override bool Equals(object obj) diff --git a/Simple.Data/IAdapterWithTransactions.cs b/Simple.Data/IAdapterWithTransactions.cs index aa0c6df1..fcf101f8 100644 --- a/Simple.Data/IAdapterWithTransactions.cs +++ b/Simple.Data/IAdapterWithTransactions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; @@ -7,8 +8,8 @@ namespace Simple.Data { public interface IAdapterWithTransactions { - IAdapterTransaction BeginTransaction(); - IAdapterTransaction BeginTransaction(string name); + IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified); + IAdapterTransaction BeginTransaction(string name, IsolationLevel isolationLevel = IsolationLevel.Unspecified); /// /// Finds data from the specified "table". @@ -66,5 +67,6 @@ public interface IAdapterWithTransactions IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, IAdapterTransaction adapterTransaction, bool isResultRequired, Func,Exception,bool> errorCallback); IEnumerable> UpsertMany(string tableName, IList> list, IAdapterTransaction adapterTransaction, bool isResultRequired, Func, Exception, bool> errorCallback); IDictionary Get(string tableName, IAdapterTransaction transaction, params object[] parameterValues); + IEnumerable> RunQuery(SimpleQuery query, IAdapterTransaction transaction, out IEnumerable unhandledClauses); } } diff --git a/Simple.Data/IDatabaseOpener.cs b/Simple.Data/IDatabaseOpener.cs index 6cfd29e0..ded38cf1 100644 --- a/Simple.Data/IDatabaseOpener.cs +++ b/Simple.Data/IDatabaseOpener.cs @@ -2,12 +2,14 @@ namespace Simple.Data { public interface IDatabaseOpener { - dynamic OpenDefault(); + dynamic OpenDefault(string schemaName = null); dynamic OpenFile(string filename); dynamic OpenConnection(string connectionString); dynamic OpenConnection(string connectionString, string providerName); dynamic Open(string adapterName, object settings); dynamic OpenNamedConnection(string connectionName); + dynamic OpenNamedConnection(string connectionName, string schemaName); void ClearAdapterCache(); + dynamic OpenConnection(string connectionString, string providerName, string schemaName); } } \ No newline at end of file diff --git a/Simple.Data/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 7ca7b541..0a82b0a3 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -1,4 +1,4 @@ -namespace Simple.Data +namespace Simple.Data { using System; using System.Collections.Generic; @@ -6,15 +6,47 @@ using System.Linq; using QueryPolyfills; - public partial class InMemoryAdapter : Adapter + public partial class InMemoryAdapter : Adapter, IAdapterWithFunctions { - private readonly Dictionary _autoIncrementColumns = new Dictionary(); - private readonly Dictionary _keyColumns = new Dictionary(); + private readonly Dictionary _autoIncrementColumns; + private readonly Dictionary _keyColumns; - private readonly Dictionary>> _tables = - new Dictionary>>(); + private readonly Dictionary>> _tables; - private readonly ICollection _joins = new Collection(); + [Flags] + private enum FunctionFlags + { + None = 0x00000000, + PassThru = 0x00000001 + } + + private class FunctionInfo + { + public FunctionInfo(Delegate func, FunctionFlags flags) + { + Delegate = func; + Flags = flags; + } + + public Delegate Delegate { get; private set; } + public FunctionFlags Flags { get; private set; } + } + + private readonly Dictionary _functions = new Dictionary(); + + private readonly ICollection _joins = new Collection(); + + public InMemoryAdapter() : this(StringComparer.OrdinalIgnoreCase) + { + } + + public InMemoryAdapter(IEqualityComparer nameComparer) + { + _nameComparer = nameComparer; + _keyColumns = new Dictionary(_nameComparer); + _autoIncrementColumns = new Dictionary(_nameComparer); + _tables = new Dictionary>>(nameComparer); + } private List> GetTable(string tableName) { @@ -51,7 +83,7 @@ public override IDictionary Get(string tableName, params object[ public override IEnumerable> Find(string tableName, SimpleExpression criteria) { - var whereClauseHandler = new WhereClauseHandler(new WhereClause(criteria)); + var whereClauseHandler = new WhereClauseHandler(tableName, new WhereClause(criteria)); return whereClauseHandler.Run(GetTable(tableName)); } @@ -65,6 +97,7 @@ out IEnumerable public override IDictionary Insert(string tableName, IDictionary data, bool resultRequired) { + data = new Dictionary(data, _nameComparer); if (_autoIncrementColumns.ContainsKey(tableName)) { var table = GetTable(tableName); @@ -78,7 +111,7 @@ public override IDictionary Insert(string tableName, IDictionary object nextVal = 0; if(table.Count > 0) { - nextVal = table.Select(d => d[autoIncrementColumn]).Max(); ; + nextVal = table.Select(d => d[autoIncrementColumn]).Max(); } nextVal = ObjectMaths.Increment(nextVal); @@ -150,7 +183,7 @@ public override int Update(string tableName, IDictionary data, S return count; } - private static void UpdateRecord(IDictionary data, IDictionary record) + private static void UpdateRecord(IEnumerable> data, IDictionary record) { foreach (var kvp in data) { @@ -208,12 +241,19 @@ public void SetKeyColumns(string tableName, params string[] columnNames) /// The name to give the collection property in the master object public void ConfigureJoin(string masterTableName, string masterKey, string masterPropertyName, string detailTableName, string detailKey, string detailPropertyName) { - var join = new Join(masterTableName, masterKey, masterPropertyName, detailTableName, detailKey, + var join = new JoinInfo(masterTableName, masterKey, masterPropertyName, detailTableName, detailKey, detailPropertyName); _joins.Add(join); } - public class Join + public JoinConfig Join + { + get { return new JoinConfig(_joins);} + } + + private IEqualityComparer _nameComparer = EqualityComparer.Default; + + internal class JoinInfo { private readonly string _masterTableName; private readonly string _masterKey; @@ -222,7 +262,7 @@ public class Join private readonly string _detailKey; private readonly string _detailPropertyName; - public Join(string masterTableName, string masterKey, string masterPropertyName, string detailTableName, string detailKey, string detailPropertyName) + public JoinInfo(string masterTableName, string masterKey, string masterPropertyName, string detailTableName, string detailKey, string detailPropertyName) { _masterTableName = masterTableName; _masterKey = masterKey; @@ -263,17 +303,17 @@ public string MasterTableName } } - public IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired, IAdapterTransaction adapterTransaction) + public override IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired, IAdapterTransaction adapterTransaction) { return Upsert(tableName, dict, criteriaExpression, isResultRequired); } - public IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, IAdapterTransaction adapterTransaction, bool isResultRequired, Func,Exception,bool> errorCallback) + public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, IAdapterTransaction adapterTransaction, bool isResultRequired, Func,Exception,bool> errorCallback) { return UpsertMany(tableName, list, keyFieldNames, isResultRequired, errorCallback); } - public IEnumerable> UpsertMany(string tableName, IList> list, IAdapterTransaction adapterTransaction, bool isResultRequired, Func, Exception, bool> errorCallback) + public override IEnumerable> UpsertMany(string tableName, IList> list, IAdapterTransaction adapterTransaction, bool isResultRequired, Func, Exception, bool> errorCallback) { return UpsertMany(tableName, list, isResultRequired, errorCallback); } @@ -282,5 +322,98 @@ public IDictionary Get(string tableName, IAdapterTransaction tra { return Get(tableName, parameterValues); } + + public class JoinConfig + { + private readonly ICollection _joins; + private JoinInfo _joinInfo; + + internal JoinConfig(ICollection joins) + { + _joins = joins; + _joinInfo = new JoinInfo(null,null,null,null,null,null); + } + + public JoinConfig Master(string tableName, string keyName, string propertyNameInDetailRecords = null) + { + if (_joins.Contains(_joinInfo)) _joins.Remove(_joinInfo); + _joinInfo = new JoinInfo(tableName, keyName, propertyNameInDetailRecords ?? tableName, _joinInfo.DetailTableName, + _joinInfo.DetailKey, _joinInfo.DetailPropertyName); + _joins.Add(_joinInfo); + return this; + } + + + public JoinConfig Detail(string tableName, string keyName, string propertyNameInMasterRecords = null) + { + if (_joins.Contains(_joinInfo)) _joins.Remove(_joinInfo); + _joinInfo = new JoinInfo(_joinInfo.MasterTableName, _joinInfo.MasterKey, _joinInfo.MasterPropertyName, + tableName, keyName, + propertyNameInMasterRecords ?? tableName); + _joins.Add(_joinInfo); + return this; + } + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddDelegate(string functionName, Delegate function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); + } + + public void AddFunction(string functionName, Func, TResult> function) + { + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.PassThru)); + } + + public bool IsValidFunction(string functionName) + { + return _functions.ContainsKey(functionName); + } + + public IEnumerable>>> Execute(string functionName, IDictionary parameters) + { + if (!_functions.ContainsKey(functionName)) throw new InvalidOperationException(string.Format("Function '{0}' not found.", functionName)); + var obj = ((_functions[functionName].Flags & FunctionFlags.PassThru) == FunctionFlags.PassThru) ? + _functions[functionName].Delegate.DynamicInvoke(parameters) : + _functions[functionName].Delegate.DynamicInvoke(parameters.Values.ToArray()); + + var dict = obj as IDictionary; + if (dict != null) return new List>> { new List> { dict } }; + + var list = obj as IEnumerable>; + if (list != null) return new List>> { list }; + + return obj as IEnumerable>>; + } + + public IEnumerable>>> Execute(string functionName, IDictionary parameters, IAdapterTransaction transaction) + { + return Execute(functionName, parameters); + } } } \ No newline at end of file diff --git a/Simple.Data/InMemoryAdapterIAdapterWithRelation.cs b/Simple.Data/InMemoryAdapterIAdapterWithRelation.cs new file mode 100644 index 00000000..dfd518a3 --- /dev/null +++ b/Simple.Data/InMemoryAdapterIAdapterWithRelation.cs @@ -0,0 +1,54 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + using System.Linq; + + public partial class InMemoryAdapter : IAdapterWithRelation + { + public bool IsValidRelation(string tableName, string relatedTableName) + { + return _joins.Any( + ji => + _nameComparer.Equals(tableName, ji.MasterTableName) && + _nameComparer.Equals(relatedTableName, ji.MasterPropertyName)) + || + _joins.Any( + ji => + _nameComparer.Equals(tableName, ji.DetailTableName) && + _nameComparer.Equals(relatedTableName, ji.DetailPropertyName)); + } + + public object FindRelated(string tableName, IDictionary row, string relatedTableName) + { + return FindMaster(tableName, row, relatedTableName) ?? FindDetail(tableName, row, relatedTableName); + } + + private object FindMaster(string tableName, IDictionary row, string relatedTableName) + { + var master = _joins.FirstOrDefault(ji => + _nameComparer.Equals(tableName, ji.MasterTableName) && + _nameComparer.Equals(relatedTableName, ji.MasterPropertyName)); + if (master != null) + { + object result; + row.TryGetValue(master.DetailPropertyName, out result); + return result; + } + return null; + } + + private object FindDetail(string tableName, IDictionary row, string relatedTableName) + { + var detail = _joins.FirstOrDefault(ji => + _nameComparer.Equals(tableName, ji.DetailTableName) && + _nameComparer.Equals(relatedTableName, ji.DetailPropertyName)); + if (detail != null) + { + object result; + row.TryGetValue(detail.MasterPropertyName, out result); + return result; + } + return null; + } + } +} \ No newline at end of file diff --git a/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs b/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs index eba83699..888a1c94 100644 --- a/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs +++ b/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; @@ -38,12 +39,12 @@ public string Name } } - public IAdapterTransaction BeginTransaction() + public IAdapterTransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) { return new InMemoryAdapterTransaction(); } - public IAdapterTransaction BeginTransaction(string name) + public IAdapterTransaction BeginTransaction(string name, IsolationLevel isolationLevel = IsolationLevel.Unspecified) { return new InMemoryAdapterTransaction(name); } @@ -87,5 +88,10 @@ public int UpdateMany(string tableName, IList> dataL { return UpdateMany(tableName, dataList, criteriaFieldNames); } + + public IEnumerable> RunQuery(SimpleQuery query, IAdapterTransaction transaction, out IEnumerable unhandledClauses) + { + return RunQuery(query, out unhandledClauses); + } } } diff --git a/Simple.Data/MathReference.cs b/Simple.Data/MathReference.cs index f357289b..c5fbf63b 100644 --- a/Simple.Data/MathReference.cs +++ b/Simple.Data/MathReference.cs @@ -18,6 +18,13 @@ public MathReference(object leftOperand, object rightOperand, MathOperator @oper _operator = @operator; } + private MathReference(object leftOperand, object rightOperand, MathOperator @operator, string alias) : base(alias) + { + _leftOperand = leftOperand; + _rightOperand = rightOperand; + _operator = @operator; + } + public MathOperator Operator { get { return _operator; } @@ -33,6 +40,11 @@ public object LeftOperand get { return _leftOperand; } } + public MathReference As(string alias) + { + return new MathReference(_leftOperand, _rightOperand, _operator, alias); + } + /// /// Implements the operator == to create a with the type . /// @@ -124,6 +136,31 @@ public override int GetHashCode() return result; } } + + public static MathReference operator +(MathReference column, object value) + { + return new MathReference(column, value, MathOperator.Add); + } + + public static MathReference operator -(MathReference column, object value) + { + return new MathReference(column, value, MathOperator.Subtract); + } + + public static MathReference operator *(MathReference column, object value) + { + return new MathReference(column, value, MathOperator.Multiply); + } + + public static MathReference operator /(MathReference column, object value) + { + return new MathReference(column, value, MathOperator.Divide); + } + + public static MathReference operator %(MathReference column, object value) + { + return new MathReference(column, value, MathOperator.Modulo); + } } public enum MathOperator diff --git a/Simple.Data/MefHelper.cs b/Simple.Data/MefHelper.cs index 7b7e867a..95cabaaa 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -14,7 +14,15 @@ class MefHelper : Composer { public override T Compose() { - using (var container = CreateContainer()) + using (var container = CreateAppDomainContainer()) + { + var exports = container.GetExports().ToList(); + if (exports.Count == 1) + { + return exports.Single().Value; + } + } + using (var container = CreateFolderContainer()) { var exports = container.GetExports().ToList(); if (exports.Count == 0) throw new SimpleDataException("No ADO Provider found."); @@ -27,17 +35,26 @@ public override T Compose(string contractName) { try { - using (var container = CreateContainer()) + using (var container = CreateAppDomainContainer()) { var exports = container.GetExports(contractName).ToList(); - if (exports.Count == 0) throw new SimpleDataException("No ADO Provider found."); + if (exports.Count == 1) + { + return exports.Single().Value; + } + } + using (var container = CreateFolderContainer()) + { + var exports = container.GetExports(contractName).ToList(); + if (exports.Count == 0) throw new SimpleDataException(string.Format("No {0} Provider found.", contractName)); if (exports.Count > 1) throw new SimpleDataException("Multiple ADO Providers found; specify provider name or remove unwanted assemblies."); return exports.Single().Value; } } catch (ReflectionTypeLoadException ex) { - Trace.WriteLine(ex.Message); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Error, SimpleDataTraceSources.GenericErrorMessageId, + "Compose failed: {0}", ex.Message); throw; } } @@ -53,30 +70,40 @@ public static T GetAdjacentComponent(Type knownSiblingType) } } - static string GetThisAssemblyPath() + private static CompositionContainer CreateFolderContainer() { - var path = Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", "").Replace("file://", "//"); - path = Path.GetDirectoryName(path); - if (path == null) throw new ArgumentException("Unrecognised file."); - if (!Path.IsPathRooted(path)) + var path = GetSimpleDataAssemblyPath (); + + var assemblyCatalog = new AssemblyCatalog(ThisAssembly); + var aggregateCatalog = new AggregateCatalog(assemblyCatalog); + foreach (string file in System.IO.Directory.GetFiles(path, "Simple.Data.*.dll")) { - path = Path.DirectorySeparatorChar + path; + var catalog = new AssemblyCatalog(file); + aggregateCatalog.Catalogs.Add(catalog); } - return path; + return new CompositionContainer(aggregateCatalog); } - private static CompositionContainer CreateContainer() + private static CompositionContainer CreateAppDomainContainer() { - var path = GetThisAssemblyPath (); - - var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); - var aggregateCatalog = new AggregateCatalog(assemblyCatalog); - foreach (string file in System.IO.Directory.GetFiles(path, "Simple.Data.*.dll")) - { - var catalog = new AssemblyCatalog(file); - aggregateCatalog.Catalogs.Add(catalog); - } + var aggregateCatalog = new AggregateCatalog(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(IsSimpleDataAssembly)) + { + aggregateCatalog.Catalogs.Add(new AssemblyCatalog(assembly)); + } return new CompositionContainer(aggregateCatalog); } + + private static bool IsSimpleDataAssembly(Assembly assembly) + { + try + { + return assembly.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); + } + catch + { + return false; + } + } } } diff --git a/Simple.Data/MethodNameParser.cs b/Simple.Data/MethodNameParser.cs index 69c3b166..7968bd4b 100644 --- a/Simple.Data/MethodNameParser.cs +++ b/Simple.Data/MethodNameParser.cs @@ -65,7 +65,7 @@ internal static string RemoveCommandPart(string methodName) { if (methodName == null) throw new ArgumentNullException("methodName"); if (!methodName.Contains("By")) return methodName; - return methodName.Substring(methodName.IndexOf("By") + 2); + return methodName.Substring(methodName.IndexOf("By", StringComparison.Ordinal) + 2); } internal static IList GetColumns(string methodName) diff --git a/Simple.Data/NugetPack.cmd b/Simple.Data/NugetPack.cmd index 32e11a37..ace20649 100644 --- a/Simple.Data/NugetPack.cmd +++ b/Simple.Data/NugetPack.cmd @@ -1 +1 @@ -nuget pack -sym Simple.Data.csproj -Properties Configuration=Release +nuget pack -sym Simple.Data.csproj -Properties Configuration=Release;Platform=AnyCPU -Build diff --git a/Simple.Data/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 5dd5cada..22fcaaee 100644 --- a/Simple.Data/ObjectReference.cs +++ b/Simple.Data/ObjectReference.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Dynamic; using System.Linq; +using System.Reflection; using System.Text; using Simple.Data.Commands; @@ -92,31 +93,61 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re { if (_dataStrategy != null) { + if (TryInvokeDataStrategyMethod(args, out result)) return true; + if (_dataStrategy.TryInvokeFunction(_name, () => binder.ArgumentsToDictionary(args), out result)) return true; } throw new InvalidOperationException(); } + private bool TryInvokeDataStrategyMethod(object[] args, out object result) + { + var methodInfo = _dataStrategy.GetType().GetMethod(_name, args.Select(a => (a ?? new object()).GetType()).ToArray()); + if (methodInfo != null) + { + result = methodInfo.Invoke(_dataStrategy, args); + return true; + } + result = null; + return false; + } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (base.TryInvokeMember(binder, args, out result)) return true; if (_dataStrategy != null) { - // Probably a table... var table = new DynamicTable(_name, _dataStrategy); - if (table.TryInvokeMember(binder, args, out result)) + try { - _dataStrategy.SetMemberAsTable(this, table); - return true; + if (table.TryInvokeMember(binder, args, out result)) + { + _dataStrategy.SetMemberAsTable(this, table); + return true; + } + } + catch (InvalidOperationException ex) + { + if (!ex.Message.StartsWith("Method")) + { + throw; + } } // Or it could be a schema reference... var schema = new DynamicSchema(_name, _dataStrategy); - if (schema.TryInvokeMember(binder, args, out result)) + try { - _dataStrategy.SetMemberAsSchema(this); - return true; + if (schema.TryInvokeMember(binder, args, out result)) + { + _dataStrategy.SetMemberAsSchema(this); + return true; + } + } + catch (KeyNotFoundException) + { + throw new InvalidOperationException(string.Format("Method {0} not recognised", binder.Name)); } } @@ -126,9 +157,17 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o var command = CommandFactory.GetCommandFor(binder.Name); if (command != null) { - var schema = dataStrategy.SetMemberAsSchema(_owner); - var table = schema.GetTable(_name); - result = command.Execute(dataStrategy, table, binder, args); + if (!ReferenceEquals(_owner, null)) + { + var schema = dataStrategy.SetMemberAsSchema(_owner); + var table = schema.GetTable(_name); + table.TryInvokeMember(binder, args, out result); + } + else + { + throw new InvalidOperationException(string.Format("Method {0} not recognised", binder.Name)); + } + //result = command.Execute(dataStrategy, table, binder, args); } else { @@ -143,7 +182,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } return true; } - throw new InvalidOperationException(); + throw new InvalidOperationException(string.Format("Method {0} not recognised", binder.Name)); } /// @@ -160,6 +199,15 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) return true; } + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (base.TrySetMember(binder, value)) + { + return true; + } + throw new BadExpressionException("Cannot assign values to object references."); + } + public dynamic this[string name] { get { return new ObjectReference(name, this); } diff --git a/Simple.Data/OptionsBase.cs b/Simple.Data/OptionsBase.cs new file mode 100644 index 00000000..b4b74e91 --- /dev/null +++ b/Simple.Data/OptionsBase.cs @@ -0,0 +1,7 @@ +namespace Simple.Data +{ + public class OptionsBase + { + + } +} \ No newline at end of file diff --git a/Simple.Data/Properties/AssemblyInfo.cs b/Simple.Data/Properties/AssemblyInfo.cs index 8812e9c0..3c428d2c 100644 --- a/Simple.Data/Properties/AssemblyInfo.cs +++ b/Simple.Data/Properties/AssemblyInfo.cs @@ -22,4 +22,5 @@ [assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)] [assembly: AllowPartiallyTrustedCallers] -[assembly: InternalsVisibleTo("Simple.Data.InMemory")] \ No newline at end of file +[assembly: InternalsVisibleTo("Simple.Data.InMemory")] +[assembly: InternalsVisibleTo("Simple.Data.Ado")] \ No newline at end of file diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 68d0229a..1156ef6f 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -1,6 +1,9 @@ +using System.ComponentModel; + namespace Simple.Data { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -11,13 +14,21 @@ class PropertySetterBuilder private static readonly MethodInfo DictionaryContainsKeyMethod = typeof(IDictionary).GetMethod("ContainsKey", new[] { typeof(string) }); private static readonly PropertyInfo DictionaryIndexerProperty = typeof(IDictionary).GetProperty("Item"); - private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray", + private static readonly MethodInfo ToArrayDictionaryMethod = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(typeof(IDictionary)); - private static readonly PropertyInfo ArrayLengthProperty = + private static readonly MethodInfo ToArrayObjectMethod = typeof(Enumerable).GetMethod("ToArray", + BindingFlags.Public | + BindingFlags.Static).MakeGenericMethod(typeof(object)); + + + private static readonly PropertyInfo ArrayDictionaryLengthProperty = typeof(IDictionary[]).GetProperty("Length"); + private static readonly PropertyInfo ArrayObjectLengthProperty = + typeof(object[]).GetProperty("Length"); + private readonly ParameterExpression _param; private readonly ParameterExpression _obj; private readonly PropertyInfo _property; @@ -42,6 +53,11 @@ public ConditionalExpression CreatePropertySetter() return Expression.IfThen(_containsKey, CreateTrySimpleAssign()); } + if (_property.PropertyType.IsArray) + { + return Expression.IfThen(_containsKey, CreateTrySimpleArrayAssign()); + } + if (_property.PropertyType.IsGenericCollection()) { var collectionCreator = BuildCollectionCreator(); @@ -62,6 +78,25 @@ public ConditionalExpression CreatePropertySetter() return ifThen; } + private Expression BuildArrayCreator() + { + if (!_property.CanWrite) return null; + + var genericType = _property.PropertyType.GetGenericArguments().Single(); + var creatorInstance = ConcreteTypeCreator.Get(genericType); + var collection = Expression.Variable(_property.PropertyType); + + var createCollection = MakeCreateNewCollection(collection, genericType); + + if (createCollection == null) return null; + + var addMethod = _property.PropertyType.GetMethod("Add"); + + if (addMethod == null) return null; + + return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod); + } + private Expression BuildCollectionCreator() { var genericType = _property.PropertyType.GetGenericArguments().Single(); @@ -70,71 +105,154 @@ private Expression BuildCollectionCreator() BinaryExpression createCollection = null; if (_property.CanWrite) { - if (_property.PropertyType.IsInterface) - { - createCollection = Expression.Assign(collection, - Expression.Call( - typeof (PropertySetterBuilder).GetMethod("CreateList", - BindingFlags. - NonPublic | - BindingFlags. - Static). - MakeGenericMethod(genericType))); - } - else - { - var defaultConstructor = _property.PropertyType.GetConstructor(Type.EmptyTypes); - if (defaultConstructor != null) - { - createCollection = Expression.Assign(collection, Expression.New(defaultConstructor)); - } - } + createCollection = MakeCreateNewCollection(collection, genericType); } else { createCollection = Expression.Assign(collection, _nameProperty); } - var addMethod = _property.PropertyType.GetMethod("Add"); + + var addMethod = _property.PropertyType.GetInterfaceMethod("Add"); if (createCollection != null && addMethod != null) { - var creator = Expression.Constant(creatorInstance); - var array = Expression.Variable(typeof(IDictionary[])); - var i = Expression.Variable(typeof(int)); - var current = Expression.Variable(typeof(IDictionary)); - - - var isDictionaryCollection = Expression.TypeIs(_itemProperty, - typeof(IEnumerable>)); - - var toArray = Expression.Assign(array, Expression.Call(ToArrayMethod, Expression.Convert(_itemProperty, typeof(IEnumerable>)))); - var start = Expression.Assign(i, Expression.Constant(0)); - var label = Expression.Label(); - var loop = Expression.Loop( - Expression.IfThenElse( - Expression.LessThan(i, Expression.Property(array, ArrayLengthProperty)), - Expression.Block( - Expression.Assign(current, Expression.ArrayIndex(array, i)), + return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod); + } + return null; + } + + private Expression BuildCollectionCreatorExpression(Type genericType, ConcreteTypeCreator creatorInstance, ParameterExpression collection, BinaryExpression createCollection, MethodInfo addMethod) + { + BlockExpression dictionaryBlock; + var isDictionaryCollection = BuildComplexTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out dictionaryBlock); + + BlockExpression objectBlock; + var isObjectcollection = BuildSimpleTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out objectBlock); + + return Expression.IfThenElse(isDictionaryCollection, dictionaryBlock, + Expression.IfThen(isObjectcollection, objectBlock)); + } + + private TypeBinaryExpression BuildComplexTypeCollectionPopulator(ParameterExpression collection, Type genericType, + MethodInfo addMethod, BinaryExpression createCollection, + ConcreteTypeCreator creatorInstance, out BlockExpression block) + { + var creator = Expression.Constant(creatorInstance); + var array = Expression.Variable(typeof (IDictionary[])); + var i = Expression.Variable(typeof (int)); + var current = Expression.Variable(typeof (IDictionary)); + + var isDictionaryCollection = Expression.TypeIs(_itemProperty, + typeof (IEnumerable>)); + + var toArray = Expression.Assign(array, + Expression.Call(ToArrayDictionaryMethod, + Expression.Convert(_itemProperty, + typeof (IEnumerable>)))); + var start = Expression.Assign(i, Expression.Constant(0)); + var label = Expression.Label(); + var loop = Expression.Loop( + Expression.IfThenElse( + Expression.LessThan(i, Expression.Property(array, ArrayDictionaryLengthProperty)), + Expression.Block( + Expression.Assign(current, Expression.ArrayIndex(array, i)), + Expression.Call(collection, addMethod, + Expression.Convert(Expression.Call(creator, CreatorCreateMethod, current), genericType)), + Expression.PreIncrementAssign(i) + ), + Expression.Break(label) + ), + label + ); + + block = Expression.Block( + new[] {array, i, collection, current}, + createCollection, + toArray, + start, + loop, + _property.CanWrite ? (Expression) Expression.Assign(_nameProperty, collection) : Expression.Empty()); + + return isDictionaryCollection; + } + + private TypeBinaryExpression BuildSimpleTypeCollectionPopulator(ParameterExpression collection, Type genericType, + MethodInfo addMethod, BinaryExpression createCollection, + ConcreteTypeCreator creatorInstance, out BlockExpression block) + { + var creator = Expression.Constant(creatorInstance); + var array = Expression.Variable(typeof(object[])); + var i = Expression.Variable(typeof(int)); + var current = Expression.Variable(typeof(object)); + + var isObjectCollection = Expression.TypeIs(_itemProperty, + typeof(IEnumerable)); + + var toArray = Expression.Assign(array, + Expression.Call(ToArrayObjectMethod, + Expression.Convert(_itemProperty, + typeof(IEnumerable)))); + var start = Expression.Assign(i, Expression.Constant(0)); + var label = Expression.Label(); + var loop = Expression.Loop( + Expression.IfThenElse( + Expression.LessThan(i, Expression.Property(array, ArrayObjectLengthProperty)), + Expression.Block( + Expression.Assign(current, Expression.ArrayIndex(array, i)), + Expression.IfThenElse( + Expression.TypeIs(current, typeof(IDictionary)), + Expression.Call(collection, addMethod, + Expression.Convert(Expression.Call(creator, CreatorCreateMethod, + Expression.Convert(current, typeof(IDictionary))), + genericType)), Expression.Call(collection, addMethod, - Expression.Convert(Expression.Call(creator, CreatorCreateMethod, current), genericType)), - Expression.PreIncrementAssign(i) - ), - Expression.Break(label) + Expression.Convert(current, genericType))), + Expression.PreIncrementAssign(i) ), - label - ); - - var block = Expression.Block( - new[] { array, i, collection, current }, - createCollection, - toArray, - start, - loop, - _property.CanWrite ? (Expression)Expression.Assign(_nameProperty, collection) : Expression.Empty()); - - return Expression.IfThen(isDictionaryCollection, block); + Expression.Break(label) + ), + label + ); + + block = Expression.Block( + new[] { array, i, collection, current }, + createCollection, + toArray, + start, + loop, + _property.CanWrite ? (Expression)Expression.Assign(_nameProperty, collection) : Expression.Empty()); + + return isObjectCollection; + } + + private BinaryExpression MakeCreateNewCollection(ParameterExpression collection, Type genericType) + { + BinaryExpression createCollection; + + if (_property.PropertyType.IsInterface) + { + createCollection = Expression.Assign(collection, + Expression.Call( + typeof (PropertySetterBuilder).GetMethod("CreateList", + BindingFlags. + NonPublic | + BindingFlags. + Static). + MakeGenericMethod(genericType))); } - return null; + else + { + var defaultConstructor = _property.PropertyType.GetConstructor(Type.EmptyTypes); + if (defaultConstructor != null) + { + createCollection = Expression.Assign(collection, Expression.New(defaultConstructor)); + } + else + { + createCollection = null; + } + } + return createCollection; } private bool PropertyIsPrimitive() @@ -163,8 +281,10 @@ private BinaryExpression CreateComplexAssign() { var creator = Expression.Constant(ConcreteTypeCreator.Get(_property.PropertyType)); var methodCallExpression = Expression.Call(creator, CreatorCreateMethod, +// ReSharper disable PossiblyMistakenUseOfParamsMethod Expression.Convert(_itemProperty, typeof(IDictionary))); +// ReSharper restore PossiblyMistakenUseOfParamsMethod var complexAssign = Expression.Assign(_nameProperty, Expression.Convert( @@ -174,35 +294,38 @@ private BinaryExpression CreateComplexAssign() private TryExpression CreateTrySimpleAssign() { - Expression assign; - var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert", - BindingFlags.Static | BindingFlags.NonPublic); - MethodCallExpression callConvert; if (_property.PropertyType.IsEnum) { + var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert", + BindingFlags.Static | BindingFlags.NonPublic); callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType.GetEnumUnderlyingType())); + Expression.Constant(_property.PropertyType.GetEnumUnderlyingType(), typeof(Type))); } else if (_property.PropertyType.IsGenericType && _property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType.GetGenericArguments().Single())); + var changeTypeMethod = typeof (PropertySetterBuilder) + .GetMethod("SafeConvertNullable", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(_property.PropertyType.GetGenericArguments().Single()); + + callConvert = Expression.Call(changeTypeMethod, _itemProperty); } else { + var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert", + BindingFlags.Static | BindingFlags.NonPublic); callConvert = Expression.Call(changeTypeMethod, _itemProperty, - Expression.Constant(_property.PropertyType)); + Expression.Constant(_property.PropertyType, typeof(Type))); } - assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType)); + var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType)); if (_property.PropertyType.IsEnum) { return Expression.TryCatch( // try { Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof (string)), Expression.Assign(_nameProperty, Expression.Convert(Expression.Call(typeof (Enum).GetMethod("Parse", new[] {typeof(Type), typeof(string), typeof(bool)}), - Expression.Constant(_property.PropertyType), + Expression.Constant(_property.PropertyType, typeof(Type)), Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), Expression.Constant(true)), _property.PropertyType)), assign), Expression.Catch(typeof(Exception), Expression.Empty())); } @@ -211,14 +334,62 @@ private TryExpression CreateTrySimpleAssign() CreateCatchBlock()); } - private static object SafeConvert(object source, Type targetType) + private TryExpression CreateTrySimpleArrayAssign() + { + var createArrayMethod = typeof (PropertySetterBuilder).GetMethod("CreateArray", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(_property.PropertyType.GetElementType()); + + var callConvert = Expression.Call(createArrayMethod, _itemProperty); + + var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType)); + return Expression.TryCatch( // try { + Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof (string)), + Expression.Assign(_nameProperty, + Expression.Convert(Expression.Call(typeof (Enum).GetMethod("Parse", new[] {typeof(Type), typeof(string), typeof(bool)}), + Expression.Constant(_property.PropertyType, typeof(Type)), + Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), Expression.Constant(true)), _property.PropertyType)), + assign), Expression.Catch(typeof(Exception), Expression.Empty())); + } + + + +// ReSharper disable UnusedMember.Local +// Because they're used from runtime-generated code, you see. + internal static object SafeConvert(object source, Type targetType) { - return ReferenceEquals(source, null) ? null : Convert.ChangeType(source, targetType); + if (ReferenceEquals(source, null)) return null; + if (targetType.IsInstanceOfType(source)) return source; + + if (source is string && targetType == typeof(Guid)) return TypeDescriptor.GetConverter(typeof(Guid)).ConvertFromInvariantString(source.ToString()); + return Convert.ChangeType(source, targetType); + } + + internal static T? SafeConvertNullable(object source) + where T : struct + { + if (ReferenceEquals(source, null)) return default(T?); + return (T) source; + } + + private static T[] CreateArray(object source) + { + if (ReferenceEquals(source, null)) return null; + var enumerable = source as IEnumerable; + if (ReferenceEquals(enumerable, null)) return null; + try + { + return enumerable.Cast().ToArray(); + } + catch (InvalidCastException) + { + return null; + } } private static List CreateList() { return new List(); } +// ReSharper restore UnusedMember.Local } } \ No newline at end of file diff --git a/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs b/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs index 033e946c..ff33250d 100644 --- a/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs +++ b/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs @@ -1,4 +1,4 @@ -namespace Simple.Data.QueryPolyfills +namespace Simple.Data.QueryPolyfills { using System; using System.Collections.Generic; @@ -13,24 +13,25 @@ private static readonly { { typeof(DistinctClause), (c,d) => d.Distinct(new DictionaryEqualityComparer()) }, { typeof(SkipClause), (c,d) => d.Skip(((SkipClause)c).Count) }, - { typeof(TakeClause), (c,d) => d.Take(((TakeClause)c).Count) }, - { typeof(OrderByClause), (c, d) => new OrderByClauseHandler((OrderByClause)c).Run(d) } + { typeof(TakeClause), (c,d) => d.Take(((TakeClause)c).Count) } }; + private readonly string _mainTableName; private readonly IEnumerable> _source; private readonly IList _clauses; private readonly WithCountClause _withCountClause; - public DictionaryQueryRunner(IEnumerable> source, IEnumerable clauses) + public DictionaryQueryRunner(string mainTableName, IEnumerable> source, IEnumerable clauses) { + _mainTableName = mainTableName; _source = source; _clauses = clauses.ToList(); _withCountClause = _clauses.OfType().FirstOrDefault(); if (_withCountClause != null) _clauses.Remove(_withCountClause); } - public DictionaryQueryRunner(IEnumerable> source, params SimpleQueryClauseBase[] clauses) - : this(source, clauses.AsEnumerable()) + public DictionaryQueryRunner(string mainTableName, IEnumerable> source, params SimpleQueryClauseBase[] clauses) + : this(mainTableName, source, clauses.AsEnumerable()) { } @@ -45,6 +46,8 @@ public IEnumerable> Run() source = list; } + source = RunOrderByClauses(source); + foreach (var clause in _clauses) { Func>, IEnumerable>> handler; @@ -53,17 +56,27 @@ public IEnumerable> Run() source = handler(clause, source); } } - + source = RunHavingClauses(source); source = RunSelectClauses(source); return source; } + private IEnumerable> RunOrderByClauses(IEnumerable> source) + { + var orderByClauses = _clauses.OfType().Reverse(); + foreach (var orderByClause in orderByClauses) + { + source = new OrderByClauseHandler(orderByClause).Run(source); + } + return source; + } + private IEnumerable> RunWhereClauses(IEnumerable> source) { foreach (var whereClause in _clauses.OfType()) { - source = new WhereClauseHandler(whereClause).Run(source); + source = new WhereClauseHandler(_mainTableName, whereClause).Run(source); } return source; } @@ -99,7 +112,7 @@ private IEnumerable> RunHavingClauses(IEnumerable d.Where(kvp => !kvp.Key.StartsWith(AutoColumnPrefix)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); } diff --git a/Simple.Data/QueryPolyfills/HavingClauseHandler.cs b/Simple.Data/QueryPolyfills/HavingClauseHandler.cs deleted file mode 100644 index 049e71ef..00000000 --- a/Simple.Data/QueryPolyfills/HavingClauseHandler.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Simple.Data.QueryPolyfills -{ - internal class HavingClauseHandler - { - private readonly Dictionary, bool>>> _expressionFormatters; - - private readonly WhereClause _whereClause; - - public HavingClauseHandler(WhereClause whereClause) - { - _whereClause = whereClause; - _expressionFormatters = new Dictionary, bool>>> - { - {SimpleExpressionType.And, LogicalExpressionToWhereClause}, - {SimpleExpressionType.Or, LogicalExpressionToWhereClause}, - {SimpleExpressionType.Equal, EqualExpressionToWhereClause}, - {SimpleExpressionType.NotEqual, NotEqualExpressionToWhereClause}, - {SimpleExpressionType.Function, FunctionExpressionToWhereClause}, - {SimpleExpressionType.GreaterThan, GreaterThanToWhereClause}, - {SimpleExpressionType.LessThan, LessThanToWhereClause}, - {SimpleExpressionType.GreaterThanOrEqual, GreaterThanOrEqualToWhereClause}, - {SimpleExpressionType.LessThanOrEqual, LessThanOrEqualToWhereClause}, - {SimpleExpressionType.Empty, expr => _ => true }, - }; - } - - private Func, bool> FunctionExpressionToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - var function = arg.RightOperand as SimpleFunction; - if (ReferenceEquals(function, null)) throw new InvalidOperationException("Expression type of function but no function supplied."); - if (function.Name.Equals("like", StringComparison.OrdinalIgnoreCase)) - { - var pattern = function.Args[0].ToString(); - if (pattern.Contains("%") || pattern.Contains("_")) // SQL Server LIKE - { - pattern = pattern.Replace("%", ".*").Replace('_', '.'); - } - - var regex = new Regex("^" + pattern + "$", RegexOptions.Multiline | RegexOptions.IgnoreCase); - - return d => d.ContainsKey(key) && (!ReferenceEquals(d[key], null)) && regex.IsMatch(d[key].ToString()); - } - - throw new NotSupportedException("Expression Function not supported."); - } - - private Func, bool> GreaterThanToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) > 0; - } - - private Func, bool> LessThanToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) < 0; - } - - private Func, bool> GreaterThanOrEqualToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) >= 0; - } - - private Func, bool> LessThanOrEqualToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) <= 0; - } - - private Func, bool> NotEqualExpressionToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - if (ReferenceEquals(arg.RightOperand, null)) - { - return d => d.ContainsKey(key) && d[key] != null; - } - - if (arg.RightOperand.GetType().IsArray) - { - return - d => - d.ContainsKey(key) && - !((IEnumerable)d[key]).Cast().SequenceEqual(((IEnumerable)arg.RightOperand).Cast()); - } - - return d => d.ContainsKey(key) && !Equals(d[key], arg.RightOperand); - } - - private Func, bool> EqualExpressionToWhereClause(SimpleExpression arg) - { - var key = GetKeyFromLeftOperand(arg); - - if (ReferenceEquals(arg.RightOperand, null)) - { - return d => (!d.ContainsKey(key)) || d[key] == null; - } - - if (arg.RightOperand.GetType().IsArray) - { - return - d => - d.ContainsKey(key) && - ((IEnumerable) d[key]).Cast().SequenceEqual(((IEnumerable) arg.RightOperand).Cast()); - } - - return d => d.ContainsKey(key) && Equals(d[key], arg.RightOperand); - } - - private static string GetKeyFromLeftOperand(SimpleExpression arg) - { - var reference = arg.LeftOperand as ObjectReference; - - if (reference.IsNull()) throw new NotSupportedException("Only ObjectReference types are supported."); - - var key = reference.GetName(); - return key; - } - - private Func, bool> Format(SimpleExpression expression) - { - Func,bool>> formatter; - - if (_expressionFormatters.TryGetValue(expression.Type, out formatter)) - { - return formatter(expression); - } - - return _ => true; - } - - private Func, bool> LogicalExpressionToWhereClause(SimpleExpression arg) - { - var left = Format((SimpleExpression) arg.LeftOperand); - var right = Format((SimpleExpression) arg.RightOperand); - - if (arg.Type == SimpleExpressionType.Or) - { - return d => (left(d) || right(d)); - } - return d => (left(d) && right(d)); - } - - public IEnumerable> Run(IEnumerable> source) - { - var predicate = Format(_whereClause.Criteria); - return source.Where(predicate); - } - } -} \ No newline at end of file diff --git a/Simple.Data/QueryPolyfills/ObjectMaths.cs b/Simple.Data/QueryPolyfills/ObjectMaths.cs index e1efe864..647becc4 100644 --- a/Simple.Data/QueryPolyfills/ObjectMaths.cs +++ b/Simple.Data/QueryPolyfills/ObjectMaths.cs @@ -22,7 +22,7 @@ public static object Increment(object value) if (value is byte) return (byte)value + 1; if (value is sbyte) return (sbyte)value + 1; - throw new ArgumentException("Cannot increment object type."); + throw new ArgumentException(string.Format("Cannot increment object of type '{0}'.", value.GetType().FullName)); } public static object Add(object value1, object value2) @@ -43,11 +43,13 @@ public static object Add(object value1, object value2) if (value1 is byte) return (byte)value1 + (byte)value2; if (value1 is sbyte) return (sbyte)value1 + (sbyte)value2; - throw new ArgumentException("Cannot add object types."); + throw new ArgumentException(string.Format("Cannot add object of types '{0}' and '{1}'.", value1.GetType().FullName, value2.GetType().FullName)); } public static object Divide(object value, int divisor) { + if (ReferenceEquals(value, null)) throw new ArgumentNullException("value"); + if (value is long) return (long)value / divisor; if (value is int) return (int)value / divisor; if (value is short) return (short)value / divisor; @@ -60,7 +62,7 @@ public static object Divide(object value, int divisor) if (value is byte) return (byte)value / divisor; if (value is sbyte) return (sbyte)value / divisor; - throw new ArgumentException("Cannot divide object type."); + throw new ArgumentException(string.Format("Cannot divide object of type '{0}'.", value.GetType())); } } } diff --git a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs index a610a0ad..40e19e11 100644 --- a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs +++ b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs @@ -5,17 +5,20 @@ namespace Simple.Data.QueryPolyfills using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; + using Extensions; internal class WhereClauseHandler { private readonly Dictionary, bool>>> _expressionFormatters; - private readonly WhereClause _whereClause; + private readonly string _mainTableName; + private readonly WhereClause _whereClause; - public WhereClauseHandler(WhereClause whereClause) + public WhereClauseHandler(string mainTableName, WhereClause whereClause) { + _mainTableName = mainTableName; _whereClause = whereClause; - _expressionFormatters = new Dictionary, bool>>> + _expressionFormatters = new Dictionary, bool>>> { {SimpleExpressionType.And, LogicalExpressionToWhereClause}, {SimpleExpressionType.Or, LogicalExpressionToWhereClause}, @@ -95,16 +98,24 @@ private Func, bool> EqualExpressionToWhereClause(Sim { return d => - Resolve(d, arg.LeftOperand).OfType().Any( - o => o.Cast().SequenceEqual(((IEnumerable) arg.RightOperand).Cast())); + { + var resolvedLeftOperand = Resolve(d, arg.LeftOperand); + if (resolvedLeftOperand.Any(o => !(o is string) && o is IEnumerable)) + { + return resolvedLeftOperand.OfType().Any( + o => o.Cast().SequenceEqual(((IEnumerable)arg.RightOperand).Cast())); + } + return resolvedLeftOperand.Any( + o => ((IEnumerable)arg.RightOperand).Cast().Contains(o)); + }; } return d => Resolve(d, arg.LeftOperand).Contains(arg.RightOperand); } - private Func, bool> Format(SimpleExpression expression) + private Func, bool> Format(SimpleExpression expression) { - Func,bool>> formatter; + Func, bool>> formatter; if (_expressionFormatters.TryGetValue(expression.Type, out formatter)) { @@ -116,8 +127,8 @@ private Func, bool> Format(SimpleExpression expressio private Func, bool> LogicalExpressionToWhereClause(SimpleExpression arg) { - var left = Format((SimpleExpression) arg.LeftOperand); - var right = Format((SimpleExpression) arg.RightOperand); + var left = Format((SimpleExpression)arg.LeftOperand); + var right = Format((SimpleExpression)arg.RightOperand); if (arg.Type == SimpleExpressionType.Or) { @@ -134,13 +145,36 @@ private IList Resolve(IDictionary dict, object operand, key = key ?? objectReference.GetAliasOrName(); var keys = objectReference.GetAllObjectNames(); + if (keys.Length > 2) + { + if (_mainTableName.Contains(".")) + { + keys = keys.Skip(1).ToArray(); + keys[0] = _mainTableName; + } + } if (keys.Length > 2) { return ResolveSubs(dict, objectReference.GetOwner(), key).ToList(); } + if (keys.Length == 2 && !HomogenizedEqualityComparer.DefaultInstance.Equals(keys[0].Singularize(), _mainTableName.Singularize())) + { + var joinedDict = dict[keys[0]] as IDictionary; + if (joinedDict != null && joinedDict.ContainsKey(keys[1])) + { + return new[] { joinedDict[keys[1]] }; + } + + var joinedDicts = dict[keys[0]] as IEnumerable>; + if (joinedDicts != null) + { + return joinedDicts.Select(d => d.ContainsKey(keys[1]) ? d[keys[1]] : null).ToArray(); + } + } + if (dict.ContainsKey(key)) - return new[] {dict[key]}; + return new[] { dict[key] }; return new object[0]; } @@ -151,12 +185,12 @@ private IEnumerable ResolveSubs(IDictionary dict, Object if (dict.ContainsKey(objectReference.GetName())) { - var master = dict[objectReference.GetName()] as IDictionary; + var master = dict[objectReference.GetName()] as IDictionary; if (master != null) { if (master.ContainsKey(key)) { - return new[] {master[key]}; + return new[] { master[key] }; } } diff --git a/Simple.Data/RunStrategy.cs b/Simple.Data/RunStrategy.cs new file mode 100644 index 00000000..dfaca998 --- /dev/null +++ b/Simple.Data/RunStrategy.cs @@ -0,0 +1,92 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + using System.Linq; + + internal abstract class RunStrategy + { + protected abstract Adapter Adapter { get; } + + /// + /// Finds data from the specified "table". + /// + /// Name of the table. + /// The criteria. This may be null, in which case all records should be returned. + /// The list of records matching the criteria. If no records are found, return an empty list. + internal abstract IEnumerable> Find(string tableName, SimpleExpression criteria); + + /// + /// Inserts a record into the specified "table". + /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. + internal abstract IEnumerable> InsertMany(string tableName, IEnumerable> enumerable, ErrorCallback onError, bool resultRequired); + + /// + /// Inserts many records into the specified "table". + /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. + internal abstract IDictionary Insert(string tableName, IDictionary data, bool resultRequired); + + /// + /// Updates the specified "table" according to specified criteria. + /// Name of the table.The new values.The expression to use as criteria for the update operation.The number of records affected by the update operation. + internal abstract int Update(string tableName, IDictionary data, SimpleExpression criteria); + + /// + /// Deletes from the specified table. + /// Name of the table.The expression to use as criteria for the delete operation.The number of records which were deleted. + internal abstract int Delete(string tableName, SimpleExpression criteria); + + internal abstract IDictionary FindOne(string getQualifiedName, SimpleExpression criteriaExpression); + + internal abstract int UpdateMany(string tableName, IList> dataList); + + internal abstract int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames); + + internal abstract int UpdateMany(string tableName, IList> newValuesList, + IList> originalValuesList); + + internal abstract int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict); + + public abstract IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback); + + public abstract IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired); + + public abstract IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback); + + public abstract IDictionary Get(string tableName, object[] args); + + public abstract IEnumerable> RunQuery(SimpleQuery query, out IEnumerable unhandledClauses); + + protected static Dictionary CreateChangedValuesDict( + IEnumerable> newValuesDict, IDictionary originalValuesDict) + { + var changedValuesDict = + newValuesDict.Where( + kvp => + (!originalValuesDict.ContainsKey(kvp.Key)) || !(Equals(kvp.Value, originalValuesDict[kvp.Key]))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + return changedValuesDict; + } + + protected SimpleExpression CreateCriteriaFromOriginalValues(string tableName, + IDictionary newValuesDict, + IDictionary + originalValuesDict) + { + var criteriaValues = Adapter.GetKey(tableName, originalValuesDict); + + foreach (var kvp in originalValuesDict + .Where( + originalKvp => + newValuesDict.ContainsKey(originalKvp.Key) && + !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value)))) + { + if (!criteriaValues.ContainsKey(kvp.Key)) + { + criteriaValues.Add(kvp); + } + }; + + return ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteriaValues); + } + } +} \ No newline at end of file diff --git a/Simple.Data/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 8378c42b..7bdf50df 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -53,19 +53,22 @@ + + + - - + + @@ -83,14 +86,20 @@ + + + + + + @@ -117,7 +126,6 @@ - @@ -126,8 +134,10 @@ + + @@ -176,6 +186,7 @@ + diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 01de8f5b..402443a2 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta1 + 0.18.3.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php diff --git a/Simple.Data/SimpleDataTraceSources.cs b/Simple.Data/SimpleDataTraceSources.cs new file mode 100644 index 00000000..f7ff0fab --- /dev/null +++ b/Simple.Data/SimpleDataTraceSources.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; + +namespace Simple.Data +{ + public static class SimpleDataTraceSources + { + private const string TraceSourceName = "Simple.Data"; + private const SourceLevels DefaultLevel = SourceLevels.Warning; + + // Verbose=1xxx Information=2xxx Warning=3xxx Error=4xxx Critical=5xxx + internal const int DebugMessageId = 1000; + internal const int SqlMessageId = 2000; + internal const int GenericWarningMessageId = 3000; + internal const int PerformanceWarningMessageId = 3001; + internal const int ObsoleteWarningMessageId = 3002; + internal const int GenericErrorMessageId = 4000; + + private static TraceSource _traceSource; + + public static TraceSource TraceSource + { + get { return _traceSource ?? (_traceSource = new TraceSource(TraceSourceName, DefaultLevel)); } + } + } +} \ No newline at end of file diff --git a/Simple.Data/SimpleFunction.cs b/Simple.Data/SimpleFunction.cs index 8b088627..9c29e21c 100644 --- a/Simple.Data/SimpleFunction.cs +++ b/Simple.Data/SimpleFunction.cs @@ -22,7 +22,7 @@ public ReadOnlyCollection Args get { return _args; } } - public SimpleFunction(string name, object[] args) + public SimpleFunction(string name, IEnumerable args) { _name = name; _args = args.ToList().AsReadOnly(); diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 144ddc51..81496cef 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; + using Commands; using Extensions; using QueryPolyfills; @@ -105,9 +106,59 @@ public override bool TryConvert(ConvertBinder binder, out object result) result = Cast(); return true; } + + var collectionType = binder.Type.GetInterface("ICollection`1"); + if (collectionType != null) + { + if (TryConvertToGenericCollection(binder, out result, collectionType)) return true; + } + + if (binder.Type.Name.Equals("IEnumerable`1")) + { + var genericArguments = binder.Type.GetGenericArguments(); + var cast = + typeof (SimpleQuery).GetMethod("Cast").MakeGenericMethod(genericArguments); + result = cast.Invoke(this, null); + return true; + } + return base.TryConvert(binder, out result); } + private bool TryConvertToGenericCollection(ConvertBinder binder, out object result, Type collectionType) + { + var genericArguments = collectionType.GetGenericArguments(); + var enumerableConstructor = + binder.Type.GetConstructor(new[] + { + typeof (IEnumerable<>).MakeGenericType( + genericArguments) + }); + if (enumerableConstructor != null) + { + var cast = + typeof (SimpleQuery).GetMethod("Cast").MakeGenericMethod(genericArguments); + result = Activator.CreateInstance(binder.Type, cast.Invoke(this, null)); + return true; + } + + var defaultConstructor = binder.Type.GetConstructor(new Type[0]); + if (defaultConstructor != null) + { + result = Activator.CreateInstance(binder.Type); + var add = binder.Type.GetMethod("Add", genericArguments); + var cast = + typeof (SimpleQuery).GetMethod("Cast").MakeGenericMethod(genericArguments); + foreach (var item in (IEnumerable) cast.Invoke(this, null)) + { + add.Invoke(result, new[] {item}); + } + return true; + } + result = null; + return false; + } + /// /// Selects only the specified columns. /// @@ -149,6 +200,25 @@ public SimpleQuery ReplaceSelect(IEnumerable columns) return new SimpleQuery(this, _clauses.Where(c => !(c is SelectClause)).Append(new SelectClause(columns)).ToArray()); } + /// + /// Alters the query to lock the rows for update. + /// + /// Indicates whether the selection should skip rows already locked + /// A new which will perform locking on the selected rows + public SimpleQuery ForUpdate(bool skipLockedRows) + { + return new SimpleQuery(this, _clauses.Where(c => !(c is ForUpdateClause)).Append(new ForUpdateClause(skipLockedRows)).ToArray()); + } + + /// + /// Removes any specified ForUpdate from the Query + /// + /// A new with any specified ForUpdate removed + public SimpleQuery ClearForUpdate() + { + return new SimpleQuery(this, _clauses.Where(c => !(c is ForUpdateClause)).ToArray()); + } + private void ThrowIfThereIsAlreadyASelectClause() { if (_clauses.OfType().Any()) @@ -163,6 +233,7 @@ public SimpleQuery ReplaceWhere(SimpleExpression criteria) public SimpleQuery Where(SimpleExpression criteria) { + if (criteria == null) throw new ArgumentNullException("criteria"); return new SimpleQuery(this, _clauses.Append(new WhereClause(criteria))); } @@ -171,6 +242,20 @@ public SimpleQuery OrderBy(ObjectReference reference, OrderByDirection? directio return new SimpleQuery(this, _clauses.Append(new OrderByClause(reference, direction))); } + public SimpleQuery OrderBy(params ObjectReference[] references) + { + if (references.Length == 0) + { + throw new ArgumentException("OrderBy requires parameters"); + } + var q = this.OrderBy(references[0]); + foreach (var reference in references.Skip(1)) + { + q = q.ThenBy(reference); + } + return q; + } + public SimpleQuery OrderByDescending(ObjectReference reference) { return new SimpleQuery(this, _clauses.Append(new OrderByClause(reference, OrderByDirection.Descending))); @@ -185,7 +270,7 @@ public SimpleQuery ThenBy(ObjectReference reference, OrderByDirection? direction private void ThrowIfNoOrderByClause(string message) { - if (!_clauses.OfType().Any()) + if (_clauses == null || !_clauses.OfType().Any()) { throw new InvalidOperationException(message); } @@ -193,12 +278,7 @@ private void ThrowIfNoOrderByClause(string message) public SimpleQuery ThenByDescending(ObjectReference reference) { - if (!_clauses.OfType().Any()) - { - throw new InvalidOperationException("ThenBy requires an existing OrderBy"); - } - - return new SimpleQuery(this, _clauses.Append(new OrderByClause(reference, OrderByDirection.Descending))); + return ThenBy(reference, OrderByDirection.Descending); } public SimpleQuery Skip(int skip) @@ -234,14 +314,14 @@ public SimpleQuery ClearWithTotalCount() protected IEnumerable Run() { IEnumerable unhandledClauses; - var result = _adapter.RunQuery(this, out unhandledClauses); + var result = _dataStrategy.Run.RunQuery(this, out unhandledClauses); if (unhandledClauses != null) { var unhandledClausesList = unhandledClauses.ToList(); if (unhandledClausesList.Count > 0) { - result = new DictionaryQueryRunner(result, unhandledClausesList).Run(); + result = new DictionaryQueryRunner(_tableName, result, unhandledClausesList).Run(); } } @@ -256,11 +336,19 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } if (binder.Name.StartsWith("order", StringComparison.OrdinalIgnoreCase)) { + if (args.Length != 0) + { + throw new ArgumentException("OrderByColumn form does not accept parameters"); + } result = ParseOrderBy(binder.Name); return true; } if (binder.Name.StartsWith("then", StringComparison.OrdinalIgnoreCase)) { + if (args.Length != 0) + { + throw new ArgumentException("ThenByColumn form does not accept parameters"); + } result = ParseThenBy(binder.Name); return true; } @@ -281,14 +369,23 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } if (binder.Name.Equals("having", StringComparison.OrdinalIgnoreCase)) { - var expression = args.SingleOrDefault() as SimpleExpression; + SimpleExpression expression; + try + { + expression = args.SingleOrDefault() as SimpleExpression; + } + catch (InvalidOperationException) + { + throw new ArgumentException("Having requires an expression"); + } if (expression != null) { result = new SimpleQuery(this, _clauses.Append(new HavingClause(expression))); return true; } + throw new ArgumentException("Having requires an expression"); } - if (binder.Name.StartsWith("with", StringComparison.OrdinalIgnoreCase)) + if (binder.Name.StartsWith("with", StringComparison.OrdinalIgnoreCase) && !binder.Name.Equals("WithTotalCount", StringComparison.OrdinalIgnoreCase)) { result = ParseWith(binder, args); return true; @@ -299,7 +396,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o return true; } - var command = Commands.CommandFactory.GetCommandFor(binder.Name); + var command = Commands.CommandFactory.GetCommandFor(binder.Name) as IQueryCompatibleCommand; if (command != null) { try @@ -313,7 +410,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } try { - var methodInfo = typeof(SimpleQuery).GetMethod(binder.Name); + var methodInfo = typeof(SimpleQuery).GetMethod(binder.Name, args.Select(a => (a ?? new object()).GetType()).ToArray()); if (methodInfo != null) { methodInfo.Invoke(this, args); @@ -323,14 +420,30 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o { } + if (binder.Name.Equals("where", StringComparison.InvariantCultureIgnoreCase) || binder.Name.Equals("replacewhere", StringComparison.InvariantCultureIgnoreCase)) + { + throw new BadExpressionException("Where methods require a single criteria expression."); + } + return false; } - private static bool MethodIsCallable(MethodInfo mi, InvokeMemberBinder binder, object[] args) + public SimpleQuery With(ObjectReference reference, out dynamic queryObjectReference) { - return mi.Name.Equals(binder.Name, StringComparison.OrdinalIgnoreCase) && - mi.GetParameters().Length == 1 && - Convert.ChangeType(args, mi.GetParameters().Single().ParameterType) != null; + queryObjectReference = reference; + return With(new[] {reference}); + } + + public SimpleQuery WithOne(ObjectReference reference, out dynamic queryObjectReference) + { + queryObjectReference = reference; + return With(new[] {reference}, WithType.One); + } + + public SimpleQuery WithMany(ObjectReference reference, out dynamic queryObjectReference) + { + queryObjectReference = reference; + return With(new[] {reference}, WithType.Many); } private SimpleQuery ParseWith(InvokeMemberBinder binder, object[] args) @@ -351,9 +464,15 @@ private SimpleQuery ParseWith(InvokeMemberBinder binder, object[] args) { return With(args, WithType.Many); } + + throw new ArgumentException("WithTable form does not accept parameters"); } var objectName = binder.Name.Substring(4); + if (string.IsNullOrWhiteSpace(objectName)) + { + throw new ArgumentException("With requires a Table reference"); + } var withClause = new WithClause(new ObjectReference(objectName, new ObjectReference(_tableName, _dataStrategy), _dataStrategy)); return new SimpleQuery(this, _clauses.Append(withClause)); } @@ -424,10 +543,65 @@ public SimpleQuery OuterJoin(ObjectReference objectReference, out dynamic queryO return this; } + public SimpleQuery Join(DynamicTable dynamicTable, JoinType joinType) + { + if (ReferenceEquals(dynamicTable, null)) throw new ArgumentNullException("dynamicTable"); + _tempJoinWaitingForOn = new JoinClause(dynamicTable.ToObjectReference(), joinType, null); + + return this; + } + + public SimpleQuery Join(DynamicTable dynamicTable, out dynamic queryObjectReference) + { + return Join(dynamicTable, JoinType.Inner, out queryObjectReference); + } + + public SimpleQuery Join(DynamicTable dynamicTable, JoinType joinType, out dynamic queryObjectReference) + { + if (ReferenceEquals(dynamicTable, null)) throw new ArgumentNullException("dynamicTable"); + var newJoin = new JoinClause(dynamicTable.ToObjectReference(), null); + _tempJoinWaitingForOn = newJoin; + queryObjectReference = dynamicTable.ToObjectReference(); + + return this; + } + + public SimpleQuery LeftJoin(DynamicTable dynamicTable) + { + return OuterJoin(dynamicTable); + } + + public SimpleQuery LeftJoin(DynamicTable dynamicTable, out dynamic queryObjectReference) + { + return OuterJoin(dynamicTable, out queryObjectReference); + } + + public SimpleQuery OuterJoin(DynamicTable dynamicTable) + { + if (ReferenceEquals(dynamicTable, null)) throw new ArgumentNullException("dynamicTable"); + _tempJoinWaitingForOn = new JoinClause(dynamicTable.ToObjectReference(), JoinType.Outer); + + return this; + } + + public SimpleQuery OuterJoin(DynamicTable dynamicTable, out dynamic queryObjectReference) + { + _tempJoinWaitingForOn = new JoinClause(dynamicTable.ToObjectReference(), JoinType.Outer); + queryObjectReference = dynamicTable; + + return this; + } + public SimpleQuery On(SimpleExpression joinExpression) { if (_tempJoinWaitingForOn == null) - throw new InvalidOperationException("Call to On must be preceded by call to Join."); + { + throw new InvalidOperationException("Call to On must be preceded by call to JoinInfo."); + } + if (ReferenceEquals(joinExpression, null)) + { + throw new BadExpressionException("On expects an expression or named parameters."); + } return AddNewJoin(new JoinClause(_tempJoinWaitingForOn.Table, _tempJoinWaitingForOn.JoinType, joinExpression)); } @@ -449,7 +623,19 @@ public SimpleQuery WithTotalCount(out Promise count) private SimpleQuery ParseJoin(InvokeMemberBinder binder, object[] args) { var tableToJoin = args[0] as ObjectReference; - if (tableToJoin == null) throw new InvalidOperationException(); + if (ReferenceEquals(tableToJoin, null)) + { + var dynamicTable = args[0] as DynamicTable; + if (!ReferenceEquals(dynamicTable, null)) + { + tableToJoin = dynamicTable.ToObjectReference(); + } + } + if (tableToJoin == null) throw new BadJoinExpressionException("Incorrect join table specified"); + if (HomogenizedEqualityComparer.DefaultInstance.Equals(tableToJoin.GetAliasOrName(), _tableName)) + { + throw new BadJoinExpressionException("Cannot join unaliased table to itself."); + } SimpleExpression joinExpression = null; @@ -463,7 +649,7 @@ private SimpleQuery ParseJoin(InvokeMemberBinder binder, object[] args) joinExpression = args[1] as SimpleExpression; } - if (joinExpression == null) throw new InvalidOperationException(); + if (joinExpression == null) throw new BadJoinExpressionException("Could not create join expression"); var type = binder.Name.Equals("join", StringComparison.OrdinalIgnoreCase) ? JoinType.Inner : JoinType.Outer; var newJoin = new JoinClause(tableToJoin, type, joinExpression); @@ -474,9 +660,16 @@ private SimpleQuery ParseJoin(InvokeMemberBinder binder, object[] args) private SimpleQuery ParseOn(InvokeMemberBinder binder, IEnumerable args) { if (_tempJoinWaitingForOn == null) - throw new InvalidOperationException("Call to On must be preceded by call to Join."); + { + throw new InvalidOperationException("Call to On must be preceded by call to JoinInfo."); + } + var namedArguments = binder.NamedArgumentsToDictionary(args); + if (namedArguments == null || namedArguments.Count == 0) + { + throw new BadExpressionException("On expects an expression or named parameters."); + } var joinExpression = ExpressionHelper.CriteriaDictionaryToExpression(_tempJoinWaitingForOn.Table, - binder.NamedArgumentsToDictionary(args)); + namedArguments); return AddNewJoin(new JoinClause(_tempJoinWaitingForOn.Table, _tempJoinWaitingForOn.JoinType, joinExpression)); } @@ -489,9 +682,17 @@ private SimpleQuery AddNewJoin(JoinClause newJoin) private SimpleQuery ParseOrderBy(string methodName) { methodName = Regex.Replace(methodName, "^order_?by_?", "", RegexOptions.IgnoreCase); + if (string.IsNullOrWhiteSpace(methodName)) + { + throw new ArgumentException("Invalid arguments to OrderBy"); + } if (methodName.EndsWith("descending", StringComparison.OrdinalIgnoreCase)) { methodName = Regex.Replace(methodName, "_?descending$", "", RegexOptions.IgnoreCase); + if (string.IsNullOrWhiteSpace(methodName)) + { + throw new ArgumentException("Invalid arguments to OrderByDescending"); + } return OrderByDescending(ObjectReference.FromString(_tableName + "." + methodName)); } return OrderBy(ObjectReference.FromString(_tableName + "." + methodName)); @@ -499,6 +700,7 @@ private SimpleQuery ParseOrderBy(string methodName) private SimpleQuery ParseThenBy(string methodName) { + ThrowIfNoOrderByClause("Must call OrderBy before ThenBy"); methodName = Regex.Replace(methodName, "^then_?by_?", "", RegexOptions.IgnoreCase); if (methodName.EndsWith("descending", StringComparison.OrdinalIgnoreCase)) { @@ -535,15 +737,11 @@ public dynamic ToScalar() { throw new SimpleDataException("Query returned no rows; cannot return scalar value."); } - if (data.Length != 1) - { - throw new SimpleDataException("Query returned multiple rows; cannot return scalar value."); - } - if (data[0].Count > 1) + if (data[0].Count == 0) { - throw new SimpleDataException("Selected row contains multiple values; cannot return scalar value."); + throw new SimpleDataException("Selected row contains no values; cannot return scalar value."); } - return data[0].Single().Value; + return data[0].First().Value; } public dynamic ToScalarOrDefault() @@ -553,15 +751,11 @@ public dynamic ToScalarOrDefault() { return null; } - if (data.Length != 1) + if (data[0].Count == 0) { - throw new SimpleDataException("Query returned multiple rows; cannot return scalar value."); - } - if (data[0].Count > 1) - { - throw new SimpleDataException("Selected row contains multiple values; cannot return scalar value."); + return null; } - return data[0].Single().Value; + return data[0].First().Value; } public IList ToScalarList() @@ -611,22 +805,22 @@ public T ToScalarOrDefault() public dynamic First() { - return Run().First(); + return Take(1).Run().First(); } public dynamic FirstOrDefault() { - return Run().FirstOrDefault(); + return Take(1).Run().FirstOrDefault(); } public T First() { - return Cast().First(); + return Take(1).Cast().First(); } public T FirstOrDefault() { - return Cast().FirstOrDefault(); + return Take(1).Cast().FirstOrDefault(); } public T First(Func predicate) @@ -641,22 +835,22 @@ public T FirstOrDefault(Func predicate) public dynamic Single() { - return Run().First(); + return Take(1).Run().First(); } public dynamic SingleOrDefault() { - return Run().FirstOrDefault(); + return Take(1).Run().FirstOrDefault(); } public T Single() { - return Cast().Single(); + return Take(1).Cast().Single(); } public T SingleOrDefault() { - return Cast().SingleOrDefault(); + return Take(1).Cast().SingleOrDefault(); } public T Single(Func predicate) @@ -671,7 +865,7 @@ public T SingleOrDefault(Func predicate) public int Count() { - return (int)Select(new CountSpecialReference()).ToScalar(); + return Convert.ToInt32(Select(new CountSpecialReference()).ToScalar()); } /// diff --git a/Simple.Data/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index 4aab3ddf..65e99d1a 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -46,12 +46,22 @@ public SimpleRecord(IDictionary data, string tableName, DataStra public override bool TryGetMember(GetMemberBinder binder, out object result) { - if (_data.ContainsKey(binder.Name)) + if (TryGetMember(binder.Name, out result) == false) { - result = _data[binder.Name]; + return base.TryGetMember(binder, out result); + } + + return true; + } + + private bool TryGetMember(string name, out object result) + { + if (_data.ContainsKey(name)) + { + result = _data[name]; var converted = ConvertResult(result); if (!ReferenceEquals(result, converted)) - _data[binder.Name] = result = converted; + _data[name] = result = converted; return true; } @@ -64,20 +74,29 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) if (_database != null) { - var relatedAdapter = _database.GetAdapter() as IAdapterWithRelation; - if (relatedAdapter != null && relatedAdapter.IsValidRelation(_tableName, binder.Name)) + try { - result = GetRelatedData(binder, relatedAdapter); - return true; + var relatedAdapter = _database.GetAdapter() as IAdapterWithRelation; + if (relatedAdapter != null && relatedAdapter.IsValidRelation(_tableName, name)) + { + result = GetRelatedData(name, relatedAdapter); + return true; + } + } + catch (UnresolvableObjectException e) + { + throw new UnresolvableObjectException(e.ObjectName, string.Format("Column '{0}' not found.", e.ObjectName), e); } } - return base.TryGetMember(binder, out result); + + result = null; + return false; } - private object GetRelatedData(GetMemberBinder binder, IAdapterWithRelation relatedAdapter) + private object GetRelatedData(string name, IAdapterWithRelation relatedAdapter) { object result; - var related = relatedAdapter.FindRelated(_tableName, _data, binder.Name); + var related = relatedAdapter.FindRelated(_tableName, _data, name); var query = related as SimpleQuery; if (query != null) { @@ -87,10 +106,10 @@ private object GetRelatedData(GetMemberBinder binder, IAdapterWithRelation relat else { result = related is IDictionary - ? (object) new SimpleRecord(related as IDictionary, binder.Name, _database) - : new SimpleResultSet( - ((IEnumerable>) related).Select( - dict => new SimpleRecord(dict, binder.Name, _database))); + ? (object) new SimpleRecord(related as IDictionary, name, _database) + : ((IEnumerable>) related).Select( + dict => new SimpleRecord(dict, name, _database)).ToList(); + _data[name] = result; } return result; @@ -108,6 +127,19 @@ public override bool TryConvert(ConvertBinder binder, out object result) return result != null; } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + if (indexes.Length == 1 && indexes[0] is string) + { + if (TryGetMember((string) indexes[0], out result)) + { + return true; + } + } + + return base.TryGetIndex(binder, indexes, out result); + } + public override IEnumerable GetDynamicMemberNames() { return _data.Keys.AsEnumerable(); diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 7ea2db19..5af48cf3 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Dynamic; -using System.Linq; using System.Text; namespace Simple.Data @@ -14,54 +13,76 @@ namespace Simple.Data /// public sealed class SimpleTransaction : DataStrategy, IDisposable { - private readonly Database _database; + private readonly DataStrategy _database; + private readonly IsolationLevel _isolationLevel; private readonly IAdapterWithTransactions _adapter; + private TransactionRunner _transactionRunner; private IAdapterTransaction _adapterTransaction; - private SimpleTransaction(IAdapterWithTransactions adapter, Database database) + private SimpleTransaction(IAdapterWithTransactions adapter, DataStrategy database, IsolationLevel isolationLevel) { if (adapter == null) throw new ArgumentNullException("adapter"); if (database == null) throw new ArgumentNullException("database"); _adapter = adapter; _database = database; + _isolationLevel = isolationLevel; + } + + private SimpleTransaction(SimpleTransaction copy) : base(copy) + { + _adapter = copy._adapter; + _database = copy._database; + _adapterTransaction = copy._adapterTransaction; + _transactionRunner = copy._transactionRunner; } private void Begin() { - _adapterTransaction = _adapter.BeginTransaction(); + _adapterTransaction = _adapter.BeginTransaction(_isolationLevel); + _transactionRunner = new TransactionRunner(_adapter, _adapterTransaction); } private void Begin(string name) { - _adapterTransaction = _adapter.BeginTransaction(name); + _adapterTransaction = _adapter.BeginTransaction(name, _isolationLevel); + _transactionRunner = new TransactionRunner(_adapter, _adapterTransaction); } - internal static SimpleTransaction Begin(Database database) + internal static SimpleTransaction Begin(DataStrategy database) { SimpleTransaction transaction = CreateTransaction(database); transaction.Begin(); return transaction; } - internal static SimpleTransaction Begin(Database database, string name) + internal static SimpleTransaction Begin(DataStrategy database, string name) { SimpleTransaction transaction = CreateTransaction(database); transaction.Begin(name); return transaction; } - private static SimpleTransaction CreateTransaction(Database database) + public static SimpleTransaction Begin(DataStrategy database, IsolationLevel isolationLevel) + { + var transaction = CreateTransaction(database, isolationLevel); + transaction.Begin(); + return transaction; + } + + private static SimpleTransaction CreateTransaction(DataStrategy database, IsolationLevel isolationLevel = IsolationLevel.Unspecified) { var adapterWithTransactions = database.GetAdapter() as IAdapterWithTransactions; if (adapterWithTransactions == null) throw new NotSupportedException(); - return new SimpleTransaction(adapterWithTransactions, database); + return new SimpleTransaction(adapterWithTransactions, database, isolationLevel); } - - internal Database Database + internal DataStrategy Database { - get { return _database; } + get + { + return _database; + } } /// @@ -94,85 +115,6 @@ public void Rollback() _adapterTransaction.Rollback(); } - internal override IDictionary FindOne(string tableName, SimpleExpression criteria) - { - return Find(tableName, criteria).FirstOrDefault(); - } - - internal override int UpdateMany(string tableName, IList> dataList) - { - return _adapter.UpdateMany(tableName, dataList, AdapterTransaction); - } - - internal override int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames) - { - return _adapter.UpdateMany(tableName, dataList, criteriaFieldNames, AdapterTransaction); - } - - internal override IEnumerable> Find(string tableName, SimpleExpression criteria) - { - return _adapter.Find(tableName, criteria, AdapterTransaction); - } - - internal override IDictionary Insert(string tableName, IDictionary data, bool resultRequired) - { - return _adapter.Insert(tableName, data, AdapterTransaction, resultRequired); - } - - /// - /// Inserts a record into the specified "table". - /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. - internal override IEnumerable> InsertMany(string tableName, IEnumerable> data, ErrorCallback onError, bool resultRequired) - { - return _adapter.InsertMany(tableName, data, AdapterTransaction, (dict, exception) => onError(new SimpleRecord(dict), exception), resultRequired); - } - - internal override int Update(string tableName, IDictionary data, SimpleExpression criteria) - { - return _adapter.Update(tableName, data, criteria, AdapterTransaction); - } - - public override IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired) - { - return _adapter.Upsert(tableName, dict, criteriaExpression, isResultRequired, AdapterTransaction); - } - - public override IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback) - { - return _adapter.UpsertMany(tableName, list, AdapterTransaction, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); - } - - public override IDictionary Get(string tableName, object[] args) - { - return _adapter.Get(tableName, AdapterTransaction, args); - } - - public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) - { - return _adapter.UpsertMany(tableName, list, keyFieldNames, AdapterTransaction, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); - } - - internal override int UpdateMany(string tableName, IList> newValuesList, IList> originalValuesList) - { - int count = 0; - for (int i = 0; i < newValuesList.Count; i++) - { - count += Update(tableName, newValuesList[i], originalValuesList[i]); - } - return count; - } - - public override int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) - { - SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); - var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); - return _adapter.Update(tableName, changedValuesDict, criteria, AdapterTransaction); - } - - internal override int Delete(string tableName, SimpleExpression criteria) - { - return _adapter.Delete(tableName, criteria, AdapterTransaction); - } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. @@ -185,7 +127,8 @@ public void Dispose() } catch (Exception ex) { - Trace.WriteLine("IAdapterTransaction Dispose threw exception: " + ex.Message); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Error, SimpleDataTraceSources.GenericErrorMessageId, + "IAdapterTransaction Dispose threw exception: {0}", ex.Message); } } @@ -194,14 +137,24 @@ public override Adapter GetAdapter() return _adapter as Adapter; } - protected override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) + protected internal override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) { return command.Execute(out result, _adapterTransaction); } - protected internal override Database GetDatabase() + protected internal override DataStrategy GetDatabase() { return _database; } + + internal override RunStrategy Run + { + get { return _transactionRunner; } + } + + protected internal override DataStrategy Clone() + { + return new SimpleTransaction(this); + } } } diff --git a/Simple.Data/TransactionRunner.cs b/Simple.Data/TransactionRunner.cs new file mode 100644 index 00000000..b7624373 --- /dev/null +++ b/Simple.Data/TransactionRunner.cs @@ -0,0 +1,108 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + using System.Linq; + + internal class TransactionRunner : RunStrategy + { + private readonly IAdapterWithTransactions _adapter; + private readonly IAdapterTransaction _adapterTransaction; + + public TransactionRunner(IAdapterWithTransactions adapter, IAdapterTransaction adapterTransaction) + { + _adapter = adapter; + _adapterTransaction = adapterTransaction; + } + + protected override Adapter Adapter + { + get { return (Adapter) _adapter; } + } + + internal override IDictionary FindOne(string tableName, SimpleExpression criteria) + { + return Find(tableName, criteria).FirstOrDefault(); + } + + internal override int UpdateMany(string tableName, IList> dataList) + { + return _adapter.UpdateMany(tableName, dataList, _adapterTransaction); + } + + internal override int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames) + { + return _adapter.UpdateMany(tableName, dataList, criteriaFieldNames, _adapterTransaction); + } + + internal override IEnumerable> Find(string tableName, SimpleExpression criteria) + { + return _adapter.Find(tableName, criteria, _adapterTransaction); + } + + internal override IDictionary Insert(string tableName, IDictionary data, bool resultRequired) + { + return _adapter.Insert(tableName, data, _adapterTransaction, resultRequired); + } + + /// + /// Inserts a record into the specified "table". + /// Name of the table.The values to insert.If possible, return the newly inserted row, including any automatically-set values such as primary keys or timestamps. + internal override IEnumerable> InsertMany(string tableName, IEnumerable> data, ErrorCallback onError, bool resultRequired) + { + return _adapter.InsertMany(tableName, data, _adapterTransaction, (dict, exception) => onError(new SimpleRecord(dict), exception), resultRequired); + } + + internal override int Update(string tableName, IDictionary data, SimpleExpression criteria) + { + return _adapter.Update(tableName, data, criteria, _adapterTransaction); + } + + public override IDictionary Upsert(string tableName, IDictionary dict, SimpleExpression criteriaExpression, bool isResultRequired) + { + return _adapter.Upsert(tableName, dict, criteriaExpression, isResultRequired, _adapterTransaction); + } + + public override IEnumerable> UpsertMany(string tableName, IList> list, bool isResultRequired, ErrorCallback errorCallback) + { + return _adapter.UpsertMany(tableName, list, _adapterTransaction, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); + } + + public override IDictionary Get(string tableName, object[] args) + { + return _adapter.Get(tableName, _adapterTransaction, args); + } + + public override IEnumerable> RunQuery(SimpleQuery query, out IEnumerable unhandledClauses) + { + return _adapter.RunQuery(query, _adapterTransaction, out unhandledClauses); + } + + public override IEnumerable> UpsertMany(string tableName, IList> list, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) + { + return _adapter.UpsertMany(tableName, list, keyFieldNames, _adapterTransaction, isResultRequired, (dict, exception) => errorCallback(new SimpleRecord(dict), exception)); + } + + internal override int UpdateMany(string tableName, IList> newValuesList, IList> originalValuesList) + { + int count = 0; + for (int i = 0; i < newValuesList.Count; i++) + { + count += Update(tableName, newValuesList[i], originalValuesList[i]); + } + return count; + } + + internal override int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) + { + SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); + var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); + if (changedValuesDict.Count == 0) return 0; + return _adapter.Update(tableName, changedValuesDict, criteria, _adapterTransaction); + } + + internal override int Delete(string tableName, SimpleExpression criteria) + { + return _adapter.Delete(tableName, criteria, _adapterTransaction); + } + } +} \ No newline at end of file diff --git a/Simple.Data/TypeExtensions.cs b/Simple.Data/TypeExtensions.cs index cb070d93..76410b43 100644 --- a/Simple.Data/TypeExtensions.cs +++ b/Simple.Data/TypeExtensions.cs @@ -3,6 +3,7 @@ namespace Simple.Data using System; using System.Collections.Generic; using System.Linq; + using System.Reflection; public static class TypeExtensions { @@ -15,5 +16,14 @@ public static bool IsGenericCollection(this Type type) .Select(i => i.GetGenericTypeDefinition()) .Contains(typeof(ICollection<>))); } + + public static MethodInfo GetInterfaceMethod(this Type type, string name) + { + return type.GetMethod(name) + ?? + type.GetInterfaces() + .Select(t => t.GetInterfaceMethod(name)) + .FirstOrDefault(m => m != null); + } } } \ No newline at end of file diff --git a/Simple.Data/WithClause.cs b/Simple.Data/WithClause.cs index 5810d4b0..14d61258 100644 --- a/Simple.Data/WithClause.cs +++ b/Simple.Data/WithClause.cs @@ -4,7 +4,7 @@ public class WithClause : SimpleQueryClauseBase { private readonly ObjectReference _objectReference; private readonly WithMode _mode; - private readonly WithType _type; + private WithType _type; public WithClause(ObjectReference objectReference) : this(objectReference, WithType.NotSpecified) { @@ -33,6 +33,7 @@ public WithMode Mode public WithType Type { get { return _type; } + set { _type = value; } } public ObjectReference ObjectReference diff --git a/Simple_Data.ndproj b/Simple_Data.ndproj index 21a31924..e93ccd2c 100644 --- a/Simple_Data.ndproj +++ b/Simple_Data.ndproj @@ -10,6 +10,7 @@ System System.ComponentModel.Composition Microsoft.CSharp + System.Configuration C:\Windows\Microsoft.NET\Framework\v4.0.30319 diff --git a/cleanup.sh b/cleanup.sh index a7056d76..b492911f 100644 --- a/cleanup.sh +++ b/cleanup.sh @@ -1,2 +1 @@ -find . -name *.nupkg -exec rm {} \; -find . -name *.mm.dll -exec rm {} \; +find . -name *.nupkg -maxdepth 2 -exec rm {} \; diff --git a/packages/repositories.config b/packages/repositories.config index bc22641c..c672440b 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -1,14 +1,15 @@  - + + + + + + + + - - - - - - \ No newline at end of file diff --git a/replacenugetversion.sh b/replacenugetversion.sh index cd147a60..8264ef83 100644 --- a/replacenugetversion.sh +++ b/replacenugetversion.sh @@ -1 +1 @@ -find . -name *.nuspec -exec sed -i "s/$1/$2/" {} \; +find . -name *.nuspec -maxdepth 2 -exec sed -i "s/$1/$2/" {} \;