From 4799bbb5cae1ca02fc0d948f1cf143691b4a795b Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 23 Feb 2012 13:20:13 +0000 Subject: [PATCH 001/160] Added if(DEBUG) directive to Trace output. Closes issue #159. --- Simple.Data.Ado/ProcedureExecutor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index 1a40b048..4a160250 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -96,7 +96,9 @@ public IEnumerable ExecuteReader(IDbCommand command) private static IEnumerable ExecuteNonQuery(IDbCommand command) { command.WriteTrace(); +#if(DEBUG) Trace.TraceInformation("ExecuteNonQuery", "Simple.Data.SqlTest"); +#endif command.Connection.OpenIfClosed(); command.ExecuteNonQuery(); return Enumerable.Empty(); From 02162bedc178a4b1c6c1e3fac8654936b145264e Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 23 Feb 2012 13:52:21 +0000 Subject: [PATCH 002/160] Fixed object-name-related JOINing issue #157 --- Simple.Data.Ado/QueryBuilder.cs | 18 +++++++++++++----- Simple.Data.BehaviourTest/Query/WithTest.cs | 18 ++++++++++++++++++ Simple.Data.SqlTest/QueryTest.cs | 12 ++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index 88977b1f..c95850df 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -206,11 +206,19 @@ private void HandleJoins() ? 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()); + 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)) { diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index 506b6059..c29f0339 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -172,5 +172,23 @@ public void MultipleWithClauseJustDoesEverythingYouWouldHope() 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); + } } } \ No newline at end of file diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index c4e94787..11fdb084 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -331,6 +331,18 @@ public void WithClauseShouldPreselectDetailTableAsCollection() Assert.AreEqual(1, orders.Count); } + [Test] + public void WithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollection() + { + var db = DatabaseHelper.Open(); + 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>; + Assert.IsNotNull(orders); + Assert.AreEqual(1, orders.Count); + } + [Test] public void WithClauseShouldPreselectMasterTableAsDictionary() { From 8a6d9055db0cb327abbdd25244ca86f258abdf70 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 23 Feb 2012 14:22:37 +0000 Subject: [PATCH 003/160] Release 1.0.0-beta2 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index a1e7751d..22a753ff 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("1.0.0.2")] +[assembly: AssemblyFileVersion("1.0.0.2")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index bdafe2d8..0ee3ada5 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 + 1.0.0-beta2 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 3501cfe8..4794e986 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 + 1.0.0-beta2 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 7d805129..29e623d1 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta1 + 1.0.0-beta2 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 429cdef7..998a8eb2 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 + 1.0.0-beta2 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 01de8f5b..ac0f6e9c 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta1 + 1.0.0-beta2 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 28da710865bd237bb90aedf8dd756a6fc4706542 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Tue, 28 Feb 2012 11:17:52 +0000 Subject: [PATCH 004/160] Fix for issue #163 --- Simple.Data.SqlServer/DbTypeLookup.cs | 3 ++ Simple.Data.SqlServer/SqlSchemaProvider.cs | 7 ++-- Simple.Data.SqlTest/FindTests.cs | 9 ++++ Simple.Data.SqlTest/FunctionTest.cs | 22 ++++++++++ .../Resources/DatabaseReset.txt | 33 +++++++++++++++ .../Simple.Data.SqlTest.csproj | 2 + Simple.Data.SqlTest/WeirdTypeTest.cs | 41 +++++++++++++++++++ 7 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 Simple.Data.SqlTest/FunctionTest.cs create mode 100644 Simple.Data.SqlTest/WeirdTypeTest.cs diff --git a/Simple.Data.SqlServer/DbTypeLookup.cs b/Simple.Data.SqlServer/DbTypeLookup.cs index bac85c50..06e1c96a 100644 --- a/Simple.Data.SqlServer/DbTypeLookup.cs +++ b/Simple.Data.SqlServer/DbTypeLookup.cs @@ -42,6 +42,9 @@ internal static class DbTypeLookup {"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/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index 3570f6e0..c48174d6 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -41,11 +41,9 @@ 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"]); + var sqlDbType = row.IsNull("type_name") ? SqlDbType.Udt : DbTypeFromInformationSchemaTypeName((string)row["type_name"]); var size = (short)row["max_length"]; switch (sqlDbType) { @@ -156,7 +154,8 @@ 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", + @"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); } diff --git a/Simple.Data.SqlTest/FindTests.cs b/Simple.Data.SqlTest/FindTests.cs index 594b875e..d5e6e4dc 100644 --- a/Simple.Data.SqlTest/FindTests.cs +++ b/Simple.Data.SqlTest/FindTests.cs @@ -212,5 +212,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..1a8fd710 --- /dev/null +++ b/Simple.Data.SqlTest/FunctionTest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Simple.Data.SqlTest +{ + using NUnit.Framework; + + [TestFixture] + public class FunctionTest + { + [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/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index d23a3371..01c05239 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -62,6 +62,12 @@ BEGIN 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] CREATE TABLE [dbo].[Users] ( [Id] INT IDENTITY (1, 1) NOT NULL, @@ -166,6 +172,33 @@ BEGIN [Value] [decimal](20,6) NOT NULL ) + 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 + )) + BEGIN TRANSACTION SET IDENTITY_INSERT [dbo].[Customers] ON INSERT INTO [dbo].[Customers] ([CustomerId], [Name], [Address]) VALUES (1, N'Test', N'100 Road') diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index f5b82a41..c94a55c9 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -65,6 +65,8 @@ + + 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); + } + } +} From 2f74d15281d9810f57d59838973705f1fb043dcb Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Tue, 28 Feb 2012 11:54:27 +0000 Subject: [PATCH 005/160] Fixed issue #164 --- Simple.Data.Ado/Joiner.cs | 4 +- Simple.Data.Ado/Schema/ForeignKey.cs | 13 ++++- .../Schema/ForeignKeyCollection.cs | 13 +---- Simple.Data.Ado/Schema/Table.cs | 4 +- .../AmbiguousForeignKeyTest.cs | 48 +++++++++++++++++++ .../Simple.Data.BehaviourTest.csproj | 1 + 6 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 Simple.Data.BehaviourTest/AmbiguousForeignKeyTest.cs diff --git a/Simple.Data.Ado/Joiner.cs b/Simple.Data.Ado/Joiner.cs index 5476b7b8..244307dc 100644 --- a/Simple.Data.Ado/Joiner.cs +++ b/Simple.Data.Ado/Joiner.cs @@ -103,10 +103,10 @@ 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) 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/Table.cs b/Simple.Data.Ado/Schema/Table.cs index 64057ce7..89304174 100644 --- a/Simple.Data.Ado/Schema/Table.cs +++ b/Simple.Data.Ado/Schema/Table.cs @@ -145,7 +145,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 +164,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.BehaviourTest/AmbiguousForeignKeyTest.cs b/Simple.Data.BehaviourTest/AmbiguousForeignKeyTest.cs new file mode 100644 index 00000000..98b70c6e --- /dev/null +++ b/Simple.Data.BehaviourTest/AmbiguousForeignKeyTest.cs @@ -0,0 +1,48 @@ +namespace Simple.Data.IntegrationTest +{ + using Mocking.Ado; + using NUnit.Framework; + + [TestFixture] + public class AmbiguousForeignKeyTest : DatabaseIntegrationContext + { + protected override void SetSchema(MockSchemaProvider schemaProvider) + { + schemaProvider.SetTables(new[] { "dbo", "Fixture", "BASE TABLE" }, + new[] { "dbo", "Team", "BASE TABLE" }); + schemaProvider.SetColumns(new[] { "dbo", "Team", "Id" }, + new[] { "dbo", "Fixture", "Id" }, + new[] { "dbo", "Fixture", "HomeTeamId" }, + new[] { "dbo", "Fixture", "AwayTeamId" }, + new[] { "dbo", "Team", "Name" }); + schemaProvider.SetPrimaryKeys(new object[] { "dbo", "Team", "Id", 0 }, + new object[] { "dbo", "Team", "Id", 0 }); + schemaProvider.SetForeignKeys(new object[] { "FK_Fixture_HomeTeam", "dbo", "Fixture", "HomeTeamId", "dbo", "Team", "Id", 0 }, + new object[] { "FK_Fixture_AwayTeam", "dbo", "Fixture", "AwayTeamId", "dbo", "Team", "Id", 0 }); + } + + [Test] + public void CanSelectTwoWithUsingExplicitJoin() + { + const string expectedSql = + "select [dbo].[Fixture].[Id],[dbo].[Fixture].[HomeTeamId],[dbo].[Fixture].[AwayTeamId],[Home].[Id] AS [__withn__Home__Id],[Home].[Name] AS [__withn__Home__Name]," + + "[Away].[Id] AS [__withn__Away__Id],[Away].[Name] AS [__withn__Away__Name] from [dbo].[Fixture] " + + "JOIN [dbo].[Team] [Home] ON ([dbo].[Fixture].[HomeTeamId] = [Home].[Id]) " + + "JOIN [dbo].[Team] [Away] ON ([dbo].[Fixture].[AwayTeamId] = [Away].[Id])"; + + dynamic homeTeam; + dynamic awayTeam; + + var q = _db.Fixture.All() + .Join(_db.Team.As("Home"), out homeTeam) + .On(_db.Fixture.HomeTeamId == homeTeam.Id) + .Join(_db.Team.As("Away"), out awayTeam) + .On(_db.Fixture.AwayTeamId == awayTeam.Id) + .With(homeTeam) + .With(awayTeam); + + EatException(() => q.ToList()); + GeneratedSqlIs(expectedSql); + } + } +} diff --git a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj index d77b4151..2de891ec 100644 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj @@ -63,6 +63,7 @@ + From dcffe8625036887a32c9381138e48904c509e72f Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Tue, 28 Feb 2012 12:36:11 +0000 Subject: [PATCH 006/160] Added StopUsingMockAdapter method per issue #162 --- Simple.Data.BehaviourTest/App.config | 3 +++ .../DatabaseIntegrationContext.cs | 4 +-- .../Simple.Data.BehaviourTest.csproj | 1 + .../StopUsingMockTest.cs | 26 +++++++++++++++++++ .../Ado/MockConnectionProvider.cs | 11 +++++++- Simple.Data.Mocking/Ado/MockDbConnection.cs | 1 - .../Simple.Data.Mocking.csproj | 3 ++- Simple.Data/Database.cs | 5 ++++ Simple.Data/DatabaseOpener.cs | 5 ++++ Simple.Data/DatabaseOpenerMethods.cs | 8 ++++++ 10 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 Simple.Data.BehaviourTest/StopUsingMockTest.cs 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/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/Simple.Data.BehaviourTest.csproj b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj index 2de891ec..86d78241 100644 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj @@ -81,6 +81,7 @@ + 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.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/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/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/Database.cs b/Simple.Data/Database.cs index 0085f24d..12a0b7ce 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -201,6 +201,11 @@ public static void UseMockAdapter(Func mockAdapterCreator) Data.DatabaseOpener.UseMockAdapter(mockAdapterCreator()); } + public static void StopUsingMockAdapter() + { + Data.DatabaseOpener.StopUsingMock(); + } + private static TraceLevel? _traceLevel; public static TraceLevel TraceLevel { diff --git a/Simple.Data/DatabaseOpener.cs b/Simple.Data/DatabaseOpener.cs index d82fd5bc..edf2b1b1 100644 --- a/Simple.Data/DatabaseOpener.cs +++ b/Simple.Data/DatabaseOpener.cs @@ -97,5 +97,10 @@ 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..0d0f4057 100644 --- a/Simple.Data/DatabaseOpenerMethods.cs +++ b/Simple.Data/DatabaseOpenerMethods.cs @@ -72,5 +72,13 @@ public void UseMockAdapter(Func adapterCreator) _open = (ignore1, ignore2) => new Database(adapterCreator()); _openConnectionWithProvider = (ignore1, ignore2) => new Database(adapterCreator()); } + + public void StopUsingMock() + { + _openDefault = null; + _openFile = _openConnection = _openNamedConnection = null; + _open = null; + _openConnectionWithProvider = null; + } } } \ No newline at end of file From f73478b4f45e874855a16e374ddc005ab6cb8be6 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 29 Feb 2012 16:34:50 +0000 Subject: [PATCH 007/160] Added support for joined criteria in Updates where table has single-column primary key --- Simple.Data.Ado/UpdateHelper.cs | 36 +++++++++++++++++++++---- Simple.Data.BehaviourTest/UpdateTest.cs | 19 +++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index abebf4b8..1859b857 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,23 @@ public UpdateHelper(DatabaseSchema schema) public ICommandBuilder GetUpdateCommand(string tableName, IDictionary data, SimpleExpression criteria) { - _commandBuilder.Append(GetUpdateClause(tableName, data)); + var table = _schema.FindTable(tableName); + _commandBuilder.Append(GetUpdateClause(table, data)); if (criteria != null ) { - var whereStatement = _expressionFormatter.Format(criteria); + string whereStatement = null; + if (criteria.GetOperandsOfType().Any(o => !o.GetOwner().GetName().Equals(tableName))) + { + if (table.PrimaryKey.Length == 1) + { + whereStatement = CreateWhereInStatement(criteria, table); + } + } + else + { + whereStatement = _expressionFormatter.Format(criteria); + } if (!string.IsNullOrEmpty(whereStatement)) _commandBuilder.Append(" where " + whereStatement); } @@ -35,10 +48,23 @@ public ICommandBuilder GetUpdateCommand(string tableName, IDictionary> data) + 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 GetUpdateClause(Table table, IEnumerable> data) { - var table = _schema.FindTable(tableName); - var setClause = string.Join(", ", data.Where(kvp => table.HasColumn(kvp.Key)) .Select(kvp => CreateColumnUpdateClause(kvp.Key, kvp.Value, table))); diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 91e12fa9..f8859c06 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] @@ -11,19 +12,26 @@ public class UpdateTest : DatabaseIntegrationContext protected override void SetSchema(MockSchemaProvider schemaProvider) { schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, + new[] { "dbo", "UserHistory", "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 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", "USER_TABLE", "ID", 0 }); + + schemaProvider.SetForeignKeys(new object[] { "FK_UserHistory_User", "dbo", "UserHistory", "UserId", "dbo", "Users", "Id", 0 }); } [Test] @@ -302,6 +310,17 @@ 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 TestUpdateWithCriteriaAndDictionary() { From cda43bde14cce9e9f281fbb16a6b3969190de4ee Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 29 Feb 2012 17:19:28 +0000 Subject: [PATCH 008/160] Added support for joined criteria in Updates where table has multi-column primary key --- Simple.Data.Ado/UpdateHelper.cs | 27 +++++++++++++++++++++++ Simple.Data.BehaviourTest/UpdateTest.cs | 29 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index 1859b857..d63d5f20 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -36,6 +36,10 @@ public ICommandBuilder GetUpdateCommand(string tableName, IDictionary 1) + { + whereStatement = CreateWhereExistsStatement(criteria, table); + } } else { @@ -63,6 +67,29 @@ private string CreateWhereInStatement(SimpleExpression criteria, Table table) 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 setClause = string.Join(", ", diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index f8859c06..66d830d9 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -13,6 +13,8 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) { schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, new[] { "dbo", "UserHistory", "BASE TABLE"}, + new[] { "dbo", "AnnoyingMaster", "BASE TABLE"}, + new[] { "dbo", "AnnoyingDetail", "BASE TABLE"}, new[] {"dbo", "USER_TABLE", "BASE TABLE"}); schemaProvider.SetColumns(new object[] { "dbo", "Users", "Id", true }, @@ -22,6 +24,13 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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" }, @@ -29,9 +38,16 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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 }); + 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 } + ); } [Test] @@ -321,6 +337,17 @@ public void TestUpdateWithCriteriaWithNaturalJoin() 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() { From 83a51eda63bbbb5aad14babe0e09f1e3f9e8b621 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 2 Mar 2012 12:03:33 +0000 Subject: [PATCH 009/160] Complex update and With SQL tests --- Simple.Data.Ado/QueryBuilder.cs | 20 ++++++++++++ Simple.Data.BehaviourTest/Query/WithTest.cs | 34 +++++++++++++++++---- Simple.Data.BehaviourTest/UpdateTest.cs | 6 ++-- Simple.Data.SqlTest/QueryTest.cs | 26 +++++++++++++++- Simple.Data.SqlTest/UpdateTests.cs | 21 ++++++++++++- Simple.Data/WithClause.cs | 3 +- 6 files changed, 98 insertions(+), 12 deletions(-) diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index c95850df..a1c61f1e 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -114,6 +114,10 @@ private void HandleWithClauses() } else { + if (withClause.Type == WithType.NotSpecified) + { + InferWithType(withClause); + } HandleWithClauseUsingNaturalJoin(withClause, relationTypeDict); } } @@ -124,6 +128,22 @@ private void HandleWithClauses() } } + private void InferWithType(WithClause withClause) + { + var objectReference = withClause.ObjectReference; + while (!ReferenceEquals(objectReference.GetOwner(), null)) + { + var fromTable = _schema.FindTable(objectReference.GetName()); + var toTable = _schema.FindTable(objectReference.GetOwner().GetName()); + if (_schema.GetRelationType(fromTable.ActualName, toTable.ActualName) == RelationType.OneToMany) + { + withClause.Type = WithType.Many; + return; + } + objectReference = objectReference.GetOwner(); + } + } + private void HandleWithClauseUsingAssociatedJoinClause(Dictionary relationTypeDict, WithClause withClause) { var joinClause = diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index c29f0339..87048203 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -21,19 +21,22 @@ 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", "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 }); + 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 } + ); } [Test] @@ -113,6 +116,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() { diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 66d830d9..8cc250f9 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -11,9 +11,9 @@ public class UpdateTest : DatabaseIntegrationContext { protected override void SetSchema(MockSchemaProvider schemaProvider) { - schemaProvider.SetTables(new[] { "dbo", "Users", "BASE TABLE" }, - new[] { "dbo", "UserHistory", "BASE TABLE"}, - new[] { "dbo", "AnnoyingMaster", "BASE TABLE"}, + 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"}); diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 11fdb084..1ecac1ed 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -332,7 +332,31 @@ public void WithClauseShouldPreselectDetailTableAsCollection() } [Test] - public void WithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollection() + 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] + 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.FindAll(db.Customers.Order.OrderId == 1).WithOrders().FirstOrDefault() as IDictionary; diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index 87b9326c..c72fac89 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -87,6 +87,24 @@ 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 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 +115,8 @@ public void ToListShouldExecuteQuery() customer.Address = "Updated"; } - db.Customers.Update(customers); + Assert.DoesNotThrow(() => + db.Customers.Update(customers)); } } } \ 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 From bd983a312717fe24d06e79f766c4b05cfeb4b404 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 2 Mar 2012 13:00:57 +0000 Subject: [PATCH 010/160] Added Function support to InMemoryAdapter --- Simple.Data.Ado/DataReaderEnumerable.cs | 1 + Simple.Data.Ado/QueryBuilder.cs | 4 +- Simple.Data.InMemoryTest/InMemoryTests.cs | 69 +++++++++++++++ Simple.Data/InMemoryAdapter.cs | 103 ++++++++++++++++++++-- Simple.Data/SimpleQuery.cs | 4 +- 5 files changed, 172 insertions(+), 9 deletions(-) diff --git a/Simple.Data.Ado/DataReaderEnumerable.cs b/Simple.Data.Ado/DataReaderEnumerable.cs index 8755ebc5..9715396a 100644 --- a/Simple.Data.Ado/DataReaderEnumerable.cs +++ b/Simple.Data.Ado/DataReaderEnumerable.cs @@ -139,6 +139,7 @@ private void ExecuteReader() { try { + _command.WriteTrace(); _command.Connection.OpenIfClosed(); _reader = _command.ExecuteReader(); CreateIndexIfNecessary(); diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index a1c61f1e..ff6fc0bf 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -133,8 +133,8 @@ private void InferWithType(WithClause withClause) var objectReference = withClause.ObjectReference; while (!ReferenceEquals(objectReference.GetOwner(), null)) { - var fromTable = _schema.FindTable(objectReference.GetName()); - var toTable = _schema.FindTable(objectReference.GetOwner().GetName()); + 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; diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index ae925602..1db1aa15 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -285,6 +285,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 /// @@ -542,5 +559,57 @@ 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); + } + } } } diff --git a/Simple.Data/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 7ca7b541..e1479c82 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -6,7 +6,7 @@ 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(); @@ -14,7 +14,9 @@ public partial class InMemoryAdapter : Adapter private readonly Dictionary>> _tables = new Dictionary>>(); - private readonly ICollection _joins = new Collection(); + private readonly Dictionary _functions = new Dictionary(); + + private readonly ICollection _joins = new Collection(); private List> GetTable(string tableName) { @@ -208,12 +210,17 @@ 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);} + } + + internal class JoinInfo { private readonly string _masterTableName; private readonly string _masterKey; @@ -222,7 +229,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; @@ -282,5 +289,91 @@ 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, function); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, function); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, function); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, function); + } + + public void AddFunction(string functionName, Func function) + { + _functions.Add(functionName, function); + } + + public void AddDelegate(string functionName, Delegate function) + { + _functions.Add(functionName, function); + } + + public bool IsValidFunction(string functionName) + { + return _functions.ContainsKey(functionName); + } + + public IEnumerable>>> Execute(string functionName, IDictionary parameters) + { + if (!_functions.ContainsKey(functionName)) throw new InvalidOperationException("No function found with that name."); + var obj = _functions[functionName].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/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 144ddc51..f44a6cec 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -427,7 +427,7 @@ public SimpleQuery OuterJoin(ObjectReference objectReference, out dynamic queryO 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."); return AddNewJoin(new JoinClause(_tempJoinWaitingForOn.Table, _tempJoinWaitingForOn.JoinType, joinExpression)); } @@ -474,7 +474,7 @@ 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 joinExpression = ExpressionHelper.CriteriaDictionaryToExpression(_tempJoinWaitingForOn.Table, binder.NamedArgumentsToDictionary(args)); return AddNewJoin(new JoinClause(_tempJoinWaitingForOn.Table, _tempJoinWaitingForOn.JoinType, joinExpression)); From 0e882142ebb53d2a61174ff631957bb8a1e4cf6c Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 2 Mar 2012 13:16:07 +0000 Subject: [PATCH 011/160] 1.0.0-beta3 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data.SqlTest/ProcedureTest.cs | 2 ++ Simple.Data/Simple.Data.nuspec | 2 +- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 22a753ff..208264cf 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.2")] -[assembly: AssemblyFileVersion("1.0.0.2")] +[assembly: AssemblyVersion("1.0.0.3")] +[assembly: AssemblyFileVersion("1.0.0.3")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 0ee3ada5..b4c82774 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-beta2 + 1.0.0-beta3 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 4794e986..802d37fb 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-beta2 + 1.0.0-beta3 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 29e623d1..f58b9c50 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta2 + 1.0.0-beta3 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 998a8eb2..4b95b6f7 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-beta2 + 1.0.0-beta3 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.SqlTest/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index fa719258..5ef7642f 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -43,6 +43,7 @@ public void FindGetCustomerCountUsingIndexerAndInvokeTest() Assert.AreEqual(1, results.ReturnValue); } +#if DEBUG // Trace is only written for DEBUG build [Test] public void GetCustomerCountSecondCallExecutesNonQueryTest() { @@ -55,6 +56,7 @@ public void GetCustomerCountSecondCallExecutesNonQueryTest() Assert.IsTrue(listener.Output.Contains("ExecuteNonQuery")); Trace.Listeners.Remove(listener); } +#endif [Test] public void GetCustomerAndOrdersTest() diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index ac0f6e9c..aee80d52 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta2 + 1.0.0-beta3 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 1305aaeada34202dacbc5f89bfb6bbfcf71b42e7 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 15 Mar 2012 13:19:59 +0000 Subject: [PATCH 012/160] Fixes issue #172 (Count type problem) --- Simple.Data/SimpleQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index f44a6cec..24b84a8d 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -671,7 +671,7 @@ public T SingleOrDefault(Func predicate) public int Count() { - return (int)Select(new CountSpecialReference()).ToScalar(); + return Convert.ToInt32(Select(new CountSpecialReference()).ToScalar()); } /// From 3f10a595c36e7234fbd83d9d54628ec49c8d4f91 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 15 Mar 2012 17:54:34 +0000 Subject: [PATCH 013/160] Fix issue #171 (SqlBulkInserter does not use specified schema) --- Simple.Data.BehaviourTest/BulkInsertTest.cs | 18 +++++++- Simple.Data.SqlServer/SqlBulkInserter.cs | 2 +- Simple.Data.SqlTest/BulkInsertTest.cs | 46 +++++++++++++++++++ .../Simple.Data.SqlTest.csproj | 1 + 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 Simple.Data.SqlTest/BulkInsertTest.cs 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.SqlServer/SqlBulkInserter.cs b/Simple.Data.SqlServer/SqlBulkInserter.cs index a3dd5995..a5e234dd 100644 --- a/Simple.Data.SqlServer/SqlBulkInserter.cs +++ b/Simple.Data.SqlServer/SqlBulkInserter.cs @@ -37,7 +37,7 @@ public IEnumerable> Insert(AdoAdapter adapter, strin bulkCopy = new SqlBulkCopy(connection); } - bulkCopy.DestinationTableName = adapter.GetSchema().FindTable(tableName).ActualName; + bulkCopy.DestinationTableName = adapter.GetSchema().FindTable(tableName).QualifiedName; using (connection.MaybeDisposable()) using (bulkCopy) diff --git a/Simple.Data.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs new file mode 100644 index 00000000..a79e41b9 --- /dev/null +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -0,0 +1,46 @@ +namespace Simple.Data.SqlTest +{ + using System.Collections.Generic; + using NUnit.Framework; + + [TestFixture] + public class BulkInsertTest + { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + + [Test] + public void BulkInsertUsesSchema() + { + var db = DatabaseHelper.Open(); + db.test.SchemaTable.DeleteAll(); + db.test.SchemaTable.Insert(GenerateItems()); + + var list = db.test.SchemaTable.All().ToList(); + Assert.AreEqual(1000, list.Count); + } + + private static IEnumerable GenerateItems() + { + for (int i = 0; i < 1000; i++) + { + yield return new SchemaItem(i, i.ToString()); + } + } + } + + 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/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index c94a55c9..4c63cdab 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -62,6 +62,7 @@ + From df487712496c8a4c2327e97ca1789ec251d13113 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 15 Mar 2012 18:14:45 +0000 Subject: [PATCH 014/160] Fix problems with default schema resolution --- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 26 +++++++++++++----------- Simple.Data.Ado/Schema/DatabaseSchema.cs | 13 ++++++++---- Simple.Data.SqlTest/BulkInsertTest.cs | 2 ++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index c71ee76d..71e6d5dc 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Data; + using System.Diagnostics; using System.Linq; internal class AdoAdapterQueryRunner @@ -22,7 +23,7 @@ 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) { @@ -101,13 +102,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 +121,6 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 unhandledClausesForPagedQuery); unhandledClausesList.AddRange(unhandledClausesForPagedQuery); - SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); @@ -129,7 +129,8 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); if (queryPager == null) { - DeferPaging(query, mainCommandBuilder, commandBuilders, unhandledClausesList); + Trace.TraceWarning("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 { @@ -139,11 +140,12 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 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()); + query = query.ClearSkip().ClearTake(); var commandBuilder = new CommandBuilder(mainCommandBuilder.Text, _adapter.GetSchema(), mainCommandBuilder.Parameters); commandBuilders.Add(commandBuilder); @@ -172,23 +174,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,7 +208,7 @@ 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(); diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 63ecab56..e6687b60 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -14,6 +14,7 @@ public class DatabaseSchema private readonly ISchemaProvider _schemaProvider; private readonly Lazy _lazyTables; private readonly Lazy _lazyProcedures; + private string _defaultSchema; private DatabaseSchema(ISchemaProvider schemaProvider, ProviderHelper providerHelper) { @@ -45,6 +46,10 @@ public IEnumerable Tables public Table FindTable(string tableName) { + if (!string.IsNullOrWhiteSpace(DefaultSchema) && !(tableName.Contains("."))) + { + tableName = DefaultSchema + "." + tableName; + } return _lazyTables.Value.Find(tableName); } @@ -63,11 +68,11 @@ public Procedure FindProcedure(ObjectName procedureName) 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() @@ -110,7 +115,7 @@ 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."); return new ObjectName(schemaDotTable[0], schemaDotTable[1]); diff --git a/Simple.Data.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs index a79e41b9..55d4bc04 100644 --- a/Simple.Data.SqlTest/BulkInsertTest.cs +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -21,6 +21,8 @@ public void BulkInsertUsesSchema() var list = db.test.SchemaTable.All().ToList(); Assert.AreEqual(1000, list.Count); + + db.test.SchemaTable.DeleteAll(); } private static IEnumerable GenerateItems() From c86482a5a210769f978a21832799413a63cf68f4 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 16 Mar 2012 11:02:10 +0000 Subject: [PATCH 015/160] Fixed issue #168 (FindBy named parameters InMemoryAdapter) --- Simple.Data.InMemoryTest/InMemoryTests.cs | 12 ++++++++++++ Simple.Data/Commands/FindByCommand.cs | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index 1db1aa15..bcee0014 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -47,6 +47,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() { diff --git a/Simple.Data/Commands/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index 736a413b..b50fafbd 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -60,7 +60,9 @@ 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 criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), + CreateCriteriaDictionary(binder, + args)); var data = dataStrategy.FindOne(table.GetQualifiedName(), criteriaExpression); return data != null ? new SimpleRecord(data, table.GetQualifiedName(), dataStrategy) : null; } From c532dc4ce95402b66f1c1a22e99ef208506d680f Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 16 Mar 2012 11:26:06 +0000 Subject: [PATCH 016/160] Fixed issue #167 --- Simple.Data.Ado/ListExtensions.cs | 14 +++++++++++++- Simple.Data.SqlCe40Test/Northwind.sdf | Bin 1597440 -> 1597440 bytes Simple.Data.SqlCe40Test/NorthwindTests.cs | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) 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.SqlCe40Test/Northwind.sdf b/Simple.Data.SqlCe40Test/Northwind.sdf index a68bee7eb616786dc4083f5645eade9539f986ba..5b8ef0d3eeef69c6a3f627c37477ceaaa8861863 100644 GIT binary patch delta 148 zcmWlPD-Oay07R$DQa+_HrRC!Yko6paBOsCOO&StLpdi&)6s8K>DLgwh6EYa3v6V9^Zg7K>qUK_xF3{%sM=s0j?g;Wm?6Td~u){h(>IQ mR&0x**b%#8Pwb14I1poTD2_z`PsFj9ikUbOr{e6!`R)f$j3Lhe delta 148 zcmWlPJrcoC0EG9&6CuJz{B?#*j^GZN(Kv$CE})Z9Y^5=&H442-n-ZOCID^8o&G&sX zTh>Kc7k0IL1vmROSsDX5fHQ&6@$&@s2G~Aaxc&W7WnSv 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(); + } } } From 6e7b5dfda53a25ccc53d1e52443464e41c3d248b Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 16 Mar 2012 12:45:05 +0000 Subject: [PATCH 017/160] Fixing regressions and refuctoring :( --- .../AdoAdapter.IAdapterWithFunctions.cs | 2 +- .../AdoAdapter.IAdapterWithTransactions.cs | 26 ++++--- Simple.Data.Ado/AdoAdapter.cs | 2 +- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 36 ++++++++-- Simple.Data.Ado/AdoAdapterTransaction.cs | 2 +- Simple.Data.Ado/DataReaderEnumerable.cs | 10 +++ Simple.Data.Ado/DbCommandExtensions.cs | 10 +++ Simple.Data.SqlTest/BulkInsertTest.cs | 15 +++-- Simple.Data.SqlTest/FindTests.cs | 24 +------ Simple.Data.SqlTest/QueryTest.cs | 25 +------ Simple.Data.SqlTest/SchemaQualifiedTests.cs | 67 +++++++++++++++++++ .../Simple.Data.SqlTest.csproj | 1 + Simple.Data/Commands/FindByCommand.cs | 17 +++++ Simple.Data/DataStrategy.cs | 1 + Simple.Data/Database.cs | 5 ++ Simple.Data/IAdapterWithTransactions.cs | 1 + ...InMemoryAdapterIAdapterWithTransactions.cs | 5 ++ Simple.Data/SimpleQuery.cs | 4 +- Simple.Data/SimpleTransaction.cs | 5 ++ 19 files changed, 187 insertions(+), 71 deletions(-) create mode 100644 Simple.Data.SqlTest/SchemaQualifiedTests.cs diff --git a/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs index 1ee2299d..cc610dcf 100644 --- a/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs +++ b/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs @@ -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..16210389 100644 --- a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs +++ b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs @@ -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,7 +52,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 Update(string tableName, IDictionary data, IAdapterTransaction adapterTransaction) @@ -68,7 +68,7 @@ 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() @@ -93,20 +93,26 @@ public IAdapterTransaction BeginTransaction(string name) 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..5b2cb832 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -265,7 +265,7 @@ internal static int Execute(ICommandBuilder commandBuilder, IDbConnection connec internal static int Execute(ICommandBuilder commandBuilder, IAdapterTransaction transaction) { - IDbTransaction dbTransaction = ((AdoAdapterTransaction) transaction).Transaction; + IDbTransaction dbTransaction = ((AdoAdapterTransaction) transaction).DbTransaction; return Execute(commandBuilder, dbTransaction); } diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 71e6d5dc..3db82b14 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -9,10 +9,16 @@ internal class AdoAdapterQueryRunner { private readonly AdoAdapter _adapter; + private readonly AdoAdapterTransaction _transaction; - public AdoAdapterQueryRunner(AdoAdapter adapter) + public AdoAdapterQueryRunner(AdoAdapter adapter) : this(adapter, null) + { + } + + public AdoAdapterQueryRunner(AdoAdapter adapter, AdoAdapterTransaction transaction) { _adapter = adapter; + _transaction = transaction; } public IEnumerable> RunQuery(SimpleQuery query, @@ -27,11 +33,21 @@ out IEnumerable IDbConnection connection = _adapter.CreateConnection(); if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) { - result = + var command = CommandBuilder.CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders, - connection).ToEnumerable(_adapter.CreateConnection); + connection); + + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + result = command.ToEnumerable(_transaction.DbTransaction); + } + else + { + result = command.ToEnumerable(_adapter.CreateConnection); + } } else { @@ -211,11 +227,23 @@ public IEnumerable>> RunQueries(SimpleQu 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( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders.ToArray(), connection); + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + } foreach (var item in command.ToEnumerables(connection)) { yield return item.ToList(); 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/DataReaderEnumerable.cs b/Simple.Data.Ado/DataReaderEnumerable.cs index 9715396a..582ab84b 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 { diff --git a/Simple.Data.Ado/DbCommandExtensions.cs b/Simple.Data.Ado/DbCommandExtensions.cs index a39400f7..3192ff67 100644 --- a/Simple.Data.Ado/DbCommandExtensions.cs +++ b/Simple.Data.Ado/DbCommandExtensions.cs @@ -102,6 +102,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.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs index 55d4bc04..7bcde034 100644 --- a/Simple.Data.SqlTest/BulkInsertTest.cs +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -16,13 +16,18 @@ public void Setup() public void BulkInsertUsesSchema() { var db = DatabaseHelper.Open(); - db.test.SchemaTable.DeleteAll(); - db.test.SchemaTable.Insert(GenerateItems()); + List list; + Promise count; + using (var tx = db.BeginTransaction()) + { + tx.test.SchemaTable.DeleteAll(); + tx.test.SchemaTable.Insert(GenerateItems()); - var list = db.test.SchemaTable.All().ToList(); + list = tx.test.SchemaTable.All().WithTotalCount(out count).ToList(); + tx.Rollback(); + } + Assert.AreEqual(1000, count.Value); Assert.AreEqual(1000, list.Count); - - db.test.SchemaTable.DeleteAll(); } private static IEnumerable GenerateItems() diff --git a/Simple.Data.SqlTest/FindTests.cs b/Simple.Data.SqlTest/FindTests.cs index d5e6e4dc..aadc2baa 100644 --- a/Simple.Data.SqlTest/FindTests.cs +++ b/Simple.Data.SqlTest/FindTests.cs @@ -122,19 +122,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 +134,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() { diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 1ecac1ed..85e89eb6 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -295,30 +295,7 @@ public void ToScalarOrDefault() Assert.AreEqual(0, max); } - [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); - } - + [Test] public void WithClauseShouldPreselectDetailTableAsCollection() { 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/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index 4c63cdab..f31ca8af 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -67,6 +67,7 @@ + diff --git a/Simple.Data/Commands/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index b50fafbd..867f832f 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -6,6 +6,7 @@ namespace Simple.Data.Commands { + using System.Reflection; using Extensions; class FindByCommand : ICommand @@ -19,6 +20,16 @@ public Func CreateDelegate(DataStrategy dataStrategy, DynamicT { if (dataStrategy is SimpleTransaction) return null; + if (binder.Name.Equals("FindBy") || binder.Name.Equals("find_by")) + { + if (args.Length == 0) throw new ArgumentException("FindBy requires arguments."); + if (args.Length == 1) + { + if (ReferenceEquals(args[0], null)) throw new ArgumentException("FindBy does not accept unnamed null argument."); + if (args[0].GetType().Namespace == null) return null; + } + } + var criteriaDictionary = CreateCriteriaDictionary(binder, args); if (criteriaDictionary == null) return null; @@ -46,10 +57,16 @@ private static IDictionary CreateCriteriaDictionary(InvokeMember 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 { diff --git a/Simple.Data/DataStrategy.cs b/Simple.Data/DataStrategy.cs index 246da312..93af1d25 100644 --- a/Simple.Data/DataStrategy.cs +++ b/Simple.Data/DataStrategy.cs @@ -169,5 +169,6 @@ protected static Dictionary CreateChangedValuesDict(IEnumerable< 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); } } diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 12a0b7ce..6a44917c 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -142,6 +142,11 @@ 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)); diff --git a/Simple.Data/IAdapterWithTransactions.cs b/Simple.Data/IAdapterWithTransactions.cs index aa0c6df1..bc0fdb43 100644 --- a/Simple.Data/IAdapterWithTransactions.cs +++ b/Simple.Data/IAdapterWithTransactions.cs @@ -66,5 +66,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/InMemoryAdapterIAdapterWithTransactions.cs b/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs index eba83699..e846f10a 100644 --- a/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs +++ b/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs @@ -87,5 +87,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/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 24b84a8d..bd7c580f 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -234,7 +234,7 @@ public SimpleQuery ClearWithTotalCount() protected IEnumerable Run() { IEnumerable unhandledClauses; - var result = _adapter.RunQuery(this, out unhandledClauses); + var result = _dataStrategy.RunQuery(this, out unhandledClauses); if (unhandledClauses != null) { @@ -288,7 +288,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o return true; } } - 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; diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 7ea2db19..47815660 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -147,6 +147,11 @@ 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)); From be2bb27c5ef4801c7649ebd8b9ee3fd4d2a56fcb Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 16 Mar 2012 18:03:39 +0000 Subject: [PATCH 018/160] Just doing some cleaning up --- CommonAssemblyInfo.cs | 4 +- Simple.Data.Ado/AdoAdapter.cs | 3 - Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 +- .../Simple.Data.Mocking.nuspec | 4 +- .../Simple.Data.SqlCe40.nuspec | 4 +- .../Simple.Data.SqlServer.nuspec | 4 +- .../BufferedEnumerableTest.cs | 29 --- .../Simple.Data.UnitTest.csproj | 1 - Simple.Data/ActionDisposable.cs | 13 +- Simple.Data/Adapter.cs | 3 +- Simple.Data/BufferedEnumerable.cs | 18 -- Simple.Data/BufferedEnumerable1.cs | 205 ------------------ Simple.Data/Commands/FindByCommand.cs | 2 +- Simple.Data/Commands/InsertCommand.cs | 2 +- Simple.Data/Commands/UpdateByCommand.cs | 2 +- Simple.Data/DictionaryCloner.cs | 2 +- Simple.Data/FunctionSignature.cs | 2 +- Simple.Data/InMemoryAdapter.cs | 10 +- Simple.Data/MethodNameParser.cs | 2 +- Simple.Data/PropertySetterBuilder.cs | 3 +- .../QueryPolyfills/HavingClauseHandler.cs | 162 -------------- Simple.Data/Simple.Data.csproj | 3 - Simple.Data/Simple.Data.nuspec | 2 +- Simple.Data/SimpleFunction.cs | 2 +- Simple_Data.ndproj | 1 + 25 files changed, 38 insertions(+), 449 deletions(-) delete mode 100644 Simple.Data.UnitTest/BufferedEnumerableTest.cs delete mode 100644 Simple.Data/BufferedEnumerable.cs delete mode 100644 Simple.Data/BufferedEnumerable1.cs delete mode 100644 Simple.Data/QueryPolyfills/HavingClauseHandler.cs diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 208264cf..0a59657d 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.3")] -[assembly: AssemblyFileVersion("1.0.0.3")] +[assembly: AssemblyVersion("1.0.0.4")] +[assembly: AssemblyFileVersion("1.0.0.4")] diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index 5b2cb832..94472b98 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -3,14 +3,11 @@ using System.ComponentModel.Composition; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Linq; using Simple.Data.Ado.Schema; namespace Simple.Data.Ado { - using Extensions; - [Export("Ado", typeof (Adapter))] public partial class AdoAdapter : Adapter, ICloneable { diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index b4c82774..3e4b8b59 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-beta3 + 1.0.0-beta4 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 802d37fb..0b5393ae 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-beta3 + 1.0.0-beta4 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index f58b9c50..b1b42649 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta3 + 1.0.0-beta4 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 4b95b6f7..257db781 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-beta3 + 1.0.0-beta4 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.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/Simple.Data.UnitTest.csproj b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj index 801d8bbe..3e8bf196 100644 --- a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj +++ b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj @@ -71,7 +71,6 @@ - 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..b4e7fc75 100644 --- a/Simple.Data/Adapter.cs +++ b/Simple.Data/Adapter.cs @@ -150,7 +150,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 +194,6 @@ private void InsertManyWithoutReturn(string tableName, IEnumerable inserted; try { Insert(tableName, row, false); 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/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index 867f832f..92cf018c 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -52,7 +52,7 @@ 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")) diff --git a/Simple.Data/Commands/InsertCommand.cs b/Simple.Data/Commands/InsertCommand.cs index 7d709133..3677fcca 100644 --- a/Simple.Data/Commands/InsertCommand.cs +++ b/Simple.Data/Commands/InsertCommand.cs @@ -51,7 +51,7 @@ 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()); } diff --git a/Simple.Data/Commands/UpdateByCommand.cs b/Simple.Data/Commands/UpdateByCommand.cs index 10186331..7ceab93b 100644 --- a/Simple.Data/Commands/UpdateByCommand.cs +++ b/Simple.Data/Commands/UpdateByCommand.cs @@ -59,7 +59,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/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/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/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index e1479c82..cb3e86c1 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -80,7 +80,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); @@ -152,7 +152,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) { @@ -270,17 +270,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); } 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/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 68d0229a..e72179ae 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -174,7 +174,6 @@ private BinaryExpression CreateComplexAssign() private TryExpression CreateTrySimpleAssign() { - Expression assign; var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert", BindingFlags.Static | BindingFlags.NonPublic); @@ -195,7 +194,7 @@ private TryExpression CreateTrySimpleAssign() Expression.Constant(_property.PropertyType)); } - 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 { 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/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 8378c42b..fd4f2bd5 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -56,8 +56,6 @@ - - @@ -117,7 +115,6 @@ - diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index aee80d52..9acd68e6 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta3 + 1.0.0-beta4 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php 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.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 From 4fd06caadd1ce36fb10b0ea2d96822851b9e0f63 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Tue, 20 Mar 2012 15:59:35 +0000 Subject: [PATCH 019/160] Allow complex references as update values --- Simple.Data.Ado/DelegatingConnectionBase1.cs | 6 +-- Simple.Data.Ado/SimpleReferenceFormatter.cs | 12 +++-- Simple.Data.Ado/UpdateHelper.cs | 15 +++++- Simple.Data.BehaviourTest/FindTest.cs | 48 ++++++++++--------- Simple.Data.BehaviourTest/Query/WithTest.cs | 43 +++++++++++++++-- Simple.Data.BehaviourTest/UpdateTest.cs | 18 +++++-- Simple.Data.SqlTest/QueryTest.cs | 23 +++++++-- .../Resources/DatabaseReset.txt | 13 +++++ Simple.Data/AdapterException.cs | 5 -- 9 files changed, 136 insertions(+), 47 deletions(-) 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/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index 9226e41c..d2fb93c2 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -42,7 +42,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 +57,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) @@ -133,10 +135,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/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index d63d5f20..6d89c78a 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -101,7 +101,20 @@ private string GetUpdateClause(Table table, IEnumerable 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 +66,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 +74,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 +82,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,15 +90,16 @@ 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); } @@ -105,7 +107,7 @@ public void TestFindWithAdd() 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 +115,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 +123,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,7 +131,7 @@ 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"); } @@ -137,7 +139,7 @@ public void TestFindByNamedParameterSingleColumn() 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 +148,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 +162,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 +187,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 +195,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"); } @@ -233,7 +237,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"); } diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index 87048203..78e9f936 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,13 @@ 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", "Note", "BASE TABLE" }, new[] { "dbo", "Activity_Join", "BASE TABLE" }, new[] { "dbo", "Location", "BASE_TABLE" }); @@ -21,6 +24,14 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) new[] { "dbo", "Employee", "DepartmentId" }, new[] { "dbo", "Department", "Id" }, new[] { "dbo", "Department", "Name" }, + new[] { "dbo", "Customer", "Id" }, + new[] { "dbo", "Customer", "Name" }, + new[] { "dbo", "Order", "Id" }, + new[] { "dbo", "Order", "CustomerId" }, + new[] { "dbo", "Order", "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" }, @@ -30,13 +41,20 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) ); schemaProvider.SetPrimaryKeys(new object[] {"dbo", "Employee", "Id", 0}, - new object[] {"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", "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_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_Note_Customer", "dbo", "Note", "CustomerId", "dbo", "Customer", "Id", 0 } ); +// ReSharper restore CoVariantArrayConversion } [Test] @@ -195,6 +213,25 @@ 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 for issue #157 /// diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 8cc250f9..08b2f3b5 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -11,6 +11,7 @@ public class UpdateTest : DatabaseIntegrationContext { protected override void SetSchema(MockSchemaProvider schemaProvider) { +// ReSharper disable CoVariantArrayConversion schemaProvider.SetTables(new object[] { "dbo", "Users", "BASE TABLE" }, new object[] { "dbo", "UserHistory", "BASE TABLE"}, new object[] { "dbo", "AnnoyingMaster", "BASE TABLE"}, @@ -48,6 +49,7 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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] @@ -60,6 +62,14 @@ public void TestUpdateWithNamedArguments() 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() { @@ -187,7 +197,7 @@ public void TestUpdateWithStaticObjectAndOriginalObject() Name = "Steve", Age = 50 }; - var originalUser = new User() {Id = 2, Name = "Steve", Age = 50}; + var originalUser = new User {Id = 2, Name = "Steve", Age = 50}; _db.Users.Update(newUser, originalUser); GeneratedSqlIs( "update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); @@ -204,7 +214,7 @@ public void TestUpdateWithStaticObjectsAndOriginalObject() Name = "Steve", Age = 50 }; - var originalUser = new User() { Id = 2, Name = "Steve", Age = 50 }; + var originalUser = new User { Id = 2, Name = "Steve", Age = 50 }; _db.Users.Update(new[] {newUser}, new[] {originalUser}); GeneratedSqlIs( "update [dbo].[Users] set [Id] = @p1 where [dbo].[Users].[Id] = @p2"); @@ -255,7 +265,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( @@ -272,7 +282,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( diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 85e89eb6..58e01082 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using NUnit.Framework; namespace Simple.Data.SqlTest @@ -179,7 +178,9 @@ public void WithTotalCountWithExplicitSelectAndOrderByShouldGiveCount() } [Test] +// ReSharper disable InconsistentNaming public void WithTotalCountShouldGiveCount_ObsoleteFutureVersion() +// ReSharper restore InconsistentNaming { Future count; var db = DatabaseHelper.Open(); @@ -295,17 +296,33 @@ public void ToScalarOrDefault() Assert.AreEqual(0, max); } - + [Test] public void WithClauseShouldPreselectDetailTableAsCollection() { var db = DatabaseHelper.Open(); - var result = db.Customers.FindAllByCustomerId(1).WithOrders().FirstOrDefault() as IDictionary; + 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 WithClauseShouldPreselectDetailTablesAsCollections() + { + var db = DatabaseHelper.Open(); + 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] diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 01c05239..28b131a1 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -48,6 +48,8 @@ BEGIN 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')) @@ -141,6 +143,12 @@ BEGIN 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].[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) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int); @@ -203,6 +211,8 @@ BEGIN 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 @@ -239,6 +249,9 @@ BEGIN INSERT INTO [dbo].[DecimalTest] ([Value]) VALUES (1.234567) 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; diff --git a/Simple.Data/AdapterException.cs b/Simple.Data/AdapterException.cs index 6632f654..25e8cb72 100644 --- a/Simple.Data/AdapterException.cs +++ b/Simple.Data/AdapterException.cs @@ -1,13 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text; namespace Simple.Data { - using System.Security; - [Serializable] public abstract class AdapterException : Exception { From e8f513ae3eac3cf962c75bd5fa7b6ac606cd1940 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Tue, 20 Mar 2012 17:52:54 +0000 Subject: [PATCH 020/160] Mammoth refactoring and defer methods to Adapter --- PerformanceTestConsole/Program.cs | 2 +- .../AdoAdapter.IAdapterWithFunctions.cs | 2 +- Simple.Data.Ado/Schema/DatabaseSchema.cs | 5 + Simple.Data.Ado/Schema/ProcedureCollection.cs | 13 ++ Simple.Data/AdapterMethodDynamicInvoker.cs | 92 +++++++++++++ Simple.Data/Commands/DeleteAllCommand.cs | 4 +- Simple.Data/Commands/DeleteByCommand.cs | 2 +- Simple.Data/Commands/FindByCommand.cs | 2 +- Simple.Data/Commands/FindCommand.cs | 2 +- Simple.Data/Commands/GetCommand.cs | 2 +- Simple.Data/Commands/InsertCommand.cs | 10 +- Simple.Data/Commands/UpdateAllCommand.cs | 2 +- Simple.Data/Commands/UpdateByCommand.cs | 6 +- Simple.Data/Commands/UpdateCommand.cs | 8 +- Simple.Data/Commands/UpsertByCommand.cs | 6 +- Simple.Data/Commands/UpsertCommand.cs | 6 +- Simple.Data/DataStrategy.cs | 92 ++++--------- Simple.Data/Database.cs | 122 +++--------------- Simple.Data/DatabaseRunner.cs | 113 ++++++++++++++++ Simple.Data/DynamicTable.cs | 2 +- Simple.Data/RunStrategy.cs | 82 ++++++++++++ Simple.Data/Simple.Data.csproj | 4 + Simple.Data/SimpleQuery.cs | 2 +- Simple.Data/SimpleTransaction.cs | 108 ++++------------ Simple.Data/TransactionRunner.cs | 102 +++++++++++++++ 25 files changed, 508 insertions(+), 283 deletions(-) create mode 100644 Simple.Data/AdapterMethodDynamicInvoker.cs create mode 100644 Simple.Data/DatabaseRunner.cs create mode 100644 Simple.Data/RunStrategy.cs create mode 100644 Simple.Data/TransactionRunner.cs 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/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithFunctions.cs index cc610dcf..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) diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index e6687b60..cc43b9e0 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -129,6 +129,11 @@ 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 enum RelationType diff --git a/Simple.Data.Ado/Schema/ProcedureCollection.cs b/Simple.Data.Ado/Schema/ProcedureCollection.cs index 200a8bd7..303aeaac 100644 --- a/Simple.Data.Ado/Schema/ProcedureCollection.cs +++ b/Simple.Data.Ado/Schema/ProcedureCollection.cs @@ -45,6 +45,19 @@ public Procedure Find(string procedureName) return procedure; } + public bool IsProcedure(string procedureName) + { + if (procedureName.Contains('.')) + { + var schemaDotprocedure = procedureName.Split('.'); + if (schemaDotprocedure.Length != 2) throw new InvalidOperationException("Could not resolve qualified procedure name."); + return Find(schemaDotprocedure[1], schemaDotprocedure[0]) != null; + } + return (FindprocedureWithName(procedureName.Homogenize()) + ?? FindprocedureWithPluralName(procedureName.Homogenize()) + ?? FindprocedureWithSingularName(procedureName.Homogenize())) != null; + } + /// /// Finds the procedure with a name most closely matching the specified procedure name. /// This method will try an exact match first, then a case-insensitve search, then a pluralized or singular version. 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/Commands/DeleteAllCommand.cs b/Simple.Data/Commands/DeleteAllCommand.cs index f3ec86f5..dc14eb36 100644 --- a/Simple.Data/Commands/DeleteAllCommand.cs +++ b/Simple.Data/Commands/DeleteAllCommand.cs @@ -17,12 +17,12 @@ 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(); diff --git a/Simple.Data/Commands/DeleteByCommand.cs b/Simple.Data/Commands/DeleteByCommand.cs index d9a7056a..fa0249b9 100644 --- a/Simple.Data/Commands/DeleteByCommand.cs +++ b/Simple.Data/Commands/DeleteByCommand.cs @@ -16,7 +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); + return dataStrategy.Run.Delete(table.GetQualifiedName(), criteriaExpression); } public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) diff --git a/Simple.Data/Commands/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index 92cf018c..2f4ec1cd 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -80,7 +80,7 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), CreateCriteriaDictionary(binder, args)); - var data = dataStrategy.FindOne(table.GetQualifiedName(), criteriaExpression); + 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..06839bf9 100644 --- a/Simple.Data/Commands/FindCommand.cs +++ b/Simple.Data/Commands/FindCommand.cs @@ -32,7 +32,7 @@ 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; } diff --git a/Simple.Data/Commands/GetCommand.cs b/Simple.Data/Commands/GetCommand.cs index 8377d449..95cfbd4c 100644 --- a/Simple.Data/Commands/GetCommand.cs +++ b/Simple.Data/Commands/GetCommand.cs @@ -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) diff --git a/Simple.Data/Commands/InsertCommand.cs b/Simple.Data/Commands/InsertCommand.cs index 3677fcca..3710b2aa 100644 --- a/Simple.Data/Commands/InsertCommand.cs +++ b/Simple.Data/Commands/InsertCommand.cs @@ -53,18 +53,18 @@ private static object DoInsert(InvokeMemberBinder binder, object[] args, DataStr 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 +81,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/UpdateAllCommand.cs b/Simple.Data/Commands/UpdateAllCommand.cs index db029479..2edbd42f 100644 --- a/Simple.Data/Commands/UpdateAllCommand.cs +++ b/Simple.Data/Commands/UpdateAllCommand.cs @@ -28,7 +28,7 @@ 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(); } diff --git a/Simple.Data/Commands/UpdateByCommand.cs b/Simple.Data/Commands/UpdateByCommand.cs index 7ceab93b..d4a117d1 100644 --- a/Simple.Data/Commands/UpdateByCommand.cs +++ b/Simple.Data/Commands/UpdateByCommand.cs @@ -27,7 +27,7 @@ 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); + return dataStrategy.Run.Update(table.GetQualifiedName(), data, criteriaExpression); } public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMemberBinder binder, object[] args) @@ -44,12 +44,12 @@ internal static object UpdateByKeyFields(string tableName, DataStrategy dataStra { 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) diff --git a/Simple.Data/Commands/UpdateCommand.cs b/Simple.Data/Commands/UpdateCommand.cs index c641385c..01b91c0c 100644 --- a/Simple.Data/Commands/UpdateCommand.cs +++ b/Simple.Data/Commands/UpdateCommand.cs @@ -42,27 +42,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); + return dataStrategy.Run.Update(table.GetQualifiedName(), dict, criteria); } public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) diff --git a/Simple.Data/Commands/UpsertByCommand.cs b/Simple.Data/Commands/UpsertByCommand.cs index e1f52f60..1a9edd37 100644 --- a/Simple.Data/Commands/UpsertByCommand.cs +++ b/Simple.Data/Commands/UpsertByCommand.cs @@ -30,7 +30,7 @@ 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); @@ -50,12 +50,12 @@ internal static object UpsertByKeyFields(string tableName, DataStrategy dataStra { 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) diff --git a/Simple.Data/Commands/UpsertCommand.cs b/Simple.Data/Commands/UpsertCommand.cs index 53e93f82..82d93eaa 100644 --- a/Simple.Data/Commands/UpsertCommand.cs +++ b/Simple.Data/Commands/UpsertCommand.cs @@ -47,8 +47,8 @@ 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; @@ -56,7 +56,7 @@ private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable ta var key = dataStrategy.GetAdapter().GetKey(table.GetQualifiedName(), dict); var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), key); - return dataStrategy.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); + return dataStrategy.Run.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); } public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) diff --git a/Simple.Data/DataStrategy.cs b/Simple.Data/DataStrategy.cs index 93af1d25..c4f56de7 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,12 +50,14 @@ 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; @@ -59,9 +70,11 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o { if (this.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,99 +89,44 @@ 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"); _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); - internal bool IsExpressionFunction(string name, object[] args) { return GetAdapter().IsExpressionFunction(name, args); } - internal abstract int UpdateMany(string tableName, IList> dataList, IEnumerable criteriaFieldNames); - - internal abstract int UpdateMany(string tableName, IList> newValuesList, - IList> originalValuesList); - - public abstract int Update(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict); + internal abstract RunStrategy Run { get; } - protected static SimpleExpression CreateCriteriaFromOriginalValues(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) + object ICloneable.Clone() { - var criteriaValues = originalValuesDict - .Where(originalKvp => newValuesDict.ContainsKey(originalKvp.Key) && !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value))); - - return ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteriaValues); - } - - 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; + return Clone(); } - 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 internal abstract DataStrategy Clone(); } } diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 6a44917c..173d1b61 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text; @@ -8,19 +7,21 @@ namespace Simple.Data { using System.Configuration; + using System.Data; 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 readonly IDatabaseOpener DatabaseOpener; private static IPluralizer _pluralizer; private readonly Adapter _adapter; + private readonly DatabaseRunner _databaseRunner; static Database() { @@ -38,6 +39,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,105 +68,7 @@ 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> 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); - } + public SimpleTransaction BeginTransaction() { @@ -217,5 +127,15 @@ public static TraceLevel TraceLevel get { return _traceLevel ?? Configuration.TraceLevel; } set { _traceLevel = 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/DatabaseRunner.cs b/Simple.Data/DatabaseRunner.cs new file mode 100644 index 00000000..3da70c93 --- /dev/null +++ b/Simple.Data/DatabaseRunner.cs @@ -0,0 +1,113 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + + internal class DatabaseRunner : RunStrategy + { + private readonly Adapter _adapter; + public DatabaseRunner(Adapter adapter) + { + _adapter = 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); + 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/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 5a85fff7..a30d1e07 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -159,7 +159,7 @@ 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)); } } } diff --git a/Simple.Data/RunStrategy.cs b/Simple.Data/RunStrategy.cs new file mode 100644 index 00000000..4e1ea768 --- /dev/null +++ b/Simple.Data/RunStrategy.cs @@ -0,0 +1,82 @@ +namespace Simple.Data +{ + using System.Collections.Generic; + using System.Linq; + + internal abstract class RunStrategy + { + /// + /// 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 static SimpleExpression CreateCriteriaFromOriginalValues(string tableName, + IDictionary newValuesDict, + IDictionary + originalValuesDict) + { + var criteriaValues = originalValuesDict + .Where( + originalKvp => + newValuesDict.ContainsKey(originalKvp.Key) && + !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value))); + + 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 fd4f2bd5..86a27623 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -53,6 +53,7 @@ + @@ -81,6 +82,7 @@ + @@ -123,6 +125,7 @@ + @@ -173,6 +176,7 @@ + diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index bd7c580f..49193b05 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -234,7 +234,7 @@ public SimpleQuery ClearWithTotalCount() protected IEnumerable Run() { IEnumerable unhandledClauses; - var result = _dataStrategy.RunQuery(this, out unhandledClauses); + var result = _dataStrategy.Run.RunQuery(this, out unhandledClauses); if (unhandledClauses != null) { diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 47815660..3b48be4d 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; -using System.Linq; using System.Text; namespace Simple.Data @@ -17,6 +15,7 @@ public sealed class SimpleTransaction : DataStrategy, IDisposable private readonly Database _database; private readonly IAdapterWithTransactions _adapter; + private TransactionRunner _transactionRunner; private IAdapterTransaction _adapterTransaction; private SimpleTransaction(IAdapterWithTransactions adapter, Database database) @@ -27,14 +26,24 @@ private SimpleTransaction(IAdapterWithTransactions adapter, Database database) _database = database; } + private SimpleTransaction(SimpleTransaction copy) : base(copy) + { + _adapter = copy._adapter; + _database = copy._database; + _adapterTransaction = copy._adapterTransaction; + _transactionRunner = copy._transactionRunner; + } + private void Begin() { _adapterTransaction = _adapter.BeginTransaction(); + _transactionRunner = new TransactionRunner(_adapter, _adapterTransaction); } private void Begin(string name) { _adapterTransaction = _adapter.BeginTransaction(name); + _transactionRunner = new TransactionRunner(_adapter, _adapterTransaction); } internal static SimpleTransaction Begin(Database database) @@ -94,90 +103,7 @@ 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> 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; - } - - 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. @@ -208,5 +134,15 @@ protected internal override Database 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..f3189aff --- /dev/null +++ b/Simple.Data/TransactionRunner.cs @@ -0,0 +1,102 @@ +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; + } + + 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); + 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 From ec067535a2c320b09218c3d368fec465cb29ee30 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 28 Mar 2012 15:57:58 +0100 Subject: [PATCH 021/160] Copy collection properties to arrays when creating concrete type --- Simple.Data.Ado/AdoAdapterInserter.cs | 15 +- Simple.Data.Ado/BulkInserter.cs | 2 +- Simple.Data.Ado/Schema/Column.cs | 5 + Simple.Data.InMemoryTest/CanAssignArray.cs | 35 +++ .../Simple.Data.InMemoryTest.csproj | 4 +- Simple.Data.SqlServer/SqlColumn.cs | 5 + Simple.Data.SqlTest/InsertTests.cs | 9 + .../Resources/DatabaseReset.txt | 11 + Simple.Data/PropertySetterBuilder.cs | 199 +++++++++++++----- 9 files changed, 221 insertions(+), 64 deletions(-) create mode 100644 Simple.Data.InMemoryTest/CanAssignArray.cs diff --git a/Simple.Data.Ado/AdoAdapterInserter.cs b/Simple.Data.Ado/AdoAdapterInserter.cs index ad0f8658..f013da50 100644 --- a/Simple.Data.Ado/AdoAdapterInserter.cs +++ b/Simple.Data.Ado/AdoAdapterInserter.cs @@ -50,16 +50,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); } - 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 +83,8 @@ public IDictionary Insert(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/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.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/Simple.Data.InMemoryTest.csproj b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj index 8eb30a7c..70261ea1 100644 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj @@ -1,4 +1,4 @@ - + Debug @@ -56,7 +56,9 @@ + + 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.SqlTest/InsertTests.cs b/Simple.Data.SqlTest/InsertTests.cs index af3f4ba8..cc8ddd86 100644 --- a/Simple.Data.SqlTest/InsertTests.cs +++ b/Simple.Data.SqlTest/InsertTests.cs @@ -262,5 +262,14 @@ 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); + } } } diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 28b131a1..d00f0b0c 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -70,6 +70,8 @@ BEGIN 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] CREATE TABLE [dbo].[Users] ( [Id] INT IDENTITY (1, 1) NOT NULL, @@ -207,6 +209,15 @@ BEGIN [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 + )) + BEGIN TRANSACTION SET IDENTITY_INSERT [dbo].[Customers] ON INSERT INTO [dbo].[Customers] ([CustomerId], [Name], [Address]) VALUES (1, N'Test', N'100 Road') diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index e72179ae..c57e59d8 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -1,6 +1,7 @@ namespace Simple.Data { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -42,6 +43,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 +68,29 @@ 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; + + BlockExpression block; + var isDictionaryCollection = BuildCollectionPopulator(collection, genericType, addMethod, createCollection, + creatorInstance, out block); + + return Expression.IfThen(isDictionaryCollection, block); + } + private Expression BuildCollectionCreator() { var genericType = _property.PropertyType.GetGenericArguments().Single(); @@ -70,73 +99,98 @@ 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"); 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)), - Expression.Call(collection, addMethod, - Expression.Convert(Expression.Call(creator, CreatorCreateMethod, current), genericType)), - Expression.PreIncrementAssign(i) - ), - Expression.Break(label) - ), - label - ); - - var block = Expression.Block( - new[] { array, i, collection, current }, - createCollection, - toArray, - start, - loop, - _property.CanWrite ? (Expression)Expression.Assign(_nameProperty, collection) : Expression.Empty()); + BlockExpression block; + var isDictionaryCollection = BuildCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out block); return Expression.IfThen(isDictionaryCollection, block); } return null; } + private TypeBinaryExpression BuildCollectionPopulator(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(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)), + 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 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))); + } + 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() { return _property.PropertyType.IsPrimitive || _property.PropertyType == typeof(string) || @@ -163,8 +217,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( @@ -210,14 +266,51 @@ private TryExpression CreateTrySimpleAssign() CreateCatchBlock()); } + 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), + 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. private 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; + return Convert.ChangeType(source, targetType); + } + + 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 From 58bc30901d7a8e80799ad92097d8b55641c0fd20 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 28 Mar 2012 16:31:36 +0100 Subject: [PATCH 022/160] SQL Server doesn't try to write to TIMESTAMP columns --- Simple.Data.Ado/UpdateHelper.cs | 2 +- Simple.Data.BehaviourTest/UpdateTest.cs | 46 ++++++++++--------- .../Simple.Data.InMemoryTest.csproj | 1 - Simple.Data.SqlServer/DbTypeLookup.cs | 2 +- Simple.Data.SqlTest/UpdateTests.cs | 11 +++++ Simple.Data/DatabaseRunner.cs | 5 ++ Simple.Data/RunStrategy.cs | 16 +++++-- Simple.Data/TransactionRunner.cs | 5 ++ 8 files changed, 60 insertions(+), 28 deletions(-) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index 6d89c78a..8ddd28dc 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -93,7 +93,7 @@ private string CreateWhereExistsStatement(SimpleExpression criteria, Table table private string GetUpdateClause(Table table, IEnumerable> data) { 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); } diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 08b2f3b5..61b704f3 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -89,17 +89,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] @@ -107,17 +108,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] @@ -194,15 +196,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] @@ -211,15 +213,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] diff --git a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj index 70261ea1..217e3e7f 100644 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj @@ -58,7 +58,6 @@ - diff --git a/Simple.Data.SqlServer/DbTypeLookup.cs b/Simple.Data.SqlServer/DbTypeLookup.cs index 06e1c96a..69f12a7e 100644 --- a/Simple.Data.SqlServer/DbTypeLookup.cs +++ b/Simple.Data.SqlServer/DbTypeLookup.cs @@ -37,7 +37,7 @@ 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}, diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index c72fac89..e45dfafc 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -118,5 +118,16 @@ public void ToListShouldExecuteQuery() 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); + } } } \ No newline at end of file diff --git a/Simple.Data/DatabaseRunner.cs b/Simple.Data/DatabaseRunner.cs index 3da70c93..9e9b0ba3 100644 --- a/Simple.Data/DatabaseRunner.cs +++ b/Simple.Data/DatabaseRunner.cs @@ -10,6 +10,11 @@ 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); diff --git a/Simple.Data/RunStrategy.cs b/Simple.Data/RunStrategy.cs index 4e1ea768..dfaca998 100644 --- a/Simple.Data/RunStrategy.cs +++ b/Simple.Data/RunStrategy.cs @@ -5,6 +5,8 @@ namespace Simple.Data internal abstract class RunStrategy { + protected abstract Adapter Adapter { get; } + /// /// Finds data from the specified "table". /// @@ -65,16 +67,24 @@ protected static Dictionary CreateChangedValuesDict( return changedValuesDict; } - protected static SimpleExpression CreateCriteriaFromOriginalValues(string tableName, + protected SimpleExpression CreateCriteriaFromOriginalValues(string tableName, IDictionary newValuesDict, IDictionary originalValuesDict) { - var criteriaValues = originalValuesDict + var criteriaValues = Adapter.GetKey(tableName, originalValuesDict); + + foreach (var kvp in originalValuesDict .Where( originalKvp => newValuesDict.ContainsKey(originalKvp.Key) && - !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value))); + !(Equals(newValuesDict[originalKvp.Key], originalKvp.Value)))) + { + if (!criteriaValues.ContainsKey(kvp.Key)) + { + criteriaValues.Add(kvp); + } + }; return ExpressionHelper.CriteriaDictionaryToExpression(tableName, criteriaValues); } diff --git a/Simple.Data/TransactionRunner.cs b/Simple.Data/TransactionRunner.cs index f3189aff..7c3943fa 100644 --- a/Simple.Data/TransactionRunner.cs +++ b/Simple.Data/TransactionRunner.cs @@ -14,6 +14,11 @@ public TransactionRunner(IAdapterWithTransactions adapter, IAdapterTransaction a _adapterTransaction = adapterTransaction; } + protected override Adapter Adapter + { + get { return (Adapter) _adapter; } + } + internal override IDictionary FindOne(string tableName, SimpleExpression criteria) { return Find(tableName, criteria).FirstOrDefault(); From 7e0302923cbbb5d40de6f471c3eab1f9b440c8bd Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 28 Mar 2012 17:12:09 +0100 Subject: [PATCH 023/160] Added constructor to InMemoryAdapter to specify EqualityComparer for name resolution. Added AdoCompatibleComparer. --- .../NameResolutionTest.cs | 62 +++++++++++++++++++ .../Simple.Data.InMemoryTest.csproj | 1 + Simple.Data/AdoCompatibleComparer.cs | 22 +++++++ Simple.Data/InMemoryAdapter.cs | 22 +++++-- Simple.Data/Simple.Data.csproj | 1 + 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 Simple.Data.InMemoryTest/NameResolutionTest.cs create mode 100644 Simple.Data/AdoCompatibleComparer.cs 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 217e3e7f..18d770c6 100644 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj @@ -58,6 +58,7 @@ + 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/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index cb3e86c1..5e1b9c86 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -8,16 +8,27 @@ 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 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) { tableName = tableName.ToLowerInvariant(); @@ -67,6 +78,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); @@ -220,6 +232,8 @@ public JoinConfig Join get { return new JoinConfig(_joins);} } + private IEqualityComparer _nameComparer = EqualityComparer.Default; + internal class JoinInfo { private readonly string _masterTableName; diff --git a/Simple.Data/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 86a27623..5e9d68bd 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -55,6 +55,7 @@ + From b37894f62e6e16728a4c2a16c168241422f9a1c2 Mon Sep 17 00:00:00 2001 From: craiggwilson Date: Fri, 30 Mar 2012 08:10:16 -0500 Subject: [PATCH 024/160] fix for nested collections that are not complex types. --- Simple.Data.UnitTest/SimpleResultSetTest.cs | 23 ++++++ Simple.Data/PropertySetterBuilder.cs | 85 +++++++++++++++++---- 2 files changed, 94 insertions(+), 14 deletions(-) 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/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index c57e59d8..47e45b13 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -12,13 +12,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; @@ -84,11 +92,7 @@ private Expression BuildArrayCreator() if (addMethod == null) return null; - BlockExpression block; - var isDictionaryCollection = BuildCollectionPopulator(collection, genericType, addMethod, createCollection, - creatorInstance, out block); - - return Expression.IfThen(isDictionaryCollection, block); + return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod); } private Expression BuildCollectionCreator() @@ -110,15 +114,24 @@ private Expression BuildCollectionCreator() if (createCollection != null && addMethod != null) { - BlockExpression block; - var isDictionaryCollection = BuildCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out block); - - return Expression.IfThen(isDictionaryCollection, block); + return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod); } return null; } - private TypeBinaryExpression BuildCollectionPopulator(ParameterExpression collection, Type genericType, + 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, 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) { @@ -131,14 +144,14 @@ private TypeBinaryExpression BuildCollectionPopulator(ParameterExpression collec typeof (IEnumerable>)); var toArray = Expression.Assign(array, - Expression.Call(ToArrayMethod, + 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, ArrayLengthProperty)), + Expression.LessThan(i, Expression.Property(array, ArrayDictionaryLengthProperty)), Expression.Block( Expression.Assign(current, Expression.ArrayIndex(array, i)), Expression.Call(collection, addMethod, @@ -161,6 +174,48 @@ private TypeBinaryExpression BuildCollectionPopulator(ParameterExpression collec return isDictionaryCollection; } + private TypeBinaryExpression BuildSimpleTypeCollectionPopulator(ParameterExpression collection, Type genericType, + MethodInfo addMethod, BinaryExpression createCollection, + out BlockExpression block) + { + 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.Call(collection, addMethod, + Expression.Convert(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 isObjectCollection; + } + private BinaryExpression MakeCreateNewCollection(ParameterExpression collection, Type genericType) { BinaryExpression createCollection; @@ -283,6 +338,8 @@ private TryExpression CreateTrySimpleArrayAssign() assign), Expression.Catch(typeof(Exception), Expression.Empty())); } + + // ReSharper disable UnusedMember.Local // Because they're used from runtime-generated code, you see. private static object SafeConvert(object source, Type targetType) From cd19ca930f7f4ccd8cb59893006013584daf3001 Mon Sep 17 00:00:00 2001 From: Theo Spears Date: Mon, 9 Apr 2012 22:01:13 +0100 Subject: [PATCH 025/160] Test for doing upsert with only a single column It is possible to do an upsert specifying only columns. This fails under the AdoAdapter because its update assumes that at least one column to update will be specified. --- Simple.Data.SqlTest/UpsertTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Simple.Data.SqlTest/UpsertTests.cs b/Simple.Data.SqlTest/UpsertTests.cs index c7fbd316..c4bb65a7 100644 --- a/Simple.Data.SqlTest/UpsertTests.cs +++ b/Simple.Data.SqlTest/UpsertTests.cs @@ -401,5 +401,17 @@ 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.AreEqual("Ford Prefect", actual.Name); + } } } \ No newline at end of file From 9c71cc21239d8d0249d249fad62e39025dba5fc4 Mon Sep 17 00:00:00 2001 From: Jason Follas Date: Thu, 12 Apr 2012 11:56:19 -0400 Subject: [PATCH 026/160] UDT columns have type_name=NULL (DBNull). Bug in code tried to do this: (string) DBNull.Value --- Simple.Data.SqlServer/SqlSchemaProvider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simple.Data.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index c48174d6..05430e7f 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -43,7 +43,11 @@ public IEnumerable GetColumns(Table table) private static Column SchemaRowToColumn(Table table, DataRow row) { - var sqlDbType = row.IsNull("type_name") ? SqlDbType.Udt : 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) { From b9446ff0383e011c2b412b39395d3aba78f287af Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Mon, 16 Apr 2012 16:48:56 +0100 Subject: [PATCH 027/160] Added CountDistinct() method in queries --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/AdoAdapter.cs | 18 +++++++++++++---- Simple.Data.Ado/AdoAdapterUpserter.cs | 12 ++++++++--- Simple.Data.Ado/QueryBuilder.cs | 2 +- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Ado/SimpleReferenceFormatter.cs | 20 +++++++++++++------ Simple.Data.Ado/UpdateHelper.cs | 4 +++- .../Query/FunctionTest.cs | 10 ++++++++++ .../Simple.Data.Mocking.nuspec | 4 ++-- .../Simple.Data.SqlCe40.nuspec | 4 ++-- .../Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data.SqlTest/UpsertTests.cs | 2 +- Simple.Data/FunctionReference.cs | 19 +----------------- Simple.Data/Simple.Data.nuspec | 2 +- 14 files changed, 64 insertions(+), 45 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 0a59657d..f623c63b 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.4")] -[assembly: AssemblyFileVersion("1.0.0.4")] +[assembly: AssemblyVersion("0.16.0.0")] +[assembly: AssemblyFileVersion("0.16.0.0")] diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index 94472b98..a98a8fcc 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -165,10 +165,20 @@ 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); + } + + private static bool FunctionIsCount(string functionName, object[] args) + { + return (functionName.Equals("count", StringComparison.OrdinalIgnoreCase) && args.Length == 0); } public override IObservable> RunQueryAsObservable(SimpleQuery query, diff --git a/Simple.Data.Ado/AdoAdapterUpserter.cs b/Simple.Data.Ado/AdoAdapterUpserter.cs index d46bcdfa..4432ad0b 100644 --- a/Simple.Data.Ado/AdoAdapterUpserter.cs +++ b/Simple.Data.Ado/AdoAdapterUpserter.cs @@ -45,13 +45,19 @@ 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); diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index ff6fc0bf..6ca1806d 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -281,7 +281,7 @@ private void HandleHavingCriteria() private void HandleGrouping() { - if (_havingCriteria == SimpleExpression.Empty && !_columns.OfType().Any(fr => fr.IsAggregate)) return; + if (_havingCriteria == SimpleExpression.Empty && !_columns.OfType().Any(f => f.IsAggregate)) return; var groupColumns = GetColumnsToSelect(_table).Where(c => (!(c is FunctionReference)) || !((FunctionReference) c).IsAggregate).ToList(); diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 3e4b8b59..f1ead138 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-beta4 + 0.16.0.0 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 d2fb93c2..dc90b958 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -102,16 +102,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) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index 8ddd28dc..98b1b930 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -25,7 +25,9 @@ public UpdateHelper(DatabaseSchema schema) public ICommandBuilder GetUpdateCommand(string tableName, IDictionary data, SimpleExpression criteria) { var table = _schema.FindTable(tableName); - _commandBuilder.Append(GetUpdateClause(table, data)); + var updateClause = GetUpdateClause(table, data); + if (string.IsNullOrWhiteSpace(updateClause)) throw new InvalidOperationException("No columns to update."); + _commandBuilder.Append(updateClause); if (criteria != null ) { 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 0b5393ae..849411e3 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-beta4 + 0.16.0.0 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index b1b42649..95bc2ef5 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta4 + 0.16.0.0 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 257db781..5d9937b8 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-beta4 + 0.16.0.0 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.SqlTest/UpsertTests.cs b/Simple.Data.SqlTest/UpsertTests.cs index c4bb65a7..7fae97ba 100644 --- a/Simple.Data.SqlTest/UpsertTests.cs +++ b/Simple.Data.SqlTest/UpsertTests.cs @@ -411,7 +411,7 @@ public void TestUpsertWithSingleArgumentAndExistingObject() Assert.IsNotNull(actual); Assert.AreEqual(1, actual.Id); - Assert.AreEqual("Ford Prefect", actual.Name); + Assert.IsNotNull(actual.Name); } } } \ No newline at end of file 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 9acd68e6..776fbde6 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta4 + 0.16.0.0 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From f48575829a1e4df371a8ed8f65da3c9ea20a6047 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Mon, 16 Apr 2012 17:32:53 +0100 Subject: [PATCH 028/160] Fix for issue #181 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data/PropertySetterBuilder.cs | 2 +- Simple.Data/TypeExtensions.cs | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index f623c63b..993fea8e 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("0.16.0.0")] -[assembly: AssemblyFileVersion("0.16.0.0")] +[assembly: AssemblyVersion("0.16.1.0")] +[assembly: AssemblyFileVersion("0.16.1.0")] diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 47e45b13..cf1777ad 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -110,7 +110,7 @@ private Expression BuildCollectionCreator() createCollection = Expression.Assign(collection, _nameProperty); } - var addMethod = _property.PropertyType.GetMethod("Add"); + var addMethod = _property.PropertyType.GetInterfaceMethod("Add"); if (createCollection != null && addMethod != null) { 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 From 75a4ba31b4e39fae67ea8f33913e45f2cca26e90 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Mon, 16 Apr 2012 17:54:33 +0100 Subject: [PATCH 029/160] 1.0.0-beta6 --- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index f1ead138..1aed6904 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.16.0.0 + 1.0.0-beta6 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 849411e3..c37f8f15 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.16.0.0 + 1.0.0-beta6 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 95bc2ef5..485fa602 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 0.16.0.0 + 1.0.0-beta6 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 5d9937b8..28b1e1cc 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 0.16.0.0 + 1.0.0-beta6 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 776fbde6..ac8fb1d8 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 0.16.0.0 + 1.0.0-beta6 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 478cc90ebd159306b4f085807c6d0c8db7bfc3c7 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 9 May 2012 21:23:12 +0100 Subject: [PATCH 030/160] Fix for issue #188 --- Simple.Data.SqlTest/GetTests.cs | 8 ++++++++ Simple.Data.SqlTest/Simple.Data.SqlTest.csproj | 1 + Simple.Data/ObjectReference.cs | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Simple.Data.SqlTest/GetTests.cs b/Simple.Data.SqlTest/GetTests.cs index a4b2de43..ab48f5c0 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/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index f31ca8af..775f402f 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -62,6 +62,7 @@ + diff --git a/Simple.Data/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 5dd5cada..2c00914a 100644 --- a/Simple.Data/ObjectReference.cs +++ b/Simple.Data/ObjectReference.cs @@ -103,7 +103,6 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o if (_dataStrategy != null) { - // Probably a table... var table = new DynamicTable(_name, _dataStrategy); if (table.TryInvokeMember(binder, args, out result)) { @@ -128,7 +127,8 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o { var schema = dataStrategy.SetMemberAsSchema(_owner); var table = schema.GetTable(_name); - result = command.Execute(dataStrategy, table, binder, args); + table.TryInvokeMember(binder, args, out result); + //result = command.Execute(dataStrategy, table, binder, args); } else { From 782ea4c9e1a5d746f3236f4ca3072b6ba789af20 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 9 May 2012 22:04:39 +0100 Subject: [PATCH 031/160] Fix for issue #186 --- Simple.Data.BehaviourTest/FindTest.cs | 20 ++++++++ Simple.Data.SqlTest/GetTests.cs | 14 +++--- .../Simple.Data.SqlTest.csproj | 1 - Simple.Data/Commands/AllCommand.cs | 10 ---- Simple.Data/Commands/ArgumentHelper.cs | 50 +++++++++++++++++++ Simple.Data/Commands/DeleteAllCommand.cs | 10 ---- Simple.Data/Commands/DeleteByCommand.cs | 10 ---- Simple.Data/Commands/ExistsByCommand.cs | 10 ---- Simple.Data/Commands/ExistsCommand.cs | 10 ---- Simple.Data/Commands/FindAllByCommand.cs | 21 ++------ Simple.Data/Commands/FindAllCommand.cs | 10 ---- Simple.Data/Commands/FindByCommand.cs | 12 ++--- Simple.Data/Commands/FindCommand.cs | 7 +-- Simple.Data/Commands/GetCommand.cs | 2 +- Simple.Data/Commands/GetCountByCommand.cs | 10 ---- Simple.Data/Commands/GetCountCommand.cs | 10 ---- Simple.Data/Commands/ICommand.cs | 8 +++ Simple.Data/Commands/InsertCommand.cs | 10 ---- Simple.Data/Commands/QueryByCommand.cs | 33 ------------ Simple.Data/Commands/QueryCommand.cs | 28 +++++++++++ Simple.Data/Commands/UpdateAllCommand.cs | 10 ---- Simple.Data/Commands/UpdateByCommand.cs | 10 ---- Simple.Data/Commands/UpdateCommand.cs | 10 ---- Simple.Data/Commands/UpsertByCommand.cs | 10 ---- Simple.Data/Commands/UpsertCommand.cs | 10 ---- Simple.Data/DynamicTable.cs | 6 +-- Simple.Data/Extensions/ObjectEx.cs | 5 ++ Simple.Data/Simple.Data.csproj | 2 + Simple.Data/SimpleQuery.cs | 3 +- 29 files changed, 136 insertions(+), 216 deletions(-) create mode 100644 Simple.Data/Commands/ArgumentHelper.cs create mode 100644 Simple.Data/Commands/QueryCommand.cs diff --git a/Simple.Data.BehaviourTest/FindTest.cs b/Simple.Data.BehaviourTest/FindTest.cs index 1811b40f..408c1546 100644 --- a/Simple.Data.BehaviourTest/FindTest.cs +++ b/Simple.Data.BehaviourTest/FindTest.cs @@ -1,5 +1,6 @@ namespace Simple.Data.IntegrationTest { + using System; using System.Collections.Generic; using Mocking.Ado; using NUnit.Framework; @@ -223,6 +224,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() { @@ -249,6 +257,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.SqlTest/GetTests.cs b/Simple.Data.SqlTest/GetTests.cs index ab48f5c0..80df8238 100644 --- a/Simple.Data.SqlTest/GetTests.cs +++ b/Simple.Data.SqlTest/GetTests.cs @@ -22,13 +22,13 @@ 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 GetWithNonExistentPrimaryKeyShouldReturnNull() + { + var db = DatabaseHelper.Open(); + var user = db.Users.Get(1138); + Assert.IsNull(user); + } [Test] public void SelectClauseWithGetScalarShouldLimitQuery() diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index 775f402f..f31ca8af 100644 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj @@ -62,7 +62,6 @@ - diff --git a/Simple.Data/Commands/AllCommand.cs b/Simple.Data/Commands/AllCommand.cs index 9c8a591d..8c945fc9 100644 --- a/Simple.Data/Commands/AllCommand.cs +++ b/Simple.Data/Commands/AllCommand.cs @@ -34,15 +34,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe //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 dc14eb36..856c4a2f 100644 --- a/Simple.Data/Commands/DeleteAllCommand.cs +++ b/Simple.Data/Commands/DeleteAllCommand.cs @@ -27,15 +27,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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 fa0249b9..2167c5bc 100644 --- a/Simple.Data/Commands/DeleteByCommand.cs +++ b/Simple.Data/Commands/DeleteByCommand.cs @@ -19,16 +19,6 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return dataStrategy.Run.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(); - } - private static SimpleExpression GetCriteriaExpression(InvokeMemberBinder binder, object[] args, DynamicTable table) { IDictionary criteria; diff --git a/Simple.Data/Commands/ExistsByCommand.cs b/Simple.Data/Commands/ExistsByCommand.cs index edfeb681..faade45b 100644 --- a/Simple.Data/Commands/ExistsByCommand.cs +++ b/Simple.Data/Commands/ExistsByCommand.cs @@ -33,15 +33,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).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/ExistsCommand.cs b/Simple.Data/Commands/ExistsCommand.cs index f180c4c1..e5cad65a 100644 --- a/Simple.Data/Commands/ExistsCommand.cs +++ b/Simple.Data/Commands/ExistsCommand.cs @@ -36,15 +36,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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..f33f4f49 100644 --- a/Simple.Data/Commands/FindAllCommand.cs +++ b/Simple.Data/Commands/FindAllCommand.cs @@ -37,15 +37,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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(); - } } } diff --git a/Simple.Data/Commands/FindByCommand.cs b/Simple.Data/Commands/FindByCommand.cs index 2f4ec1cd..04bbdfd6 100644 --- a/Simple.Data/Commands/FindByCommand.cs +++ b/Simple.Data/Commands/FindByCommand.cs @@ -9,7 +9,7 @@ namespace Simple.Data.Commands using System.Reflection; using Extensions; - class FindByCommand : ICommand + class FindByCommand : ICommand, ICreateDelegate, IQueryCompatibleCommand { public bool IsCommandFor(string method) { @@ -22,15 +22,11 @@ public Func CreateDelegate(DataStrategy dataStrategy, DynamicT if (binder.Name.Equals("FindBy") || binder.Name.Equals("find_by")) { - if (args.Length == 0) throw new ArgumentException("FindBy requires arguments."); - if (args.Length == 1) - { - if (ReferenceEquals(args[0], null)) throw new ArgumentException("FindBy does not accept unnamed null argument."); - if (args[0].GetType().Namespace == null) return null; - } + ArgumentHelper.CheckFindArgs(args, binder); + if (args.Length == 1 && args[0].IsAnonymous()) return null; } - var criteriaDictionary = CreateCriteriaDictionary(binder, args); + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "FindBy", "find_by"); if (criteriaDictionary == null) return null; var criteriaExpression = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), criteriaDictionary); diff --git a/Simple.Data/Commands/FindCommand.cs b/Simple.Data/Commands/FindCommand.cs index 06839bf9..935c6640 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. @@ -43,10 +43,5 @@ public object Execute(DataStrategy dataStrategy, SimpleQuery query, InvokeMember { 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 95cfbd4c..9a342030 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) { diff --git a/Simple.Data/Commands/GetCountByCommand.cs b/Simple.Data/Commands/GetCountByCommand.cs index 248258d7..6ed8b233 100644 --- a/Simple.Data/Commands/GetCountByCommand.cs +++ b/Simple.Data/Commands/GetCountByCommand.cs @@ -30,15 +30,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), MethodNameParser.ParseFromBinder(binder, args)); return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).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(); - } } } \ No newline at end of file diff --git a/Simple.Data/Commands/GetCountCommand.cs b/Simple.Data/Commands/GetCountCommand.cs index 5c9ca9a8..5f88ad9d 100644 --- a/Simple.Data/Commands/GetCountCommand.cs +++ b/Simple.Data/Commands/GetCountCommand.cs @@ -39,15 +39,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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(); - } } } diff --git a/Simple.Data/Commands/ICommand.cs b/Simple.Data/Commands/ICommand.cs index 46175c23..8ad3d20d 100644 --- a/Simple.Data/Commands/ICommand.cs +++ b/Simple.Data/Commands/ICommand.cs @@ -27,8 +27,16 @@ interface ICommand /// 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 3710b2aa..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()) 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 2edbd42f..e485743f 100644 --- a/Simple.Data/Commands/UpdateAllCommand.cs +++ b/Simple.Data/Commands/UpdateAllCommand.cs @@ -32,15 +32,5 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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 d4a117d1..3949e839 100644 --- a/Simple.Data/Commands/UpdateByCommand.cs +++ b/Simple.Data/Commands/UpdateByCommand.cs @@ -30,16 +30,6 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return dataStrategy.Run.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(); - } - internal static object UpdateByKeyFields(string tableName, DataStrategy dataStrategy, object entity, IEnumerable keyFieldNames) { var record = UpdateCommand.ObjectToDictionary(entity); diff --git a/Simple.Data/Commands/UpdateCommand.cs b/Simple.Data/Commands/UpdateCommand.cs index 01b91c0c..6adaf6c4 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]); @@ -65,11 +60,6 @@ private static object UpdateUsingKeys(DataStrategy dataStrategy, DynamicTable ta return dataStrategy.Run.Update(table.GetQualifiedName(), dict, criteria); } - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - internal static object ObjectToDictionary(object obj) { var dynamicRecord = obj as SimpleRecord; diff --git a/Simple.Data/Commands/UpsertByCommand.cs b/Simple.Data/Commands/UpsertByCommand.cs index 1a9edd37..58340d4b 100644 --- a/Simple.Data/Commands/UpsertByCommand.cs +++ b/Simple.Data/Commands/UpsertByCommand.cs @@ -36,16 +36,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(); - } - internal static object UpsertByKeyFields(string tableName, DataStrategy dataStrategy, object entity, IEnumerable keyFieldNames, bool isResultRequired, ErrorCallback errorCallback) { var record = UpdateCommand.ObjectToDictionary(entity); diff --git a/Simple.Data/Commands/UpsertCommand.cs b/Simple.Data/Commands/UpsertCommand.cs index 82d93eaa..42152974 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]); @@ -59,11 +54,6 @@ private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable ta return dataStrategy.Run.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); } - public Func CreateDelegate(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args) - { - throw new NotImplementedException(); - } - internal static object ObjectToDictionary(object obj) { var dynamicRecord = obj as SimpleRecord; diff --git a/Simple.Data/DynamicTable.cs b/Simple.Data/DynamicTable.cs index a30d1e07..6aa07e22 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -107,9 +107,9 @@ private Func CreateMemberDelegate(string signature, InvokeMembe { 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) { diff --git a/Simple.Data/Extensions/ObjectEx.cs b/Simple.Data/Extensions/ObjectEx.cs index 06f7a739..522ec539 100644 --- a/Simple.Data/Extensions/ObjectEx.cs +++ b/Simple.Data/Extensions/ObjectEx.cs @@ -50,5 +50,10 @@ 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) + { + return obj.GetType().Namespace == null; + } } } diff --git a/Simple.Data/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 5e9d68bd..bb2098f3 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -61,11 +61,13 @@ + + diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 49193b05..59326304 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; @@ -299,7 +300,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 From d2c6796d77c289b54fa1c027f249a53f4bf349df Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Wed, 9 May 2012 22:38:43 +0100 Subject: [PATCH 032/160] Fix for issue #178 --- Simple.Data.Ado/Schema/DatabaseSchema.cs | 12 ++++++++++++ Simple.Data.Ado/UpdateHelper.cs | 8 +++++++- Simple.Data.SqlServer/SqlSchemaProvider.cs | 7 +++---- Simple.Data.SqlTest/UpdateTests.cs | 9 +++++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index cc43b9e0..892f0a1e 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -44,6 +44,18 @@ 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("."))) diff --git a/Simple.Data.Ado/UpdateHelper.cs b/Simple.Data.Ado/UpdateHelper.cs index 98b1b930..836afe9b 100644 --- a/Simple.Data.Ado/UpdateHelper.cs +++ b/Simple.Data.Ado/UpdateHelper.cs @@ -32,7 +32,7 @@ public ICommandBuilder GetUpdateCommand(string tableName, IDictionary().Any(o => !o.GetOwner().GetName().Equals(tableName))) + if (criteria.GetOperandsOfType().Any(o => IsTableChain(tableName, o))) { if (table.PrimaryKey.Length == 1) { @@ -54,6 +54,12 @@ public ICommandBuilder GetUpdateCommand(string tableName, IDictionary row["TABLE_NAME"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)) - .CopyToDataTable(); + var primaryKeys = GetPrimaryKeys(); + var dataTable = primaryKeys.AsEnumerable().Where(row => row["TABLE_NAME"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)).CopyToDataTable(); + return dataTable; } private EnumerableRowCollection GetForeignKeys(string tableName) diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index e45dfafc..b4245e66 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -96,6 +96,15 @@ public void TestUpdateWithJoinCriteria() 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() { From 09e979fe5dd9f9654b686ed8590041dd1c3e7de9 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 11 May 2012 11:14:42 +0100 Subject: [PATCH 033/160] Added out parameter on With methods to fix #184 --- Simple.Data.BehaviourTest/Query/WithTest.cs | 23 +++++++++++++++++++++ Simple.Data/SimpleQuery.cs | 19 +++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index 78e9f936..eb672e95 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -249,5 +249,28 @@ public void CriteriaReferencesShouldNotBeDuplicatedInSql() GeneratedSqlIs(expectedSql); } + + /// + /// Test for issue #184 + /// + [Test] + public void CriteriaReferencesShouldUseWithAlias() + { + 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); + } } } \ No newline at end of file diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 59326304..0bf3039c 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -327,11 +327,22 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o 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) From 19d5d78b496771fdf18d75f7a4831ce5b394fb0f Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 11 May 2012 11:37:49 +0100 Subject: [PATCH 034/160] Fix for issue #184 --- Simple.Data.Ado/ObjectName.cs | 4 ++-- Simple.Data.Ado/QueryBuilder.cs | 14 ++++++++++- Simple.Data.BehaviourTest/Query/WithTest.cs | 26 ++++++++++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) 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/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index 6ca1806d..a9abb08b 100644 --- a/Simple.Data.Ado/QueryBuilder.cs +++ b/Simple.Data.Ado/QueryBuilder.cs @@ -223,7 +223,7 @@ private void HandleJoins() 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) + ? GetJoinClausesFromColumnList(joinClauses, joiner) : Enumerable.Empty(); var joinList = fromTable.Concat(fromJoins).Concat(fromCriteria).Concat(fromHavingCriteria).Concat(fromColumnList).Select(s => s.Trim()).Distinct().ToList(); @@ -246,6 +246,18 @@ private void HandleJoins() } } + private IEnumerable GetJoinClausesFromColumnList(IEnumerable joinClauses, Joiner joiner) + { + return joiner.GetJoinClauses(_tableName, GetObjectReferences(_columns) + .Where(o => !joinClauses.Any(j => ObjectReferenceIsInJoinClause(j, o))), JoinType.Outer); + + } + + private static bool ObjectReferenceIsInJoinClause(JoinClause clause, ObjectReference reference) + { + return reference.GetOwner().GetAliasOrName().Equals(clause.Table.GetAliasOrName()); + } + private IEnumerable GetObjectReferences(IEnumerable source) { var list = source.ToList(); diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index eb672e95..28a85064 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -254,7 +254,7 @@ public void CriteriaReferencesShouldNotBeDuplicatedInSql() /// Test for issue #184 /// [Test] - public void CriteriaReferencesShouldUseWithAlias() + public void CriteriaReferencesShouldUseWithAliasOutValue() { const string expectedSql = "select [dbo].[employee].[id],[dbo].[employee].[name]," + "[dbo].[employee].[managerid],[dbo].[employee].[departmentid]," + @@ -272,5 +272,29 @@ public void CriteriaReferencesShouldUseWithAlias() 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 From efd1b347f159fbb154d676345705af167654d296 Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 13 May 2012 14:43:29 +0100 Subject: [PATCH 035/160] Fixes issue #182, hydrating Nullable Enums --- PerformanceTestConsole/app.config | 18 +++++++++ .../ConcreteTypeCreatorTest.cs | 22 +++++++++++ .../PropertySetterBuilderTest.cs | 39 +++++++++++++++++++ .../Simple.Data.UnitTest.csproj | 1 + Simple.Data/PropertySetterBuilder.cs | 23 ++++++++--- 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 PerformanceTestConsole/app.config create mode 100644 Simple.Data.UnitTest/PropertySetterBuilderTest.cs 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/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/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 3e8bf196..64e34418 100644 --- a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj +++ b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj @@ -93,6 +93,7 @@ + diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index cf1777ad..44179890 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -285,22 +285,26 @@ private BinaryExpression CreateComplexAssign() private TryExpression CreateTrySimpleAssign() { - 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())); } 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)); } @@ -342,13 +346,20 @@ private TryExpression CreateTrySimpleArrayAssign() // ReSharper disable UnusedMember.Local // Because they're used from runtime-generated code, you see. - private static object SafeConvert(object source, Type targetType) + internal static object SafeConvert(object source, Type targetType) { if (ReferenceEquals(source, null)) return null; if (targetType.IsInstanceOfType(source)) return source; 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; From 6a703b249a88f09956c0bc936d1bdbe0042506ae Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 20 May 2012 00:10:06 +0100 Subject: [PATCH 036/160] Fix for issue #190 --- .../SchemaQualifiedTableTest.cs | 17 +++++++++++++++-- Simple.Data.SqlServer/SqlSchemaProvider.cs | 14 +++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) 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.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index f95989dc..d7061fe4 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -156,12 +156,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() @@ -188,13 +187,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); } From 5321b74afcad338b27289d832bf42a0080b0704b Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 20 May 2012 00:29:21 +0100 Subject: [PATCH 037/160] Fix for issue #189 --- Simple.Data.Ado/EagerLoadingEnumerable.cs | 1 + Simple.Data.SqlTest/QueryTest.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Simple.Data.Ado/EagerLoadingEnumerable.cs b/Simple.Data.Ado/EagerLoadingEnumerable.cs index 3b48483d..3beca4bd 100644 --- a/Simple.Data.Ado/EagerLoadingEnumerable.cs +++ b/Simple.Data.Ado/EagerLoadingEnumerable.cs @@ -107,6 +107,7 @@ private class WithContainer public void AddToCollection(IDictionary row) { + if (row.All(kvp => ReferenceEquals(null, kvp.Value))) return; if (Collection == null) Collection = new HashSet>(new DictionaryEqualityComparer()); Collection.Add(row); } diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 58e01082..7f330efe 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -392,6 +392,17 @@ 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 SelfJoinShouldNotThrowException() From 46968ff00ab51ba1ef56a429796e923db27022e8 Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 20 May 2012 00:51:03 +0100 Subject: [PATCH 038/160] Oops, other half of fix for issue #189 --- Simple.Data.Ado/EagerLoadingEnumerable.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simple.Data.Ado/EagerLoadingEnumerable.cs b/Simple.Data.Ado/EagerLoadingEnumerable.cs index 3beca4bd..a5aa5669 100644 --- a/Simple.Data.Ado/EagerLoadingEnumerable.cs +++ b/Simple.Data.Ado/EagerLoadingEnumerable.cs @@ -83,7 +83,10 @@ private Dictionary, IDictionary row) public void SetSingle(IDictionary row) { + if (row.All(kvp => ReferenceEquals(null, kvp.Value))) return; Single = row; } } From 089858e2dc312d108d79ec1a0beaf835fdf8975c Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 20 May 2012 01:36:29 +0100 Subject: [PATCH 039/160] Added Operators class in Simple.Data.Ado as extension point for providers with different operators. --- Simple.Data.Ado/AdoAdapterFinder.cs | 4 +- Simple.Data.Ado/AdoAdapterGetter.cs | 2 +- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 4 +- Simple.Data.Ado/CommandBuilder.cs | 19 +++--- Simple.Data.Ado/CommandTemplate.cs | 20 +++--- Simple.Data.Ado/ExpressionFormatter.cs | 2 +- Simple.Data.Ado/ExpressionFormatterBase.cs | 72 ++++++++++++--------- Simple.Data.Ado/ExpressionHasher.cs | 6 ++ Simple.Data.Ado/Schema/DatabaseSchema.cs | 12 ++++ Simple.Data.Ado/SimpleReferenceFormatter.cs | 12 ++-- 10 files changed, 94 insertions(+), 59 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterFinder.cs b/Simple.Data.Ado/AdoAdapterFinder.cs index 4f89fd89..efd986e4 100644 --- a/Simple.Data.Ado/AdoAdapterFinder.cs +++ b/Simple.Data.Ado/AdoAdapterFinder.cs @@ -113,7 +113,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 +121,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); } diff --git a/Simple.Data.Ado/AdoAdapterGetter.cs b/Simple.Data.Ado/AdoAdapterGetter.cs index 93c7dc2a..6bbd30c5 100644 --- a/Simple.Data.Ado/AdoAdapterGetter.cs +++ b/Simple.Data.Ado/AdoAdapterGetter.cs @@ -70,7 +70,7 @@ 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); } diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 3db82b14..71c4485b 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -34,7 +34,7 @@ out IEnumerable if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) { var command = - CommandBuilder.CreateCommand( + new CommandBuilder(_adapter.GetSchema()).CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders, connection); @@ -237,7 +237,7 @@ public IEnumerable>> RunQueries(SimpleQu connection = _adapter.CreateConnection(); } IDbCommand command = - CommandBuilder.CreateCommand( + new CommandBuilder(_adapter.GetSchema()).CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders.ToArray(), connection); if (_transaction != null) diff --git a/Simple.Data.Ado/CommandBuilder.cs b/Simple.Data.Ado/CommandBuilder.cs index f7b0a71a..626d79b7 100644 --- a/Simple.Data.Ado/CommandBuilder.cs +++ b/Simple.Data.Ado/CommandBuilder.cs @@ -13,6 +13,7 @@ class CommandBuilder : ICommandBuilder { private int _number; private Func _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; @@ -166,7 +169,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 +192,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 +231,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 +251,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,7 +292,7 @@ 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) { var command = connection.CreateCommand(); parameterFactory = parameterFactory ?? new GenericDbParameterFactory(command); diff --git a/Simple.Data.Ado/CommandTemplate.cs b/Simple.Data.Ado/CommandTemplate.cs index bb012307..e4ea388e 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(); 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/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..24159500 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 (expression.RightOperand == null) return string.Format("{0} {1}", FormatObject(expression.LeftOperand, null), Operators.IsNull); if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, "="); - 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 (expression.RightOperand == null) return string.Format("{0} {1}", FormatObject(expression.LeftOperand, null), Operators.IsNotNull); if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, "!="); - 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/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 892f0a1e..68b6689f 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -14,12 +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; } @@ -146,6 +148,16 @@ 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/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index dc90b958..9d2479fd 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -78,20 +78,20 @@ private string TryFormatAsMathReference(MathReference mathReference, bool exclud } - 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."); } From 02866affd93b1a69fb0d11e25aee42bebf490ed6 Mon Sep 17 00:00:00 2001 From: markrendle Date: Sun, 20 May 2012 22:33:21 +0100 Subject: [PATCH 040/160] Release 0.16.2.1 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 993fea8e..1a90bb25 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("0.16.1.0")] -[assembly: AssemblyFileVersion("0.16.1.0")] +[assembly: AssemblyVersion("0.16.2.1")] +[assembly: AssemblyFileVersion("0.16.2.1")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 1aed6904..706a3fdc 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-beta6 + 0.16.2.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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index c37f8f15..e4bd5cc3 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-beta6 + 0.16.2.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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 485fa602..279bf9be 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta6 + 0.16.2.1 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 28b1e1cc..4392c73a 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-beta6 + 0.16.2.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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index ac8fb1d8..e5ffebaa 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta6 + 0.16.2.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 17337cb73ed0b051c53deadd59ada94725867f00 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 23 May 2012 16:03:39 +0100 Subject: [PATCH 041/160] Added ability to create Custom Query Builders and Select for Update in SQL Server --- Simple.Data.Ado/ICustomQueryBuilder.cs | 9 + Simple.Data.Ado/QueryBuilderBase.cs | 378 ++++++++++++++++++ .../SqlCustomQueryBuilder.cs | 16 + Simple.Data.SqlServer/SqlQueryBuilder.cs | 48 +++ Simple.Data/ForUpdateClause.cs | 12 + 5 files changed, 463 insertions(+) create mode 100644 Simple.Data.Ado/ICustomQueryBuilder.cs create mode 100644 Simple.Data.Ado/QueryBuilderBase.cs create mode 100644 Simple.Data.SqlServer/SqlCustomQueryBuilder.cs create mode 100644 Simple.Data.SqlServer/SqlQueryBuilder.cs create mode 100644 Simple.Data/ForUpdateClause.cs 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/QueryBuilderBase.cs b/Simple.Data.Ado/QueryBuilderBase.cs new file mode 100644 index 00000000..74b23fb8 --- /dev/null +++ b/Simple.Data.Ado/QueryBuilderBase.cs @@ -0,0 +1,378 @@ +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 (_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; + } + + 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.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/SqlQueryBuilder.cs b/Simple.Data.SqlServer/SqlQueryBuilder.cs new file mode 100644 index 00000000..3c23c411 --- /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"; + if (forUpdateClause.SkipLockedRows) + { + forUpdate += ", READPAST"; + } + forUpdate += ")"; + select += forUpdate; + } + return select; + } + } +} 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; + } + } +} From 585b3552b7d789fb4cb8ba11f59acc98a25e171d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 23 May 2012 16:10:28 +0100 Subject: [PATCH 042/160] modified files for CustomQueryBuilder --- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 1 + Simple.Data.Ado/CommandBuilder.cs | 2 +- Simple.Data.Ado/Joiner.cs | 2 +- Simple.Data.Ado/QueryBuilder.cs | 373 +----------------- Simple.Data.Ado/Simple.Data.Ado.csproj | 2 + Simple.Data.Ado/SimpleReferenceFormatter.cs | 12 +- .../Simple.Data.SqlServer.csproj | 2 + Simple.Data.SqlTest/QueryTest.cs | 16 + Simple.Data.UnitTest/SimpleQueryTest.cs | 40 ++ Simple.Data/Simple.Data.csproj | 1 + Simple.Data/SimpleQuery.cs | 27 ++ 11 files changed, 113 insertions(+), 365 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 71c4485b..e4d2b63f 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -94,6 +94,7 @@ out IEnumerable .ClearTake() .ClearOrderBy() .ClearWith() + .ClearForUpdate() .ReplaceSelect(new CountSpecialReference()); var unhandledClausesList = new List> { diff --git a/Simple.Data.Ado/CommandBuilder.cs b/Simple.Data.Ado/CommandBuilder.cs index 626d79b7..a3a34a4b 100644 --- a/Simple.Data.Ado/CommandBuilder.cs +++ b/Simple.Data.Ado/CommandBuilder.cs @@ -9,7 +9,7 @@ namespace Simple.Data.Ado { - class CommandBuilder : ICommandBuilder + public class CommandBuilder : ICommandBuilder { private int _number; private Func _getParameterFactory; diff --git a/Simple.Data.Ado/Joiner.cs b/Simple.Data.Ado/Joiner.cs index 244307dc..d514b4f0 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; diff --git a/Simple.Data.Ado/QueryBuilder.cs b/Simple.Data.Ado/QueryBuilder.cs index a9abb08b..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,338 +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 - { - 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(); - } - } - - private 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(); - } - } - - 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)) - ? 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); - } - } - - private IEnumerable GetJoinClausesFromColumnList(IEnumerable joinClauses, Joiner joiner) - { - return joiner.GetJoinClauses(_tableName, GetObjectReferences(_columns) - .Where(o => !joinClauses.Any(j => ObjectReferenceIsInJoinClause(j, o))), JoinType.Outer); - - } - - private static bool ObjectReferenceIsInJoinClause(JoinClause clause, ObjectReference reference) - { - return reference.GetOwner().GetAliasOrName().Equals(clause.Table.GetAliasOrName()); - } - - 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(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))); - } - - 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/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index 76b1b530..05ec43e4 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -98,6 +98,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/Simple.Data.Ado/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index 9d2479fd..d9c76a96 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -5,16 +5,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) diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj index 4ff7440f..b6a69df6 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj @@ -59,12 +59,14 @@ Resources.resx + + diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 7f330efe..200a5d8c 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -431,5 +431,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.UnitTest/SimpleQueryTest.cs b/Simple.Data.UnitTest/SimpleQueryTest.cs index 61861783..3a5ea8e8 100644 --- a/Simple.Data.UnitTest/SimpleQueryTest.cs +++ b/Simple.Data.UnitTest/SimpleQueryTest.cs @@ -168,5 +168,45 @@ public void JoinOnShouldSetAJoin() Assert.AreEqual(quux.foo_id, join.JoinExpression.RightOperand); Assert.AreEqual(SimpleExpressionType.Equal, join.JoinExpression.Type); } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ForUpdateWithoutSelectShouldThrow() + { + new SimpleQuery(null, "foo").ForUpdate(false); + } + + [Test] + public void ForUpdateShouldAddAClause() + { + var query = new SimpleQuery(null, "foo").Select(new AllColumnsSpecialReference()).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").Select(new AllColumnsSpecialReference()).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").Select(new AllColumnsSpecialReference()).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/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index bb2098f3..1847bc7e 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -88,6 +88,7 @@ + diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 0bf3039c..1d73c5d6 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -150,6 +150,33 @@ 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. + /// Requires that a form of Select has already been requested + /// + /// Indicates whether the selection should skip rows already locked + /// A new which will perform locking on the selected rows + public SimpleQuery ForUpdate(bool skipLockedRows) + { + ThrowIfThereIsNotASelectClause(); + 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 ThrowIfThereIsNotASelectClause() + { + if (!_clauses.OfType().Any()) + throw new InvalidOperationException("Query does not contain a Select clause."); + } + private void ThrowIfThereIsAlreadyASelectClause() { if (_clauses.OfType().Any()) From 960644d65baefc10a149166bcdc1ff7a7504313f Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 24 May 2012 12:17:23 +0100 Subject: [PATCH 043/160] Removed need for SelectClause on ForUpdate in SimpleQuery Added ROWLOCK hint to UPDLOCK sql in SQL Server --- Simple.Data.SqlServer/SqlQueryBuilder.cs | 2 +- Simple.Data.UnitTest/SimpleQueryTest.cs | 13 +++---------- Simple.Data/SimpleQuery.cs | 10 +--------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/Simple.Data.SqlServer/SqlQueryBuilder.cs b/Simple.Data.SqlServer/SqlQueryBuilder.cs index 3c23c411..7ea9fee2 100644 --- a/Simple.Data.SqlServer/SqlQueryBuilder.cs +++ b/Simple.Data.SqlServer/SqlQueryBuilder.cs @@ -34,7 +34,7 @@ protected override string GetSelectClause(ObjectName tableName) var forUpdateClause = _query.Clauses.OfType().FirstOrDefault(); if (forUpdateClause != null) { - var forUpdate = " WITH (UPDLOCK"; + var forUpdate = " WITH (UPDLOCK, ROWLOCK"; if (forUpdateClause.SkipLockedRows) { forUpdate += ", READPAST"; diff --git a/Simple.Data.UnitTest/SimpleQueryTest.cs b/Simple.Data.UnitTest/SimpleQueryTest.cs index 3a5ea8e8..31ba421d 100644 --- a/Simple.Data.UnitTest/SimpleQueryTest.cs +++ b/Simple.Data.UnitTest/SimpleQueryTest.cs @@ -169,17 +169,10 @@ public void JoinOnShouldSetAJoin() Assert.AreEqual(SimpleExpressionType.Equal, join.JoinExpression.Type); } - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ForUpdateWithoutSelectShouldThrow() - { - new SimpleQuery(null, "foo").ForUpdate(false); - } - [Test] public void ForUpdateShouldAddAClause() { - var query = new SimpleQuery(null, "foo").Select(new AllColumnsSpecialReference()).ForUpdate(true); + 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); @@ -188,7 +181,7 @@ public void ForUpdateShouldAddAClause() [Test] public void SubsequentCallsToForUpdateShouldReplaceClause() { - var query = new SimpleQuery(null, "foo").Select(new AllColumnsSpecialReference()).ForUpdate(false); + 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); @@ -201,7 +194,7 @@ public void SubsequentCallsToForUpdateShouldReplaceClause() [Test] public void ClearForUpdateRemovesClause() { - var query = new SimpleQuery(null, "foo").Select(new AllColumnsSpecialReference()).ForUpdate(false); + 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); diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 1d73c5d6..cc349cb4 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -151,14 +151,12 @@ public SimpleQuery ReplaceSelect(IEnumerable columns) } /// - /// Alters the query to lock the rows for update. - /// Requires that a form of Select has already been requested + /// 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) { - ThrowIfThereIsNotASelectClause(); return new SimpleQuery(this, _clauses.Where(c => !(c is ForUpdateClause)).Append(new ForUpdateClause(skipLockedRows)).ToArray()); } @@ -171,12 +169,6 @@ public SimpleQuery ClearForUpdate() return new SimpleQuery(this, _clauses.Where(c => !(c is ForUpdateClause)).ToArray()); } - private void ThrowIfThereIsNotASelectClause() - { - if (!_clauses.OfType().Any()) - throw new InvalidOperationException("Query does not contain a Select clause."); - } - private void ThrowIfThereIsAlreadyASelectClause() { if (_clauses.OfType().Any()) From 920979fb6eaa1d3313f89396ec8ea6fdb56bec17 Mon Sep 17 00:00:00 2001 From: vidarls Date: Fri, 25 May 2012 09:45:50 +0200 Subject: [PATCH 044/160] Added tests to confirm upsert in the InMemoryAdapter Allso in the process learned that I need to apply adapter.SetKeyColumn() before using Upsert (or any other methods depending on a key column being present) with the InMemoryAdapter --- Simple.Data.InMemoryTest/InMemoryTests.cs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index bcee0014..b9027bf7 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -623,5 +623,33 @@ public void ProcedureWithParametersReturningArrayShouldWork() Assert.AreEqual("Bar", row.Foo); } } + + [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); + } } } From 6e5edcf0343942b0a7e1e5d171ecdaa3adb49d84 Mon Sep 17 00:00:00 2001 From: vidarls Date: Fri, 25 May 2012 10:40:35 +0200 Subject: [PATCH 045/160] Adds meaningful exception to Upsert when no key columns are defined for table --- Simple.Data.InMemoryTest/InMemoryTests.cs | 10 ++++++++++ Simple.Data/Commands/UpsertCommand.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index b9027bf7..ebed4022 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -651,5 +651,15 @@ public void UpsertShouldUpdateExistingRecord() 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); + } } } diff --git a/Simple.Data/Commands/UpsertCommand.cs b/Simple.Data/Commands/UpsertCommand.cs index 42152974..e37416f6 100644 --- a/Simple.Data/Commands/UpsertCommand.cs +++ b/Simple.Data/Commands/UpsertCommand.cs @@ -49,7 +49,7 @@ private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable ta 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())); var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), key); return dataStrategy.Run.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); } From 531278542e8edbaed8fdad5b9cd3dda2d8e4689e Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 12 Jun 2012 11:58:50 +0100 Subject: [PATCH 046/160] 0.16.1.1 - added .Star() to DynamicTable --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/NugetPack.cmd | 2 +- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/NugetPack.cmd | 2 +- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/NugetPack.cmd | 2 +- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/NugetPack.cmd | 2 +- .../Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/DynamicTable.cs | 17 +++++++++++++++++ Simple.Data/NugetPack.cmd | 2 +- Simple.Data/Simple.Data.nuspec | 2 +- 12 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 993fea8e..63c4bedd 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("0.16.1.0")] -[assembly: AssemblyFileVersion("0.16.1.0")] +[assembly: AssemblyVersion("0.16.1.1")] +[assembly: AssemblyFileVersion("0.16.1.1")] 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/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 1aed6904..1f5090cb 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-beta6 + 0.16.1.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.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.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index c37f8f15..e5ca06d0 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-beta6 + 0.16.1.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.SqlCe40/NugetPack.cmd b/Simple.Data.SqlCe40/NugetPack.cmd index b659f3e0..03572200 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.csproj -Properties Configuration=Release;Platform=AnyCPU -Build diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 485fa602..7983ce03 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-beta6 + 0.16.1.1 Mark Rendle Mark Rendle SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. @@ -12,7 +12,7 @@ sqlserver compact sqlce database data ado .net40 en-us - + 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/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 28b1e1cc..d7c9701c 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-beta6 + 0.16.1.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/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 6aa07e22..54b045fa 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -161,5 +161,22 @@ private IEnumerable GetAll() { 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/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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index ac8fb1d8..5e9a8b62 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-beta6 + 0.16.1.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 0874641b4baeb656cd6215788ab59a1eacae2da1 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 19 Jun 2012 13:28:51 +0100 Subject: [PATCH 047/160] Tweaked tests to work in NCrunch by forcing load of assembly under test. --- .gitignore | 1 + .../ConnectionProviderTest.cs | 12 ++++++ Simple.Data.SqlTest/BulkInsertTest.cs | 1 + Simple.Data.SqlTest/DatabaseHelper.cs | 1 + Simple.Data.SqlTest/SetupFixture.cs | 5 +++ Simple.Data.SqlTest/UpsertTests.cs | 1 + Simple.Data/MefHelper.cs | 38 ++++++++++++++++--- 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 257b555e..064a6a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ Simple.Data.sln.DotSettings.user Simple.Data/Simple.Data.idc .DS_Store mono-release-* +*ncrunch* 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.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs index 7bcde034..1379214f 100644 --- a/Simple.Data.SqlTest/BulkInsertTest.cs +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -1,6 +1,7 @@ namespace Simple.Data.SqlTest { using System.Collections.Generic; + using System.Diagnostics; using NUnit.Framework; [TestFixture] diff --git a/Simple.Data.SqlTest/DatabaseHelper.cs b/Simple.Data.SqlTest/DatabaseHelper.cs index 225a2cbc..ad358e9c 100644 --- a/Simple.Data.SqlTest/DatabaseHelper.cs +++ b/Simple.Data.SqlTest/DatabaseHelper.cs @@ -24,6 +24,7 @@ public static dynamic Open() public static void Reset() { + var provider = new SqlServer.SqlConnectionProvider(); using (var cn = new SqlConnection(ConnectionString)) { cn.Open(); diff --git a/Simple.Data.SqlTest/SetupFixture.cs b/Simple.Data.SqlTest/SetupFixture.cs index fe5b6b84..9f246abb 100644 --- a/Simple.Data.SqlTest/SetupFixture.cs +++ b/Simple.Data.SqlTest/SetupFixture.cs @@ -8,12 +8,17 @@ namespace Simple.Data.SqlTest { + using System.Diagnostics; + [SetUpFixture] public class SetupFixture { [SetUp] public void CreateStoredProcedures() { + var provider = new SqlServer.SqlConnectionProvider(); + Trace.Write("Loaded provider " + provider.GetType().Name); + using (var cn = new SqlConnection(Properties.Settings.Default.ConnectionString)) { cn.Open(); diff --git a/Simple.Data.SqlTest/UpsertTests.cs b/Simple.Data.SqlTest/UpsertTests.cs index 7fae97ba..526738be 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"); diff --git a/Simple.Data/MefHelper.cs b/Simple.Data/MefHelper.cs index 7b7e867a..e82b337e 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -12,9 +12,19 @@ namespace Simple.Data class MefHelper : Composer { + private static readonly Assembly ThisAssembly = typeof (MefHelper).Assembly; + 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,7 +37,15 @@ public override T Compose(string contractName) { try { - using (var container = CreateContainer()) + using (var container = CreateAppDomainContainer()) + { + var exports = container.GetExports(contractName).ToList(); + 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("No ADO Provider found."); @@ -55,7 +73,7 @@ public static T GetAdjacentComponent(Type knownSiblingType) static string GetThisAssemblyPath() { - var path = Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", "").Replace("file://", "//"); + var path = ThisAssembly.CodeBase.Replace("file:///", "").Replace("file://", "//"); path = Path.GetDirectoryName(path); if (path == null) throw new ArgumentException("Unrecognised file."); if (!Path.IsPathRooted(path)) @@ -65,11 +83,11 @@ static string GetThisAssemblyPath() return path; } - private static CompositionContainer CreateContainer() + private static CompositionContainer CreateFolderContainer() { var path = GetThisAssemblyPath (); - var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); + var assemblyCatalog = new AssemblyCatalog(ThisAssembly); var aggregateCatalog = new AggregateCatalog(assemblyCatalog); foreach (string file in System.IO.Directory.GetFiles(path, "Simple.Data.*.dll")) { @@ -78,5 +96,15 @@ private static CompositionContainer CreateContainer() } return new CompositionContainer(aggregateCatalog); } + + private static CompositionContainer CreateAppDomainContainer() + { + var aggregateCatalog = new AggregateCatalog(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.GlobalAssemblyCache)) + { + aggregateCatalog.Catalogs.Add(new AssemblyCatalog(assembly)); + } + return new CompositionContainer(aggregateCatalog); + } } } From 7d1577f68bdf9d3b522755fc1c638056af026843 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 19 Jun 2012 14:14:28 +0100 Subject: [PATCH 048/160] Missed from last commit --- Simple.Data.SqlTest/DatabaseOpenerTests.cs | 6 ++++++ Simple.Data.SqlTest/DeleteTest.cs | 6 ++++++ Simple.Data.SqlTest/FunctionTest.cs | 6 ++++++ Simple.Data.SqlTest/NaturalJoinTest.cs | 6 ++++++ Simple.Data.SqlTest/OrderDetailTests.cs | 6 ++++++ Simple.Data.SqlTest/ProcedureTest.cs | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/Simple.Data.SqlTest/DatabaseOpenerTests.cs b/Simple.Data.SqlTest/DatabaseOpenerTests.cs index 307565ad..36a9eb79 100644 --- a/Simple.Data.SqlTest/DatabaseOpenerTests.cs +++ b/Simple.Data.SqlTest/DatabaseOpenerTests.cs @@ -12,6 +12,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class DatabaseOpenerTests { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void OpenNamedConnectionTest() { 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/FunctionTest.cs b/Simple.Data.SqlTest/FunctionTest.cs index 1a8fd710..f502c563 100644 --- a/Simple.Data.SqlTest/FunctionTest.cs +++ b/Simple.Data.SqlTest/FunctionTest.cs @@ -10,6 +10,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class FunctionTest { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void CoalesceFunctionWorks() { 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 5ef7642f..0ebe7576 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -8,6 +8,12 @@ namespace Simple.Data.SqlTest [TestFixture] public class ProcedureTest { + [TestFixtureSetUp] + public void Setup() + { + DatabaseHelper.Reset(); + } + [Test] public void GetCustomersTest() { From a3accbd77045a70249d6280d6ad58882a3b1c500 Mon Sep 17 00:00:00 2001 From: Craig Wilson Date: Tue, 19 Jun 2012 10:42:07 -0500 Subject: [PATCH 049/160] added support for a list of heterogenous items by testing at runtime whether it is a dictionary or already the correct type. --- Simple.Data/PropertySetterBuilder.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 44179890..4a86f1ac 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -125,7 +125,7 @@ private Expression BuildCollectionCreatorExpression(Type genericType, ConcreteTy var isDictionaryCollection = BuildComplexTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out dictionaryBlock); BlockExpression objectBlock; - var isObjectcollection = BuildSimpleTypeCollectionPopulator(collection, genericType, addMethod, createCollection, out objectBlock); + var isObjectcollection = BuildSimpleTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out objectBlock); return Expression.IfThenElse(isDictionaryCollection, dictionaryBlock, Expression.IfThen(isObjectcollection, objectBlock)); @@ -176,8 +176,9 @@ private TypeBinaryExpression BuildComplexTypeCollectionPopulator(ParameterExpres private TypeBinaryExpression BuildSimpleTypeCollectionPopulator(ParameterExpression collection, Type genericType, MethodInfo addMethod, BinaryExpression createCollection, - out BlockExpression block) + 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)); @@ -196,8 +197,14 @@ private TypeBinaryExpression BuildSimpleTypeCollectionPopulator(ParameterExpress Expression.LessThan(i, Expression.Property(array, ArrayObjectLengthProperty)), Expression.Block( Expression.Assign(current, Expression.ArrayIndex(array, i)), - Expression.Call(collection, addMethod, - Expression.Convert(current, genericType)), + 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(current, genericType))), Expression.PreIncrementAssign(i) ), Expression.Break(label) From b182e0a61c5d2b2ef1e141d0eeb90da12b0df554 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 12 Jul 2012 15:35:08 +0100 Subject: [PATCH 050/160] Added IsolationLevel support to BeginTransaction --- .../AdoAdapter.IAdapterWithTransactions.cs | 42 +++++++++---------- Simple.Data.SqlTest/UpdateTests.cs | 1 + Simple.Data/Database.cs | 7 +++- Simple.Data/IAdapterWithTransactions.cs | 5 ++- ...InMemoryAdapterIAdapterWithTransactions.cs | 5 ++- Simple.Data/SimpleTransaction.cs | 17 ++++++-- 6 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs index 16210389..05d61dba 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(); @@ -71,25 +71,25 @@ public int UpdateMany(string tableName, IList> dataL ((AdoAdapterTransaction)adapterTransaction).DbTransaction); } - 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(); - - return new AdoAdapterTransaction(transaction, name, _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(); + + // return new AdoAdapterTransaction(transaction, name, _sharedConnection != null); + //} public IDictionary Get(string tableName, IAdapterTransaction transaction, params object[] parameterValues) { diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index b4245e66..399d986a 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -106,6 +106,7 @@ public void TestUpdateAllWithNoMatchingRows() } [Test] + [Ignore] public void TestUpdateWithJoinCriteriaOnCompoundKeyTable() { var db = DatabaseHelper.Open(); diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 173d1b61..e2ca1185 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -68,8 +68,6 @@ public static dynamic Default get { return DatabaseOpener.OpenDefault(); } } - - public SimpleTransaction BeginTransaction() { return SimpleTransaction.Begin(this); @@ -80,6 +78,11 @@ public SimpleTransaction BeginTransaction(string name) return SimpleTransaction.Begin(this, name); } + public SimpleTransaction BeginTransaction(IsolationLevel isolationLevel) + { + return SimpleTransaction.Begin(this, isolationLevel); + } + protected override bool ExecuteFunction(out object result, Commands.ExecuteFunctionCommand command) { return command.Execute(out result); diff --git a/Simple.Data/IAdapterWithTransactions.cs b/Simple.Data/IAdapterWithTransactions.cs index bc0fdb43..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". diff --git a/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs b/Simple.Data/InMemoryAdapterIAdapterWithTransactions.cs index e846f10a..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); } diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 3b48be4d..b6c680c6 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using System.Diagnostics; using System.Dynamic; using System.Text; @@ -13,17 +14,19 @@ namespace Simple.Data public sealed class SimpleTransaction : DataStrategy, IDisposable { private readonly Database _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, Database 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) @@ -60,11 +63,18 @@ internal static SimpleTransaction Begin(Database database, string name) return transaction; } - private static SimpleTransaction CreateTransaction(Database database) + public static SimpleTransaction Begin(Database database, IsolationLevel isolationLevel) + { + var transaction = CreateTransaction(database, isolationLevel); + transaction.Begin(); + return transaction; + } + + private static SimpleTransaction CreateTransaction(Database 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); } @@ -103,7 +113,6 @@ public void Rollback() _adapterTransaction.Rollback(); } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. From a57167083e18937c398488053db1b70f8805ef52 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 12 Jul 2012 15:52:11 +0100 Subject: [PATCH 051/160] Allow alias on MathReference (fix issue #209) --- Simple.Data.Ado/SimpleReferenceFormatter.cs | 4 ++-- Simple.Data.BehaviourTest/Query/QueryTest.cs | 9 +++++++++ Simple.Data/MathReference.cs | 12 ++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Simple.Data.Ado/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index d9c76a96..96761642 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -77,10 +77,10 @@ private string TryFormatAsMathReference(MathReference mathReference, bool exclud 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())); } diff --git a/Simple.Data.BehaviourTest/Query/QueryTest.cs b/Simple.Data.BehaviourTest/Query/QueryTest.cs index b8488f25..3fea703c 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() diff --git a/Simple.Data/MathReference.cs b/Simple.Data/MathReference.cs index f357289b..af947861 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 . /// From bf472b9c5fb5e95bcff723556b1d9849ee61d648 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Thu, 12 Jul 2012 16:03:51 +0100 Subject: [PATCH 052/160] Bulk update honours Transaction (fix issue #203) --- Simple.Data.Ado/BulkUpdater.cs | 4 ++++ Simple.Data.BehaviourTest/TransactionTest.cs | 25 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Simple.Data.Ado/BulkUpdater.cs b/Simple.Data.Ado/BulkUpdater.cs index 03173237..3a010a47 100644 --- a/Simple.Data.Ado/BulkUpdater.cs +++ b/Simple.Data.Ado/BulkUpdater.cs @@ -37,6 +37,10 @@ public int Update(AdoAdapter adapter, string tableName, IList Date: Fri, 13 Jul 2012 09:44:25 +0100 Subject: [PATCH 053/160] Added BadExpressionException --- Simple.Data.BehaviourTest/FindTest.cs | 12 +++++++++ Simple.Data/BadExpressionException.cs | 37 ++++++++++++++++++++++++++ Simple.Data/Commands/FindAllCommand.cs | 2 +- Simple.Data/Commands/FindCommand.cs | 2 +- Simple.Data/Simple.Data.csproj | 1 + 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 Simple.Data/BadExpressionException.cs diff --git a/Simple.Data.BehaviourTest/FindTest.cs b/Simple.Data.BehaviourTest/FindTest.cs index 408c1546..ec082ef7 100644 --- a/Simple.Data.BehaviourTest/FindTest.cs +++ b/Simple.Data.BehaviourTest/FindTest.cs @@ -208,6 +208,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() { diff --git a/Simple.Data/BadExpressionException.cs b/Simple.Data/BadExpressionException.cs new file mode 100644 index 00000000..740f2c82 --- /dev/null +++ b/Simple.Data/BadExpressionException.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace Simple.Data +{ + [Serializable] + public class BadExpressionException : Exception + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + 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) + { + } + } +} diff --git a/Simple.Data/Commands/FindAllCommand.cs b/Simple.Data/Commands/FindAllCommand.cs index f33f4f49..1bbe0f2a 100644 --- a/Simple.Data/Commands/FindAllCommand.cs +++ b/Simple.Data/Commands/FindAllCommand.cs @@ -35,7 +35,7 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where((SimpleExpression)args[0]); } - return null; + throw new BadExpressionException("FindAll only accepts a criteria expression."); } } } diff --git a/Simple.Data/Commands/FindCommand.cs b/Simple.Data/Commands/FindCommand.cs index 935c6640..c5bf450e 100644 --- a/Simple.Data/Commands/FindCommand.cs +++ b/Simple.Data/Commands/FindCommand.cs @@ -36,7 +36,7 @@ public object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMembe 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) diff --git a/Simple.Data/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 1847bc7e..8af9a5e0 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -92,6 +92,7 @@ + From e0c198ee6c8c0c9ac905b99d06df013042e24261 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 11:26:03 +0100 Subject: [PATCH 054/160] Fix issues #207 and #201 --- Simple.Data.BehaviourTest/Query/QueryTest.cs | 2 - Simple.Data.BehaviourTest/Query/WhereTest.cs | 52 +++++++++++++++++++ .../Simple.Data.BehaviourTest.csproj | 1 + Simple.Data/SimpleQuery.cs | 8 ++- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 Simple.Data.BehaviourTest/Query/WhereTest.cs diff --git a/Simple.Data.BehaviourTest/Query/QueryTest.cs b/Simple.Data.BehaviourTest/Query/QueryTest.cs index 3fea703c..de520d46 100644 --- a/Simple.Data.BehaviourTest/Query/QueryTest.cs +++ b/Simple.Data.BehaviourTest/Query/QueryTest.cs @@ -228,7 +228,5 @@ public void SpecifyingJoinTableShouldCreateDirectQuery() GeneratedSqlIs("select [dbo].[userbio].[userid],[dbo].[userbio].[text] from [dbo].[userbio]" + " join [dbo].[users] on ([dbo].[users].[id] = [dbo].[userbio].[userid]) where [dbo].[users].[id] = @p1"); } - - } } 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/Simple.Data.BehaviourTest.csproj b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj index 86d78241..93a67694 100644 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj @@ -78,6 +78,7 @@ + diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index cc349cb4..f2c8b35e 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -183,6 +183,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))); } @@ -333,7 +334,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); @@ -343,6 +344,11 @@ 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; } From 1eaa1b9f91ed1ea6fe57f1838e8a5b99c948e590 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 11:58:37 +0100 Subject: [PATCH 055/160] Added SetRegularExpression method to HomogenizeEx to fix issue #198 --- Simple.Data/Extensions/HomogenizeEx.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 From d10941bd6576f7b1e4d1ea6b96174c326183f29b Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 12:03:01 +0100 Subject: [PATCH 056/160] Fixes issue #200 --- Simple.Data.BehaviourTest/UpdateTest.cs | 10 ++++++++++ Simple.Data/BinderHelper.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 61b704f3..28511e61 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -62,6 +62,16 @@ 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() { diff --git a/Simple.Data/BinderHelper.cs b/Simple.Data/BinderHelper.cs index 273ce7ba..0a40435f 100644 --- a/Simple.Data/BinderHelper.cs +++ b/Simple.Data/BinderHelper.cs @@ -16,7 +16,7 @@ private static IDictionary NamedArgumentsToDictionary(IEnumerabl .Reverse() .Zip(args.Reverse(), (k, v) => new KeyValuePair(k, v)) .Reverse() - .ToDictionary(); + .ToDictionary(StringComparer.InvariantCultureIgnoreCase); } private static IDictionary ArgumentsToDictionary(IEnumerable argumentNames, IEnumerable args) @@ -29,7 +29,7 @@ private static IDictionary ArgumentsToDictionary(IEnumerable new KeyValuePair(k, v)) .Reverse() .Select((kvp, i) => kvp.Key == null ? new KeyValuePair("_" + i.ToString(), kvp.Value) : kvp) - .ToDictionary(); + .ToDictionary(StringComparer.InvariantCultureIgnoreCase); } internal static IDictionary NamedArgumentsToDictionary(this InvokeMemberBinder binder, IEnumerable args) From 9de9116ba068fd32c27a0d74ba162b7f7c1f8593 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 12:23:22 +0100 Subject: [PATCH 057/160] Use database default schema on Procedures; fixes issue #192 --- Simple.Data.Ado/Schema/DatabaseSchema.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 68b6689f..8d85aea4 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -74,11 +74,19 @@ 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); } From acb68169a4f2b917c513be08f1586e7b78d7cb22 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 12:32:09 +0100 Subject: [PATCH 058/160] Fixes issue #199 (and probably some other undiscovered faults. Stupid Mark.) --- Simple.Data.BehaviourTest/FindTest.cs | 7 +++++++ Simple.Data/Extensions/ObjectEx.cs | 1 + 2 files changed, 8 insertions(+) diff --git a/Simple.Data.BehaviourTest/FindTest.cs b/Simple.Data.BehaviourTest/FindTest.cs index ec082ef7..800fe64e 100644 --- a/Simple.Data.BehaviourTest/FindTest.cs +++ b/Simple.Data.BehaviourTest/FindTest.cs @@ -135,6 +135,13 @@ public void TestFindByNamedParameterSingleColumn() 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() diff --git a/Simple.Data/Extensions/ObjectEx.cs b/Simple.Data/Extensions/ObjectEx.cs index 522ec539..ba64b94d 100644 --- a/Simple.Data/Extensions/ObjectEx.cs +++ b/Simple.Data/Extensions/ObjectEx.cs @@ -53,6 +53,7 @@ static ElementInit PropertyToElementInit(PropertyInfo propertyInfo, Expression i internal static bool IsAnonymous(this object obj) { + if (obj == null) return false; return obj.GetType().Namespace == null; } } From 0e78d0b3f9b09a01753a78cb64023e387b40a3b1 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 12:48:11 +0100 Subject: [PATCH 059/160] Long-overdue fix for #195 --- Simple.Data.Ado/AdoAdapterFinder.cs | 17 +++++++---------- Simple.Data/PropertySetterBuilder.cs | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterFinder.cs b/Simple.Data.Ado/AdoAdapterFinder.cs index efd986e4..ac8e85bd 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 { diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 4a86f1ac..7084b0e9 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -363,7 +363,7 @@ internal static object SafeConvert(object source, Type targetType) internal static T? SafeConvertNullable(object source) where T : struct { - if (ReferenceEquals(source, null)) return default(T); + if (ReferenceEquals(source, null)) return default(T?); return (T) source; } From 76c1df52fa83481683ade4f6342bdcd54b1aeef9 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 12:58:26 +0100 Subject: [PATCH 060/160] Fixes issue #211 --- Simple.Data.Ado/OptimizedDictionary2.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 { From 9fa4d06709d4974142469e54e28f46ca655beb38 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 13:06:36 +0100 Subject: [PATCH 061/160] Fixes issue #206 --- Simple.Data.SqlTest/QueryTest.cs | 8 ++++++++ Simple.Data/SimpleRecord.cs | 15 +++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 200a5d8c..8bb427e4 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -82,6 +82,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() { diff --git a/Simple.Data/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index 4aab3ddf..1ced3208 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -64,11 +64,18 @@ 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, binder.Name)) + { + result = GetRelatedData(binder, relatedAdapter); + return true; + } + } + catch (UnresolvableObjectException) + { + throw new UnresolvableObjectException("Column not found."); } } return base.TryGetMember(binder, out result); From 581581194c125f56d0bd656efad54306b449ee66 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 13 Jul 2012 13:51:54 +0100 Subject: [PATCH 062/160] 1.0.0-rc0 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 17 ++++++++++------- .../Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index bd7d9c67..95510783 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("0.16.2.2")] -[assembly: AssemblyFileVersion("0.16.2.2")] +[assembly: AssemblyVersion("0.17.0.0")] +[assembly: AssemblyFileVersion("0.17.0.0")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 77a3d78b..a05b27ca 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.16.2.2 + 1.0.0-rc0 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index f21d7588..be17ba20 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.16.2.2 + 1.0.0-rc0 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 7897e131..24734c65 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -1,18 +1,21 @@ - - + + Simple.Data.SqlCompact40 - 0.16.2.2 + 1.0.0-rc0 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 - + + + + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index c3716be8..0a5b627d 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 0.16.2.2 + 1.0.0-rc0 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 8332cf06..0e77bfa4 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 0.16.2.2 + 1.0.0-rc0 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From b227feec9e4d2c0f74a154b052323c15abc247a5 Mon Sep 17 00:00:00 2001 From: Maciej Aniserowicz Date: Mon, 16 Jul 2012 12:09:58 +0200 Subject: [PATCH 063/160] Transaction IsolationLevel is now passed to underlying adapter fixes #213 --- ...BeginTransactionWithIsolataionLevelTest.cs | 115 ++++++++++++++++++ .../Simple.Data.UnitTest.csproj | 1 + Simple.Data/SimpleTransaction.cs | 4 +- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 Simple.Data.UnitTest/BeginTransactionWithIsolataionLevelTest.cs 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/Simple.Data.UnitTest.csproj b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj index 64e34418..860d6f75 100644 --- a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj +++ b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj @@ -70,6 +70,7 @@ + diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index b6c680c6..11345d97 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -39,13 +39,13 @@ private SimpleTransaction(SimpleTransaction copy) : base(copy) 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); } From aeaa346de733d97487bd770788d29eff66328b14 Mon Sep 17 00:00:00 2001 From: Roy Jacobs Date: Fri, 10 Aug 2012 15:44:50 +0200 Subject: [PATCH 064/160] Where polyfills now correctly handle where...in clause --- Simple.Data.InMemoryTest/InMemoryTests.cs | 14 +++++++++ Simple.Data.SqlTest/FindTests.cs | 8 +++++ .../QueryPolyfills/WhereClauseHandler.cs | 30 ++++++++++++------- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index ebed4022..c07779ea 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -35,6 +35,20 @@ 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 InsertAndFindWithTwoColumnsShouldWork() { diff --git a/Simple.Data.SqlTest/FindTests.cs b/Simple.Data.SqlTest/FindTests.cs index aadc2baa..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() { diff --git a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs index a610a0ad..da562cd4 100644 --- a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs +++ b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs @@ -10,12 +10,12 @@ internal class WhereClauseHandler { private readonly Dictionary, bool>>> _expressionFormatters; - private readonly WhereClause _whereClause; + private readonly WhereClause _whereClause; public WhereClauseHandler(WhereClause whereClause) { _whereClause = whereClause; - _expressionFormatters = new Dictionary, bool>>> + _expressionFormatters = new Dictionary, bool>>> { {SimpleExpressionType.And, LogicalExpressionToWhereClause}, {SimpleExpressionType.Or, LogicalExpressionToWhereClause}, @@ -95,16 +95,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.OfType().Any()) + { + 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 +124,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) { @@ -140,7 +148,7 @@ private IList Resolve(IDictionary dict, object operand, } if (dict.ContainsKey(key)) - return new[] {dict[key]}; + return new[] { dict[key] }; return new object[0]; } @@ -151,12 +159,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] }; } } From f752a2e1374d993f941cd9e5c4fe853b5773aaef Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 24 Aug 2012 14:55:45 +0100 Subject: [PATCH 065/160] Added Assembly attribute for ADO Provider assemblies --- CommonAssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 3 + Simple.Data.Ado.Test/ProviderHelperTest.cs | 9 +++ .../Simple.Data.Ado.Test.csproj | 3 +- .../TestProviderAssemblyAttribute.cs | 25 ++++++ .../ProviderAssemblyAttributeBase.cs | 70 ++++++++++++++++ Simple.Data.Ado/ProviderHelper.cs | 80 ++++++++++++++++++- Simple.Data.Ado/Simple.Data.Ado.csproj | 1 + Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 +- Simple.Data.InMemoryTest/InMemoryTests.cs | 22 +++++ .../Simple.Data.Mocking.nuspec | 4 +- .../Properties/AssemblyInfo.cs | 3 + .../Simple.Data.SqlCe40.csproj | 1 + .../Simple.Data.SqlCe40.nuspec | 4 +- .../SqlCe40ProviderAttribute.cs | 31 +++++++ .../Properties/AssemblyInfo.cs | 3 + .../Simple.Data.SqlServer.csproj | 1 + .../Simple.Data.SqlServer.nuspec | 4 +- .../SqlServerProviderAttribute.cs | 45 +++++++++++ Simple.Data/Composer.cs | 52 ++++++++++++ Simple.Data/MefHelper.cs | 18 +---- Simple.Data/Simple.Data.nuspec | 2 +- 22 files changed, 360 insertions(+), 29 deletions(-) create mode 100644 Simple.Data.Ado.Test/TestProviderAssemblyAttribute.cs create mode 100644 Simple.Data.Ado/ProviderAssemblyAttributeBase.cs create mode 100644 Simple.Data.SqlCe40/SqlCe40ProviderAttribute.cs create mode 100644 Simple.Data.SqlServer/SqlServerProviderAttribute.cs diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 95510783..6ed3aeeb 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("0.17.0.0")] -[assembly: AssemblyFileVersion("0.17.0.0")] +[assembly: AssemblyVersion("0.17.0.1")] +[assembly: AssemblyFileVersion("0.17.0.1")] 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..24b74a82 100644 --- a/Simple.Data.Ado.Test/ProviderHelperTest.cs +++ b/Simple.Data.Ado.Test/ProviderHelperTest.cs @@ -39,6 +39,15 @@ public void ShouldReturnNonExportedTypeFromServiceProvider() 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) diff --git a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj index e62f20bc..d5f3de74 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 @@ -63,6 +63,7 @@ + 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/ProviderAssemblyAttributeBase.cs b/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs new file mode 100644 index 00000000..a7443248 --- /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.GetName().FullName != 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.GetName()); + } + 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..d24d1a04 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; @@ -99,7 +101,14 @@ public IConnectionProvider GetProviderByConnectionString(string connectionString 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."); @@ -152,6 +161,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.GlobalAssemblyCache) + .SelectMany(ProviderAssemblyAttributeBase.Get) + .ToList(); + + if (attributes.Count == 0) + { + foreach (var file in Directory.EnumerateFiles(Composer.GetSimpleDataAssemblyPath(), "*.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 { /// diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index 05ec43e4..46a51681 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -120,6 +120,7 @@ True Settings.settings + diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index a05b27ca..17d73aaf 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-rc0 + 0.17.0.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.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index ebed4022..49f94ee5 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -661,5 +661,27 @@ public void UpsertWithoutDefinedKeyColumnsSHouldThrowMeaningfulException() 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)); + } } } diff --git a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index be17ba20..4e2dfdc4 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-rc0 + 0.17.0.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.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 24734c65..57dbd0a4 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-rc0 + 0.17.0.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,7 +12,7 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + 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.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 b6a69df6..6b5d94fd 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj @@ -69,6 +69,7 @@ + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 0a5b627d..3a53dfd8 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-rc0 + 0.17.0.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/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/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/MefHelper.cs b/Simple.Data/MefHelper.cs index e82b337e..03736c62 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -12,8 +12,6 @@ namespace Simple.Data class MefHelper : Composer { - private static readonly Assembly ThisAssembly = typeof (MefHelper).Assembly; - public override T Compose() { using (var container = CreateAppDomainContainer()) @@ -48,7 +46,7 @@ public override T Compose(string contractName) using (var container = CreateFolderContainer()) { var exports = container.GetExports(contractName).ToList(); - if (exports.Count == 0) throw new SimpleDataException("No ADO Provider found."); + 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; } @@ -71,21 +69,9 @@ public static T GetAdjacentComponent(Type knownSiblingType) } } - static string GetThisAssemblyPath() - { - var path = ThisAssembly.CodeBase.Replace("file:///", "").Replace("file://", "//"); - path = Path.GetDirectoryName(path); - if (path == null) throw new ArgumentException("Unrecognised file."); - if (!Path.IsPathRooted(path)) - { - path = Path.DirectorySeparatorChar + path; - } - return path; - } - private static CompositionContainer CreateFolderContainer() { - var path = GetThisAssemblyPath (); + var path = GetSimpleDataAssemblyPath (); var assemblyCatalog = new AssemblyCatalog(ThisAssembly); var aggregateCatalog = new AggregateCatalog(assemblyCatalog); diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 0e77bfa4..6dafd2b0 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-rc0 + 0.17.0.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From f9d43ebe3c683860879c179d1d1bd993d105aca8 Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 27 Aug 2012 15:16:59 +0100 Subject: [PATCH 066/160] Fixing up MEF code to only check Simple.Data.* assemblies --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/ProviderHelper.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/NugetPack.cmd | 2 +- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 10 +++++----- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/MefHelper.cs | 7 ++++++- Simple.Data/Simple.Data.nuspec | 2 +- 9 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 6ed3aeeb..208ae977 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("0.17.0.1")] -[assembly: AssemblyFileVersion("0.17.0.1")] +[assembly: AssemblyVersion("0.17.1.1")] +[assembly: AssemblyFileVersion("0.17.1.1")] diff --git a/Simple.Data.Ado/ProviderHelper.cs b/Simple.Data.Ado/ProviderHelper.cs index d24d1a04..a13bb58a 100644 --- a/Simple.Data.Ado/ProviderHelper.cs +++ b/Simple.Data.Ado/ProviderHelper.cs @@ -208,13 +208,13 @@ private static bool LoadUsingAssemblyAttribute(string connectionString, ICollect private static List LoadAssemblyAttributes() { var attributes = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.GlobalAssemblyCache) + .Where(a => a.GetName().Name.StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase)) .SelectMany(ProviderAssemblyAttributeBase.Get) .ToList(); if (attributes.Count == 0) { - foreach (var file in Directory.EnumerateFiles(Composer.GetSimpleDataAssemblyPath(), "*.dll")) + foreach (var file in Directory.EnumerateFiles(Composer.GetSimpleDataAssemblyPath(), "Simple.Data.*.dll")) { Assembly assembly; if (Composer.TryLoadAssembly(file, out assembly)) diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 17d73aaf..bfa07983 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.17.0.1 + 0.17.1.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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 4e2dfdc4..aae58b35 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.17.0.1 + 0.17.1.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.SqlCe40/NugetPack.cmd b/Simple.Data.SqlCe40/NugetPack.cmd index 03572200..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;Platform=AnyCPU -Build +nuget pack -sym Simple.Data.SqlCe40.nuspec diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 57dbd0a4..2c0bc50d 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 0.17.0.1 + 0.17.1.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,10 +12,10 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + - + + \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 3a53dfd8..981761bc 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 0.17.0.1 + 0.17.1.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/MefHelper.cs b/Simple.Data/MefHelper.cs index 03736c62..baa76089 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -86,11 +86,16 @@ private static CompositionContainer CreateFolderContainer() private static CompositionContainer CreateAppDomainContainer() { var aggregateCatalog = new AggregateCatalog(); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.GlobalAssemblyCache)) + 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) + { + return assembly.GetName().Name.StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 6dafd2b0..f86c3bc5 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 0.17.0.1 + 0.17.1.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 66028445809cad0d48ed9a79afdf9204696f869c Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 28 Aug 2012 16:19:23 +0100 Subject: [PATCH 067/160] Added WithOptions to allow Command Timeout and Identity Insert (SQL Server only) --- .../ConnectionModifierTest.cs | 77 +++++++++++ .../Simple.Data.Ado.Test.csproj | 1 + Simple.Data.Ado.Test/TestCustomInserter.cs | 57 +++++++- Simple.Data.Ado/AdoAdapter.cs | 60 ++++----- Simple.Data.Ado/AdoAdapterFinder.cs | 28 +--- Simple.Data.Ado/AdoAdapterGetter.cs | 20 +-- Simple.Data.Ado/AdoAdapterInserter.cs | 38 ++---- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 8 +- Simple.Data.Ado/AdoAdapterUpserter.cs | 4 +- Simple.Data.Ado/AdoOptions.cs | 24 ++++ Simple.Data.Ado/BulkInserterHelper.cs | 34 +---- .../BulkInserterTransactionHelper.cs | 2 +- Simple.Data.Ado/BulkUpdater.cs | 4 +- Simple.Data.Ado/CommandBuilder.cs | 24 ++-- Simple.Data.Ado/CommandHelper.cs | 18 +-- Simple.Data.Ado/CommandTemplate.cs | 2 +- Simple.Data.Ado/ConnectionEx.cs | 2 +- Simple.Data.Ado/DataReaderEnumerable.cs | 14 +- .../DataReaderMultipleEnumerator.cs | 13 +- Simple.Data.Ado/DbCommandExtensions.cs | 28 +++- Simple.Data.Ado/DbConnectionEx.cs | 9 ++ Simple.Data.Ado/ICommandBuilder.cs | 4 +- Simple.Data.Ado/ICustomInserter.cs | 2 +- Simple.Data.Ado/IObservableQueryRunner.cs | 2 +- Simple.Data.Ado/ProcedureExecutor.cs | 8 +- Simple.Data.Ado/Schema/Table.cs | 12 ++ Simple.Data.Ado/Simple.Data.Ado.csproj | 1 + .../Simple.Data.SqlServer.csproj | 1 + Simple.Data.SqlServer/SqlCustomInserter.cs | 127 ++++++++++++++++++ Simple.Data.SqlTest/InsertTests.cs | 22 +++ Simple.Data/Adapter.cs | 20 ++- Simple.Data/Commands/AllCommand.cs | 5 - Simple.Data/Commands/ICommand.cs | 2 - Simple.Data/DataStrategy.cs | 14 +- Simple.Data/DataStrategyWithOptions.cs | 50 +++++++ Simple.Data/Database.cs | 2 +- Simple.Data/OptionsBase.cs | 7 + Simple.Data/Simple.Data.csproj | 2 + Simple.Data/SimpleTransaction.cs | 2 +- 39 files changed, 543 insertions(+), 207 deletions(-) create mode 100644 Simple.Data.Ado.Test/ConnectionModifierTest.cs create mode 100644 Simple.Data.Ado/AdoOptions.cs create mode 100644 Simple.Data.SqlServer/SqlCustomInserter.cs create mode 100644 Simple.Data/DataStrategyWithOptions.cs create mode 100644 Simple.Data/OptionsBase.cs diff --git a/Simple.Data.Ado.Test/ConnectionModifierTest.cs b/Simple.Data.Ado.Test/ConnectionModifierTest.cs new file mode 100644 index 00000000..7d181535 --- /dev/null +++ b/Simple.Data.Ado.Test/ConnectionModifierTest.cs @@ -0,0 +1,77 @@ +using System.Data; +using NUnit.Framework; + +namespace Simple.Data.Ado.Test +{ + [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()); + } + + 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; } + } + } +} \ No newline at end of file diff --git a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj index d5f3de74..cdebfa7d 100644 --- a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj +++ b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj @@ -58,6 +58,7 @@ + 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/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index a98a8fcc..281c46ed 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Data; -using System.Data.Common; using System.Linq; using Simple.Data.Ado.Schema; @@ -18,6 +17,7 @@ public partial class AdoAdapter : Adapter, ICloneable private Lazy _relatedFinder; private DatabaseSchema _schema; private IDbConnection _sharedConnection; + private Func _connectionModifier = connection => connection; public AdoAdapter() { @@ -43,6 +43,11 @@ private AdoAdapter(IConnectionProvider connectionProvider, AdoAdapterFinder find _schema = schema; } + public AdoOptions AdoOptions + { + get { return Options as AdoOptions; } + } + public CommandOptimizer CommandOptimizer { get { return _commandOptimizer; } @@ -84,7 +89,7 @@ public override IDictionary GetKey(string tableName, IDictionary public object Clone() { - return new AdoAdapter(_connectionProvider); + return new AdoAdapter(_connectionProvider) {_connectionModifier = _connectionModifier}; } #endregion @@ -176,11 +181,6 @@ private static bool FunctionIsLikeOrNotLike(string functionName, object[] args) && args[0] is string); } - private static bool FunctionIsCount(string functionName, object[] args) - { - return (functionName.Equals("count", StringComparison.OrdinalIgnoreCase) && args.Length == 0); - } - public override IObservable> RunQueryAsObservable(SimpleQuery query, out IEnumerable @@ -245,56 +245,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).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(); } } @@ -310,7 +307,7 @@ public void StopUsingSharedConnection() public IDbConnection CreateConnection() { - return _sharedConnection ?? _connectionProvider.CreateConnection(); + return _sharedConnection ?? _connectionModifier(_connectionProvider.CreateConnection()); } public DatabaseSchema GetSchema() @@ -325,7 +322,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) diff --git a/Simple.Data.Ado/AdoAdapterFinder.cs b/Simple.Data.Ado/AdoAdapterFinder.cs index ac8e85bd..370e1caa 100644 --- a/Simple.Data.Ado/AdoAdapterFinder.cs +++ b/Simple.Data.Ado/AdoAdapterFinder.cs @@ -54,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 = @@ -169,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 6bbd30c5..34141651 100644 --- a/Simple.Data.Ado/AdoAdapterGetter.cs +++ b/Simple.Data.Ado/AdoAdapterGetter.cs @@ -41,7 +41,7 @@ public Func> CreateGetDelegate(string tableN 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 = @@ -77,25 +77,17 @@ private IDictionary ExecuteSingletonQuery(CommandTemplate comman 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; } @@ -121,7 +113,7 @@ public IDictionary Get(string tableName, object[] parameterValue 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 f013da50..dd44cf44 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; @@ -56,7 +55,7 @@ public IDictionary Insert(string tableName, IEnumerable(_adapter.ConnectionProvider); if (customInserter != null) { - return customInserter.Insert(_adapter, tableName, dataArray.ToDictionary(), _transaction); + return customInserter.Insert(_adapter, tableName, dataArray.ToDictionary(), _transaction, resultRequired); } var dataDictionary = dataArray.Where(kvp => table.HasColumn(kvp.Key) && table.FindColumn(kvp.Key).IsWriteable) @@ -129,7 +128,7 @@ internal IDictionary 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); @@ -141,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); @@ -151,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; } @@ -176,7 +167,7 @@ internal int Execute(string sql, IEnumerable columns, IEnumerable columns, IEnumerable new CommandBuilder(_adapter.GetSchema()).CreateCommand( _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), commandBuilders, - connection); + connection, _adapter.AdoOptions); if (_transaction != null) { @@ -51,7 +51,7 @@ out IEnumerable } 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()) @@ -70,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); } @@ -240,7 +240,7 @@ public IEnumerable>> RunQueries(SimpleQu IDbCommand command = 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; diff --git a/Simple.Data.Ado/AdoAdapterUpserter.cs b/Simple.Data.Ado/AdoAdapterUpserter.cs index 4432ad0b..109fd17e 100644 --- a/Simple.Data.Ado/AdoAdapterUpserter.cs +++ b/Simple.Data.Ado/AdoAdapterUpserter.cs @@ -60,11 +60,11 @@ private IDictionary Upsert(string tableName, IDictionary> 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 +96,7 @@ protected int InsertRow(IDictionary row, IDbCommand command, Fun try { - return TryExecute(command); + return command.TryExecuteNonQuery(); } catch (Exception ex) { @@ -113,7 +112,7 @@ protected IDictionary InsertRow(IDictionary row, try { - if (TryExecute(insertCommand) == 1) + if (insertCommand.TryExecuteNonQuery() == 1) return TryExecuteSingletonQuery(selectCommand); } catch (Exception ex) @@ -126,38 +125,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) 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 3a010a47..01fe528b 100644 --- a/Simple.Data.Ado/BulkUpdater.cs +++ b/Simple.Data.Ado/BulkUpdater.cs @@ -35,7 +35,7 @@ public int Update(AdoAdapter adapter, string tableName, 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 e4ea388e..10ba8a21 100644 --- a/Simple.Data.Ado/CommandTemplate.cs +++ b/Simple.Data.Ado/CommandTemplate.cs @@ -31,7 +31,7 @@ public Dictionary Index 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(adapter.GetSchema(), command, parameterValues)) 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 582ab84b..a6881a78 100644 --- a/Simple.Data.Ado/DataReaderEnumerable.cs +++ b/Simple.Data.Ado/DataReaderEnumerable.cs @@ -147,17 +147,9 @@ private bool EndRead() private void ExecuteReader() { - try - { - _command.WriteTrace(); - _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 3192ff67..7adbfba0 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)); + } + internal static void DisposeCommandAndReader(IDbConnection connection, IDbCommand command, IDataReader reader) { using (connection) 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/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/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/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index 4a160250..2f03a126 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -45,7 +45,7 @@ public IEnumerable Execute(IDictionary suppliedParame 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; @@ -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,12 +94,11 @@ public IEnumerable ExecuteReader(IDbCommand command) private static IEnumerable ExecuteNonQuery(IDbCommand command) { - command.WriteTrace(); #if(DEBUG) Trace.TraceInformation("ExecuteNonQuery", "Simple.Data.SqlTest"); #endif command.Connection.OpenIfClosed(); - command.ExecuteNonQuery(); + command.TryExecuteNonQuery(); return Enumerable.Empty(); } diff --git a/Simple.Data.Ado/Schema/Table.cs b/Simple.Data.Ado/Schema/Table.cs index 89304174..546ea7e9 100644 --- a/Simple.Data.Ado/Schema/Table.cs +++ b/Simple.Data.Ado/Schema/Table.cs @@ -92,6 +92,18 @@ public Column FindColumn(string columnName) } } + 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); diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index 46a51681..f0c148cf 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -64,6 +64,7 @@ + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj index 6b5d94fd..87873058 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj @@ -59,6 +59,7 @@ Resources.resx + diff --git a/Simple.Data.SqlServer/SqlCustomInserter.cs b/Simple.Data.SqlServer/SqlCustomInserter.cs new file mode 100644 index 00000000..082b6e43 --- /dev/null +++ b/Simple.Data.SqlServer/SqlCustomInserter.cs @@ -0,0 +1,127 @@ +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); + if (resultRequired) + { + insertSql.Append(" OUTPUT INSERTED.*"); + } + insertSql.AppendFormat(" VALUES ({0})", valueList); + + if (identityInsert) + { + insertSql.AppendFormat("; SET IDENTITY_INSERT {0} OFF; ", table.QualifiedName); + } + + if (resultRequired) + { + 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.SqlTest/InsertTests.cs b/Simple.Data.SqlTest/InsertTests.cs index cc8ddd86..5fd62439 100644 --- a/Simple.Data.SqlTest/InsertTests.cs +++ b/Simple.Data.SqlTest/InsertTests.cs @@ -2,6 +2,7 @@ using System.Dynamic; using System.Linq; using NUnit.Framework; +using Simple.Data.Ado; using Simple.Data.SqlTest.Resources; namespace Simple.Data.SqlTest @@ -28,6 +29,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() { diff --git a/Simple.Data/Adapter.cs b/Simple.Data/Adapter.cs index b4e7fc75..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, @@ -367,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) @@ -488,7 +491,18 @@ private static void UpsertManyWithoutResults(string tableName, IEnumerable new SimpleRecord(dict, table.GetQualifiedName(), dataStrategy))); } } } diff --git a/Simple.Data/Commands/ICommand.cs b/Simple.Data/Commands/ICommand.cs index 8ad3d20d..d620971c 100644 --- a/Simple.Data/Commands/ICommand.cs +++ b/Simple.Data/Commands/ICommand.cs @@ -26,8 +26,6 @@ interface ICommand /// The arguments from the method invocation. /// object Execute(DataStrategy dataStrategy, DynamicTable table, InvokeMemberBinder binder, object[] args); - - } interface IQueryCompatibleCommand diff --git a/Simple.Data/DataStrategy.cs b/Simple.Data/DataStrategy.cs index c4f56de7..c368352f 100644 --- a/Simple.Data/DataStrategy.cs +++ b/Simple.Data/DataStrategy.cs @@ -64,11 +64,11 @@ internal bool TryInvokeFunction(String functionName, Func 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; @@ -128,5 +128,15 @@ object ICloneable.Clone() } protected internal abstract DataStrategy Clone(); + + public dynamic WithOptions(OptionsBase options) + { + return new DataStrategyWithOptions(this, options); + } + + public virtual dynamic ClearOptions() + { + return this; + } } } diff --git a/Simple.Data/DataStrategyWithOptions.cs b/Simple.Data/DataStrategyWithOptions.cs new file mode 100644 index 00000000..1b12b499 --- /dev/null +++ b/Simple.Data/DataStrategyWithOptions.cs @@ -0,0 +1,50 @@ +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; + } + + protected internal override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) + { + return _wrappedStrategy.ExecuteFunction(out result, command); + } + + protected internal override Database 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.cs b/Simple.Data/Database.cs index e2ca1185..63b97921 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -83,7 +83,7 @@ public SimpleTransaction BeginTransaction(IsolationLevel isolationLevel) return SimpleTransaction.Begin(this, isolationLevel); } - protected override bool ExecuteFunction(out object result, Commands.ExecuteFunctionCommand command) + protected internal override bool ExecuteFunction(out object result, Commands.ExecuteFunctionCommand command) { return command.Execute(out result); } 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/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 8af9a5e0..40194a95 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -86,6 +86,7 @@ + @@ -96,6 +97,7 @@ + diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 11345d97..30a60c9c 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -134,7 +134,7 @@ 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); } From b4a8347ec1da794367729cea524aef4eb299f1cd Mon Sep 17 00:00:00 2001 From: markrendle Date: Wed, 29 Aug 2012 11:33:55 +0100 Subject: [PATCH 068/160] Fixed problem with ConcreteTypeCreator in Medium Trust --- .gitignore | 19 ++++++++++++++++--- .../ProviderAssemblyAttributeBase.cs | 4 ++-- Simple.Data.Ado/ProviderHelper.cs | 2 +- Simple.Data.sln | 17 +++++++++++++++-- Simple.Data/AssemblyEx.cs | 13 +++++++++++++ Simple.Data/ConcreteTypeCreator.cs | 5 +++-- Simple.Data/MefHelper.cs | 2 +- Simple.Data/PropertySetterBuilder.cs | 8 ++++---- Simple.Data/Simple.Data.csproj | 1 + packages/repositories.config | 15 ++++++++------- 10 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 Simple.Data/AssemblyEx.cs diff --git a/.gitignore b/.gitignore index 064a6a9b..ee5e5292 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,19 @@ NDependOut *_mm_cache.bin Simple.Data.sln.DotSettings.user Simple.Data/Simple.Data.idc -.DS_Store -mono-release-* -*ncrunch* +.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/ \ No newline at end of file diff --git a/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs b/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs index a7443248..9f98a15c 100644 --- a/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs +++ b/Simple.Data.Ado/ProviderAssemblyAttributeBase.cs @@ -40,7 +40,7 @@ public static IEnumerable Get(Assembly assembly) { foreach (var referencedAssembly in assembly.GetReferencedAssemblies()) { - if (AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().All(a => a.GetName().FullName != referencedAssembly.FullName)) + if (AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().All(a => a.GetFullName() != referencedAssembly.FullName)) { try { @@ -56,7 +56,7 @@ public static IEnumerable Get(Assembly assembly) cad => typeof (ProviderAssemblyAttributeBase).IsAssignableFrom(cad.Constructor.DeclaringType)); if (hasAttribute) { - assembly = Assembly.Load(assembly.GetName()); + assembly = Assembly.Load(assembly.GetFullName()); } else { diff --git a/Simple.Data.Ado/ProviderHelper.cs b/Simple.Data.Ado/ProviderHelper.cs index a13bb58a..870ebce9 100644 --- a/Simple.Data.Ado/ProviderHelper.cs +++ b/Simple.Data.Ado/ProviderHelper.cs @@ -208,7 +208,7 @@ private static bool LoadUsingAssemblyAttribute(string connectionString, ICollect private static List LoadAssemblyAttributes() { var attributes = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => a.GetName().Name.StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase)) + .Where(a => a.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase)) .SelectMany(ProviderAssemblyAttributeBase.Get) .ToList(); diff --git a/Simple.Data.sln b/Simple.Data.sln index 0f7c3cea..63276935 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}" @@ -55,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfilingApp", "ProfilingAp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simple.Data.InMemoryTest", "Simple.Data.InMemoryTest\Simple.Data.InMemoryTest.csproj", "{1B6A87C0-4ACA-4411-8879-844537A52126}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediumTrustApp", "MediumTrustApp\MediumTrustApp.csproj", "{CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -225,6 +227,16 @@ Global {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|Mixed Platforms.Build.0 = Release|Any CPU {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|x86.ActiveCfg = Release|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Any CPU.Build.0 = Release|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -237,6 +249,7 @@ Global {70536BA8-AF0D-46F3-B04C-45177F56B320} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {E9160373-8BD2-4D69-B88E-1D3B5BC0A6FB} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {1B6A87C0-4ACA-4411-8879-844537A52126} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} + {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {9AAF3008-8033-4A26-93D2-97928E7801E9} = {2AAC5DFA-3851-4324-A51F-5BC46BEB777C} EndGlobalSection GlobalSection(NDepend) = preSolution 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/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/MefHelper.cs b/Simple.Data/MefHelper.cs index baa76089..dab3d1af 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -95,7 +95,7 @@ private static CompositionContainer CreateAppDomainContainer() private static bool IsSimpleDataAssembly(Assembly assembly) { - return assembly.GetName().Name.StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); + return assembly.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); } } } diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 7084b0e9..2282f393 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -298,7 +298,7 @@ private TryExpression CreateTrySimpleAssign() 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<>)) { @@ -313,7 +313,7 @@ private TryExpression CreateTrySimpleAssign() 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))); } var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType)); @@ -323,7 +323,7 @@ private TryExpression CreateTrySimpleAssign() 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())); } @@ -344,7 +344,7 @@ private TryExpression CreateTrySimpleArrayAssign() 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())); } diff --git a/Simple.Data/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 40194a95..38dd6f34 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -57,6 +57,7 @@ + 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 From e1556cabc812684ce468f8b3e2806b79902fb422 Mon Sep 17 00:00:00 2001 From: markrendle Date: Wed, 29 Aug 2012 12:54:42 +0100 Subject: [PATCH 069/160] Use system.diagnostics switches config section for trace levels --- Simple.Data/Database.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 63b97921..0039fec7 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -16,7 +16,7 @@ namespace Simple.Data /// public sealed partial class Database : DataStrategy { - private static readonly SimpleDataConfigurationSection Configuration; + private static SimpleDataConfigurationSection _configuration; private static readonly IDatabaseOpener DatabaseOpener; private static IPluralizer _pluralizer; @@ -26,10 +26,23 @@ public sealed partial class Database : DataStrategy static Database() { DatabaseOpener = new DatabaseOpener(); - Configuration = - (SimpleDataConfigurationSection) ConfigurationManager.GetSection("simpleData/simpleDataConfiguration") - ?? new SimpleDataConfigurationSection(); - TraceLevel = Configuration.TraceLevel; + LoadTraceLevelFromConfig(); + } + + private static void LoadTraceLevelFromConfig() + { + _configuration = + (SimpleDataConfigurationSection) ConfigurationManager.GetSection("simpleData/simpleDataConfiguration"); + if (_configuration != null) + { + Trace.TraceWarning("SimpleDataConfiguration section is obsolete; use system.diagnostics switches instead."); + TraceLevel = _configuration.TraceLevel; + } + else + { + var traceSwitch = new TraceSwitch("Simple.Data", "", TraceLevel.Info.ToString()); + TraceLevel = traceSwitch.Level; + } } /// @@ -127,7 +140,7 @@ public static void StopUsingMockAdapter() private static TraceLevel? _traceLevel; public static TraceLevel TraceLevel { - get { return _traceLevel ?? Configuration.TraceLevel; } + get { return _traceLevel ?? _configuration.TraceLevel; } set { _traceLevel = value; } } From 590dd559d04390572036f4d01c464a44f8026675 Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 30 Aug 2012 00:10:18 +0100 Subject: [PATCH 070/160] Automatically cast from SimpleQuery to collections and such where possible (issue #219) --- ProfilingApp/CastTask.cs | 31 ++++++++++ ProfilingApp/ProfilingApp.csproj | 1 + ProfilingApp/Program.cs | 4 +- ProfilingApp/Resources/DatabaseResetSql.txt | 2 +- .../SimpleQueryConversionTests.cs | 58 +++++++++++++++++++ Simple.Data/SimpleQuery.cs | 50 ++++++++++++++++ 6 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 ProfilingApp/CastTask.cs create mode 100644 Simple.Data.InMemoryTest/SimpleQueryConversionTests.cs 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/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/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index f2c8b35e..0b9705a9 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -106,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. /// From 4583cee0cc9579508c25c23dfc1a4a8cc8fbafc7 Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 30 Aug 2012 00:26:56 +0100 Subject: [PATCH 071/160] Don't use ApplyLimit with With clauses --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 13 ++++++------- .../Simple.Data.InMemoryTest.csproj | 1 + 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 208ae977..7c2ae014 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("0.17.1.1")] -[assembly: AssemblyFileVersion("0.17.1.1")] +[assembly: AssemblyVersion("0.18.0.0")] +[assembly: AssemblyFileVersion("0.18.0.0")] diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index ebefb932..2dd50b14 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -151,7 +151,7 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, In } else { - ApplyPaging(commandBuilders, mainCommandBuilder, skipClause, takeClause, queryPager); + ApplyPaging(commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); } } return commandBuilders.ToArray(); @@ -168,21 +168,20 @@ private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuild commandBuilders.Add(commandBuilder); } - private void ApplyPaging(List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, - TakeClause takeClause, IQueryPager queryPager) + private void ApplyPaging(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); + int skip = skipClause == null ? 0 : skipClause.Count; + int take = takeClause == null ? maxInt : takeClause.Count; + commandTexts = queryPager.ApplyPaging(mainCommandBuilder.Text, skip, take); } commandBuilders.AddRange( diff --git a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj index 18d770c6..9ef4977c 100644 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj @@ -60,6 +60,7 @@ + From 4248865cf2b82650df7d41e559f6f1a93fa882e1 Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 30 Aug 2012 00:48:10 +0100 Subject: [PATCH 072/160] Fixed joined table ordering issue (#221) --- Simple.Data.Ado/QueryBuilderBase.cs | 14 ++++++++++---- Simple.Data.SqlTest/QueryTest.cs | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Simple.Data.Ado/QueryBuilderBase.cs b/Simple.Data.Ado/QueryBuilderBase.cs index 74b23fb8..93c4c405 100644 --- a/Simple.Data.Ado/QueryBuilderBase.cs +++ b/Simple.Data.Ado/QueryBuilderBase.cs @@ -303,13 +303,19 @@ protected virtual void HandleOrderBy() protected string ToOrderByDirective(OrderByClause item) { string name; - if (_columns.Any(r => (!string.IsNullOrWhiteSpace(r.GetAlias())) && r.GetAlias().Equals(item.Reference.GetName()))) + 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 - { - name = _table.FindColumn(item.Reference.GetName()).QualifiedName; + 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; diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 8bb427e4..bcfa395d 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -427,6 +428,20 @@ public void SelfJoinShouldNotThrowException() Assert.AreEqual(1, kingsSubordinates.Count); } + + [Test] + public void OrderByOnJoinedColumnShouldUseJoinedColumn() + { + var traceListener = new TestTraceListener(); + 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(); + Trace.Listeners.Remove(traceListener); + Assert.Greater(traceListener.Output.IndexOf("order by [manager].[name]", StringComparison.OrdinalIgnoreCase), 0); + } [Test] public void CanFetchMoreThanOneHundredRows() From 8722ebab0c32cd0930bcbfcd195fdb82f7e1bdaf Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 30 Aug 2012 15:54:53 +0100 Subject: [PATCH 073/160] Release 0.18 --- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- replacenugetversion.sh | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index bfa07983..24c7444c 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.17.1.1 + 1.0.0-rc1 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index aae58b35..f85d4a54 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.17.1.1 + 1.0.0-rc1 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 2c0bc50d..b3e8dfbb 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 0.17.1.1 + 1.0.0-rc1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,7 +12,7 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + + \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 1d11f053..70d3aacf 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-rc1 + 0.18.0.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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 128ac7fd..59ee0f5e 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-rc1 + 0.18.0.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 167c9f14b45015c552af37ede67863e0b4044e3f Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 20 Sep 2012 19:17:17 +0100 Subject: [PATCH 075/160] Fixes for issues #223 and #224 --- Simple.Data.Ado/Schema/DatabaseSchema.cs | 2 +- Simple.Data.Ado/Schema/ProcedureCollection.cs | 46 +++++++++++-------- Simple.Data.Ado/SimpleReferenceFormatter.cs | 2 +- Simple.Data.BehaviourTest/FindTest.cs | 19 ++++++-- Simple.Data.BehaviourTest/UpdateTest.cs | 2 +- Simple.Data.SqlServer/SqlCustomInserter.cs | 14 +++--- Simple.Data.SqlTest/DatabaseHelper.cs | 23 +++++++--- Simple.Data.SqlTest/ProcedureTest.cs | 18 ++++++++ .../Resources/DatabaseReset.txt | 18 ++++++++ Simple.Data.SqlTest/TransactionTests.cs | 14 ++++++ Simple.Data.SqlTest/UpdateTests.cs | 1 - .../Commands/ExecuteFunctionCommand.cs | 4 +- Simple.Data/DataStrategy.cs | 2 +- Simple.Data/DataStrategyWithOptions.cs | 20 +++++++- Simple.Data/Database.cs | 2 +- Simple.Data/DynamicSchema.cs | 2 +- Simple.Data/MathReference.cs | 25 ++++++++++ Simple.Data/ObjectReference.cs | 15 ++++++ Simple.Data/SimpleTransaction.cs | 22 +++++---- 19 files changed, 192 insertions(+), 59 deletions(-) diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 8d85aea4..1290f01d 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -107,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) diff --git a/Simple.Data.Ado/Schema/ProcedureCollection.cs b/Simple.Data.Ado/Schema/ProcedureCollection.cs index 303aeaac..d66407e0 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; } /// @@ -27,15 +29,7 @@ public ProcedureCollection(IEnumerable procedures) : base(procedures. /// A if a match is found; otherwise, null. public Procedure Find(string procedureName) { - if (procedureName.Contains('.')) - { - var schemaDotprocedure = procedureName.Split('.'); - if (schemaDotprocedure.Length != 2) throw new InvalidOperationException("Could not resolve qualified procedure name."); - return Find(schemaDotprocedure[1], schemaDotprocedure[0]); - } - var procedure = FindprocedureWithName(procedureName.Homogenize()) - ?? FindprocedureWithPluralName(procedureName.Homogenize()) - ?? FindprocedureWithSingularName(procedureName.Homogenize()); + var procedure = FindImpl(procedureName); if (procedure == null) { @@ -46,16 +40,32 @@ public Procedure Find(string procedureName) } 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."); - return Find(schemaDotprocedure[1], schemaDotprocedure[0]) != null; + return Find(schemaDotprocedure[1], schemaDotprocedure[0]); } - return (FindprocedureWithName(procedureName.Homogenize()) + if (!string.IsNullOrWhiteSpace(_defaultSchema)) + { + return Find(procedureName, _defaultSchema); + } + return FindprocedureWithName(procedureName.Homogenize()) ?? FindprocedureWithPluralName(procedureName.Homogenize()) - ?? FindprocedureWithSingularName(procedureName.Homogenize())) != null; + ?? FindprocedureWithSingularName(procedureName.Homogenize()); } /// @@ -91,16 +101,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/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index 96761642..090b9504 100644 --- a/Simple.Data.Ado/SimpleReferenceFormatter.cs +++ b/Simple.Data.Ado/SimpleReferenceFormatter.cs @@ -72,7 +72,7 @@ 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)); } diff --git a/Simple.Data.BehaviourTest/FindTest.cs b/Simple.Data.BehaviourTest/FindTest.cs index 800fe64e..32860526 100644 --- a/Simple.Data.BehaviourTest/FindTest.cs +++ b/Simple.Data.BehaviourTest/FindTest.cs @@ -91,7 +91,7 @@ public void TestFindLessThanOrEqualWithInt32() public void TestFindModulo() { _db.Users.Find(_db.Users.Id % 2 == 1); - GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where [dbo].[Users].[Id] % @p1 = @p2"); + GeneratedSqlIs("select " + UsersColumns + " from [dbo].[Users] where ([dbo].[Users].[Id] % @p1) = @p2"); Parameter(0).Is(2); Parameter(1).Is(1); } @@ -100,15 +100,24 @@ public void TestFindModulo() 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); } @@ -116,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); } @@ -124,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); } diff --git a/Simple.Data.BehaviourTest/UpdateTest.cs b/Simple.Data.BehaviourTest/UpdateTest.cs index 28511e61..c5ba5a81 100644 --- a/Simple.Data.BehaviourTest/UpdateTest.cs +++ b/Simple.Data.BehaviourTest/UpdateTest.cs @@ -76,7 +76,7 @@ public void TestUpdateWithNamedArgumentsUsingDifferentCase() public void TestUpdateWithNamedArgumentsUsingExpression() { _db.Users.UpdateAll(Age: _db.Users.Age + 1); - GeneratedSqlIs("update [dbo].[Users] set [Age] = [dbo].[Users].[Age] + @p1"); + GeneratedSqlIs("update [dbo].[Users] set [Age] = ([dbo].[Users].[Age] + @p1)"); Parameter(0).Is(1); } diff --git a/Simple.Data.SqlServer/SqlCustomInserter.cs b/Simple.Data.SqlServer/SqlCustomInserter.cs index 082b6e43..c27bb0a1 100644 --- a/Simple.Data.SqlServer/SqlCustomInserter.cs +++ b/Simple.Data.SqlServer/SqlCustomInserter.cs @@ -27,10 +27,6 @@ public IDictionary Insert(AdoAdapter adapter, string tableName, insertSql.AppendFormat("SET IDENTITY_INSERT {0} ON; ", table.QualifiedName); } insertSql.AppendFormat("INSERT INTO {0} ({1})", table.QualifiedName, columnList); - if (resultRequired) - { - insertSql.Append(" OUTPUT INSERTED.*"); - } insertSql.AppendFormat(" VALUES ({0})", valueList); if (identityInsert) @@ -40,8 +36,14 @@ public IDictionary Insert(AdoAdapter adapter, string tableName, if (resultRequired) { - return ExecuteSingletonQuery(adapter, insertSql.ToString(), dataDictionary.Keys, - dataDictionary.Values, transaction); + 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; diff --git a/Simple.Data.SqlTest/DatabaseHelper.cs b/Simple.Data.SqlTest/DatabaseHelper.cs index ad358e9c..f7508de8 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; @@ -24,17 +25,25 @@ public static dynamic Open() public static void Reset() { - var provider = new SqlServer.SqlConnectionProvider(); - 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/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index 0ebe7576..04235e39 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -49,6 +49,24 @@ public void FindGetCustomerCountUsingIndexerAndInvokeTest() 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); + } + #if DEBUG // Trace is only written for DEBUG build [Test] public void GetCustomerCountSecondCallExecutesNonQueryTest() diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index d00f0b0c..c9c381b7 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -1,5 +1,9 @@ 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')) @@ -329,6 +333,20 @@ BEGIN 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 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 399d986a..b4245e66 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -106,7 +106,6 @@ public void TestUpdateAllWithNoMatchingRows() } [Test] - [Ignore] public void TestUpdateWithJoinCriteriaOnCompoundKeyTable() { var db = DatabaseHelper.Open(); 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/DataStrategy.cs b/Simple.Data/DataStrategy.cs index c368352f..8ec5b7c9 100644 --- a/Simple.Data/DataStrategy.cs +++ b/Simple.Data/DataStrategy.cs @@ -113,7 +113,7 @@ internal DynamicSchema SetMemberAsSchema(ObjectReference reference, DynamicSchem return (DynamicSchema) _members[reference.GetName()]; } - protected internal abstract Database GetDatabase(); + protected internal abstract DataStrategy GetDatabase(); internal bool IsExpressionFunction(string name, object[] args) { diff --git a/Simple.Data/DataStrategyWithOptions.cs b/Simple.Data/DataStrategyWithOptions.cs index 1b12b499..82aef797 100644 --- a/Simple.Data/DataStrategyWithOptions.cs +++ b/Simple.Data/DataStrategyWithOptions.cs @@ -1,4 +1,5 @@ -using Simple.Data.Commands; +using System.Data; +using Simple.Data.Commands; namespace Simple.Data { @@ -21,12 +22,27 @@ public override Adapter GetAdapter() 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 Database GetDatabase() + protected internal override DataStrategy GetDatabase() { return _wrappedStrategy.GetDatabase(); } diff --git a/Simple.Data/Database.cs b/Simple.Data/Database.cs index 0039fec7..c8d1e5e0 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -101,7 +101,7 @@ protected internal override bool ExecuteFunction(out object result, Commands.Exe return command.Execute(out result); } - protected internal override Database GetDatabase() + protected internal override DataStrategy GetDatabase() { return this; } 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/MathReference.cs b/Simple.Data/MathReference.cs index af947861..c5fbf63b 100644 --- a/Simple.Data/MathReference.cs +++ b/Simple.Data/MathReference.cs @@ -136,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/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 2c00914a..7fe95ad4 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,11 +93,25 @@ 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; diff --git a/Simple.Data/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 30a60c9c..08338dde 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -13,14 +13,14 @@ 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, IsolationLevel isolationLevel) + private SimpleTransaction(IAdapterWithTransactions adapter, DataStrategy database, IsolationLevel isolationLevel) { if (adapter == null) throw new ArgumentNullException("adapter"); if (database == null) throw new ArgumentNullException("database"); @@ -49,38 +49,40 @@ private void Begin(string name) _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; } - public static SimpleTransaction Begin(Database database, IsolationLevel isolationLevel) + public static SimpleTransaction Begin(DataStrategy database, IsolationLevel isolationLevel) { var transaction = CreateTransaction(database, isolationLevel); transaction.Begin(); return transaction; } - private static SimpleTransaction CreateTransaction(Database database, IsolationLevel isolationLevel = IsolationLevel.Unspecified) + 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, isolationLevel); } - - internal Database Database + internal DataStrategy Database { - get { return _database; } + get + { + return _database; + } } /// @@ -139,7 +141,7 @@ protected internal override bool ExecuteFunction(out object result, ExecuteFunct return command.Execute(out result, _adapterTransaction); } - protected internal override Database GetDatabase() + protected internal override DataStrategy GetDatabase() { return _database; } From ee81fc33df93ca746aae4d3d28d90b1bad0d5e6b Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 21 Sep 2012 11:51:13 +0100 Subject: [PATCH 076/160] Pass schema name to CreateGetDelegate method --- Simple.Data.BehaviourTest/GetTest.cs | 8 ++++++++ Simple.Data/Commands/GetCommand.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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/Commands/GetCommand.cs b/Simple.Data/Commands/GetCommand.cs index 9a342030..e30547e7 100644 --- a/Simple.Data/Commands/GetCommand.cs +++ b/Simple.Data/Commands/GetCommand.cs @@ -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); From 5369740a55cc74aab5a74e6367735d26c628e715 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 21 Sep 2012 16:57:47 +0100 Subject: [PATCH 077/160] 0.18.1 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 2 +- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data.SqlTest/ProcedureTest.cs | 8 ++++++++ Simple.Data.SqlTest/Resources/DatabaseReset.txt | 9 +++++++++ Simple.Data/Simple.Data.nuspec | 2 +- 8 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 7c2ae014..264f6dd7 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("0.18.0.0")] -[assembly: AssemblyFileVersion("0.18.0.0")] +[assembly: AssemblyVersion("0.18.1.0")] +[assembly: AssemblyFileVersion("0.18.1.0")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 2479b0ba..800043cb 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.18.0.1 + 1.0.0-rc2 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index af17f67e..fb9e88f4 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.18.0.1 + 1.0.0-rc2 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 8d3f2b21..9c268c05 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 0.18.0.1 + 1.0.0-rc2 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 70d3aacf..db31cf0e 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 0.18.0.1 + 1.0.0-rc2 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.SqlTest/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index 04235e39..38294b6f 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -67,6 +67,14 @@ public void SchemaQualifiedProcedureResolutionTest() 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() diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index c9c381b7..4db2793e 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -12,6 +12,8 @@ IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCu 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]')) @@ -351,6 +353,13 @@ 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 diff --git a/Simple.Data/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 59ee0f5e..5f1b0ade 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 0.18.0.1 + 1.0.0-rc2 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From e131302266d9a7e1d86f0d9da90d60f15a8c7e29 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 2 Oct 2012 13:21:19 +0100 Subject: [PATCH 078/160] Fixed NotEqual operator in ExpressionFormatter --- Simple.Data.Ado/ExpressionFormatterBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simple.Data.Ado/ExpressionFormatterBase.cs b/Simple.Data.Ado/ExpressionFormatterBase.cs index 24159500..fe5c45a1 100644 --- a/Simple.Data.Ado/ExpressionFormatterBase.cs +++ b/Simple.Data.Ado/ExpressionFormatterBase.cs @@ -57,7 +57,7 @@ private string LogicalExpressionToWhereClause(SimpleExpression expression) private string EqualExpressionToWhereClause(SimpleExpression 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, "="); + if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, Operators.Equal); return FormatAsComparison(expression, Operators.Equal); } @@ -65,7 +65,7 @@ private string EqualExpressionToWhereClause(SimpleExpression expression) private string NotEqualExpressionToWhereClause(SimpleExpression 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, "!="); + if (CommonTypes.Contains(expression.RightOperand.GetType())) return FormatAsComparison(expression, Operators.NotEqual); return FormatAsComparison(expression, Operators.NotEqual); } From c84f6b19118f66e9896eb89df5c50230fea7d744 Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 8 Oct 2012 13:48:53 +0100 Subject: [PATCH 079/160] Removed MediumTrust app --- Simple.Data.sln | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Simple.Data.sln b/Simple.Data.sln index 63276935..d366f8c9 100644 --- a/Simple.Data.sln +++ b/Simple.Data.sln @@ -55,8 +55,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfilingApp", "ProfilingAp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simple.Data.InMemoryTest", "Simple.Data.InMemoryTest\Simple.Data.InMemoryTest.csproj", "{1B6A87C0-4ACA-4411-8879-844537A52126}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediumTrustApp", "MediumTrustApp\MediumTrustApp.csproj", "{CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -227,16 +225,6 @@ Global {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|Mixed Platforms.Build.0 = Release|Any CPU {1B6A87C0-4ACA-4411-8879-844537A52126}.Release|x86.ActiveCfg = Release|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Debug|x86.ActiveCfg = Debug|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Any CPU.Build.0 = Release|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -249,7 +237,6 @@ Global {70536BA8-AF0D-46F3-B04C-45177F56B320} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {E9160373-8BD2-4D69-B88E-1D3B5BC0A6FB} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {1B6A87C0-4ACA-4411-8879-844537A52126} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} - {CCAEC8D8-0090-4DE2-AF6C-44CF75268A0D} = {182AEEFE-9B89-4264-BCED-91A00D1EF896} {9AAF3008-8033-4A26-93D2-97928E7801E9} = {2AAC5DFA-3851-4324-A51F-5BC46BEB777C} EndGlobalSection GlobalSection(NDepend) = preSolution From cd2ec63a0bb8a39a00acb246fcaa10a07e59f58a Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 8 Oct 2012 14:34:59 +0100 Subject: [PATCH 080/160] Azure SQL Database SimpleTest for TeamCity --- Simple.Data.SqlTest/DatabaseHelper.cs | 10 +++++-- Simple.Data.SqlTest/DatabaseOpenerTests.cs | 9 ++++-- .../Resources/DatabaseReset.txt | 30 ++++++++++++------- .../SchemaTests/DatabaseSchemaTests.cs | 3 +- Simple.Data.SqlTest/SetupFixture.cs | 2 +- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Simple.Data.SqlTest/DatabaseHelper.cs b/Simple.Data.SqlTest/DatabaseHelper.cs index f7508de8..1c1154a8 100644 --- a/Simple.Data.SqlTest/DatabaseHelper.cs +++ b/Simple.Data.SqlTest/DatabaseHelper.cs @@ -11,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() { diff --git a/Simple.Data.SqlTest/DatabaseOpenerTests.cs b/Simple.Data.SqlTest/DatabaseOpenerTests.cs index 36a9eb79..ac3dbcb4 100644 --- a/Simple.Data.SqlTest/DatabaseOpenerTests.cs +++ b/Simple.Data.SqlTest/DatabaseOpenerTests.cs @@ -21,6 +21,11 @@ public void Setup() [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); @@ -30,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); } @@ -45,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/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 4db2793e..21a78413 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -87,7 +87,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC) ; CREATE TABLE [dbo].[UsersWithChar] ( [Id] INT IDENTITY (1, 1) NOT NULL, @@ -97,7 +97,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_UsersWithChar] PRIMARY KEY CLUSTERED ([Id] ASC) ; CREATE TABLE [dbo].[Employee]( [Id] [int] IDENTITY(1,1) NOT NULL, @@ -114,7 +114,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderId] ASC) ; CREATE TABLE [dbo].[OrderItems] ( [OrderItemId] INT IDENTITY (1, 1) NOT NULL, @@ -124,7 +124,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_OrderItems] PRIMARY KEY CLUSTERED ([OrderItemId] ASC) ; CREATE TABLE [dbo].[Images]( [Id] [int] NOT NULL, @@ -132,7 +132,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_Images] PRIMARY KEY CLUSTERED ([Id] ASC) ; CREATE TABLE [dbo].[Items] ( [ItemId] INT IDENTITY (1, 1) NOT NULL, @@ -141,7 +141,7 @@ BEGIN ); 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); + ADD CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED ([ItemId] ASC) ; CREATE TABLE [dbo].[Customers] ( [CustomerId] INT IDENTITY (1, 1) NOT NULL, @@ -150,15 +150,17 @@ BEGIN ); 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); + 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) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] + CONSTRAINT [PK_Notes] PRIMARY KEY CLUSTERED ( [NoteId] ASC) ) - CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int); + CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int) + + CREATE CLUSTERED INDEX [ci_azure_fixup_dbo_PagingTest] ON [dbo].[PagingTest] ( [Id] ASC ) CREATE TABLE [dbo].[Blobs]( [Id] [int] NOT NULL, @@ -188,6 +190,8 @@ BEGIN [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, @@ -293,6 +297,8 @@ 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 @@ -301,6 +307,8 @@ GO CREATE TABLE [test].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null); GO +CREATE CLUSTERED INDEX [ci_azure_fixup_test_SchemaTable] ON [test].[SchemaTable] ( [Id] ASC) +GO INSERT INTO [test].[SchemaTable] VALUES (2, 'Pass') GO @@ -374,7 +382,7 @@ CREATE TABLE [dbo].[GroupTestMaster] ( ); 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); + ADD CONSTRAINT [PK_GroupTestMaster] PRIMARY KEY CLUSTERED ([Id] ASC) ; GO CREATE TABLE [dbo].[GroupTestDetail] ( [Id] INT IDENTITY (1, 1) NOT NULL, @@ -384,7 +392,7 @@ CREATE TABLE [dbo].[GroupTestDetail] ( ); 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); + 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; 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 9f246abb..3c71b48d 100644 --- a/Simple.Data.SqlTest/SetupFixture.cs +++ b/Simple.Data.SqlTest/SetupFixture.cs @@ -19,7 +19,7 @@ public void CreateStoredProcedures() var provider = new SqlServer.SqlConnectionProvider(); Trace.Write("Loaded provider " + provider.GetType().Name); - using (var cn = new SqlConnection(Properties.Settings.Default.ConnectionString)) + using (var cn = new SqlConnection(DatabaseHelper.ConnectionString)) { cn.Open(); using (var cmd = cn.CreateCommand()) From 8bee509226ecfb3718ac2d3276c8d35241bbccf7 Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 8 Oct 2012 14:59:56 +0100 Subject: [PATCH 081/160] Added SQL CLR assembly --- Assemblies/Microsoft.SqlServer.Types.dll | Bin 0 -> 310624 bytes Simple.Data.SqlTest/Simple.Data.SqlTest.csproj | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 Assemblies/Microsoft.SqlServer.Types.dll diff --git a/Assemblies/Microsoft.SqlServer.Types.dll b/Assemblies/Microsoft.SqlServer.Types.dll new file mode 100644 index 0000000000000000000000000000000000000000..a4d5a77766f5dbadf75bbfdb7a5be24d13d52f1b GIT binary patch literal 310624 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

12=e^#)b?$!kB8f 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 z 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 literal 0 HcmV?d00001 diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj index f31ca8af..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 From 13f2be68bda84b871c49ff321977bc37e8ee9d49 Mon Sep 17 00:00:00 2001 From: Marcus Hammarberg Date: Mon, 8 Oct 2012 18:57:11 +0300 Subject: [PATCH 082/160] FindBy is deprecated. Code example updated Update code example to reflect new api as described in http://blog.markrendle.net/2012/10/03/simple-data-change-findby-is-deprecated-for-1-0/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d88f7e0f..aabdbd80 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? From b3fcbd2352a1c4dd09c46876ea92acb2e489538d Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 12 Oct 2012 14:02:47 +0100 Subject: [PATCH 083/160] Slight fix to detail loading --- .../InMemoryAdapterIAdapterWithRelation.cs | 54 +++++++++++++++++++ Simple.Data/Simple.Data.csproj | 1 + Simple.Data/SimpleRecord.cs | 5 +- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Simple.Data/InMemoryAdapterIAdapterWithRelation.cs 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/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 38dd6f34..73f78b88 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -93,6 +93,7 @@ + diff --git a/Simple.Data/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index 1ced3208..65b3ef51 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -95,9 +95,8 @@ private object GetRelatedData(GetMemberBinder binder, IAdapterWithRelation relat { 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))); + : ((IEnumerable>) related).Select( + dict => new SimpleRecord(dict, binder.Name, _database)).ToList(); } return result; From b5e7a920be8c98bd891c82e5c7b146332a70aadb Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 12 Oct 2012 15:46:40 +0100 Subject: [PATCH 084/160] Release 0.18.2.1 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- Simple.Data/SimpleRecord.cs | 1 + cleanup.sh | 3 +-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 264f6dd7..61b7e533 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("0.18.1.0")] -[assembly: AssemblyFileVersion("0.18.1.0")] +[assembly: AssemblyVersion("0.18.2.1")] +[assembly: AssemblyFileVersion("0.18.2.1")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 800043cb..c0b4a5ec 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-rc2 + 0.18.2.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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index fb9e88f4..9e0b91b8 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-rc2 + 0.18.2.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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 9c268c05..331bc6a4 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-rc2 + 0.18.2.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,7 +12,7 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index db31cf0e..fbf9932e 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-rc2 + 0.18.2.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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 5f1b0ade..c33e3825 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-rc2 + 0.18.2.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php diff --git a/Simple.Data/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index 65b3ef51..e7440526 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -97,6 +97,7 @@ private object GetRelatedData(GetMemberBinder binder, IAdapterWithRelation relat ? (object) new SimpleRecord(related as IDictionary, binder.Name, _database) : ((IEnumerable>) related).Select( dict => new SimpleRecord(dict, binder.Name, _database)).ToList(); + _data[binder.Name] = result; } return result; 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 {} \; From 5a61b7063cebb94a8cfc0ed70eae4c2081076c39 Mon Sep 17 00:00:00 2001 From: James Kyburz Date: Mon, 15 Oct 2012 20:23:19 +0200 Subject: [PATCH 085/160] Add failing UpsertBy tests --- Simple.Data.SqlTest/UpsertTests.cs | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Simple.Data.SqlTest/UpsertTests.cs b/Simple.Data.SqlTest/UpsertTests.cs index 526738be..b62a0bd2 100644 --- a/Simple.Data.SqlTest/UpsertTests.cs +++ b/Simple.Data.SqlTest/UpsertTests.cs @@ -414,5 +414,49 @@ public void TestUpsertWithSingleArgumentAndExistingObject() 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 From 48b8a06a306af74ec58e3e388235aefa37850394 Mon Sep 17 00:00:00 2001 From: James Kyburz Date: Mon, 15 Oct 2012 20:23:39 +0200 Subject: [PATCH 086/160] Stop UpsertBy removing data --- Simple.Data/Commands/UpsertByCommand.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Simple.Data/Commands/UpsertByCommand.cs b/Simple.Data/Commands/UpsertByCommand.cs index 58340d4b..428f5cc2 100644 --- a/Simple.Data/Commands/UpsertByCommand.cs +++ b/Simple.Data/Commands/UpsertByCommand.cs @@ -62,7 +62,6 @@ private static IEnumerable> GetCriteria(IEnumerable } criteria.Add(keyFieldName, keyValuePair.Value); - record.Remove(keyValuePair); } return criteria; } From cdec5638cd91b1fde11683ac47ffa3bf6aa239ae Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 15 Oct 2012 20:06:42 +0100 Subject: [PATCH 087/160] Fixed for #239 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data/Commands/UpdateCommand.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index 61b7e533..e45ecdc0 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("0.18.2.1")] -[assembly: AssemblyFileVersion("0.18.2.1")] +[assembly: AssemblyVersion("0.18.3.0")] +[assembly: AssemblyFileVersion("0.18.3.0")] diff --git a/Simple.Data/Commands/UpdateCommand.cs b/Simple.Data/Commands/UpdateCommand.cs index 6adaf6c4..9a2881e4 100644 --- a/Simple.Data/Commands/UpdateCommand.cs +++ b/Simple.Data/Commands/UpdateCommand.cs @@ -71,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; From 44a3589125c198585ee6ae7981e1201826ed57a8 Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 15 Oct 2012 20:48:41 +0100 Subject: [PATCH 088/160] Fixes for issue #227 --- Simple.Data.BehaviourTest/GetCountTest.cs | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Simple.Data.BehaviourTest/GetCountTest.cs diff --git a/Simple.Data.BehaviourTest/GetCountTest.cs b/Simple.Data.BehaviourTest/GetCountTest.cs new file mode 100644 index 00000000..5e745f12 --- /dev/null +++ b/Simple.Data.BehaviourTest/GetCountTest.cs @@ -0,0 +1,45 @@ +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 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 From 5bcbf2ef901ed7538d4aa6baa4a27d9f8a5b2b70 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 6 Nov 2012 12:35:30 +0000 Subject: [PATCH 089/160] Fix for update --- .../Simple.Data.BehaviourTest.csproj | 1 + Simple.Data.SqlTest/UpdateTests.cs | 18 ++++++++++++++++++ Simple.Data/Commands/GetCountCommand.cs | 18 +++++++++++++++--- Simple.Data/DynamicTable.cs | 9 +++++++++ Simple.Data/ObjectReference.cs | 9 +++++++++ Simple.Data/SimpleQuery.cs | 16 ++++++++-------- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj index 93a67694..caca7c23 100644 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj @@ -69,6 +69,7 @@ + diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index b4245e66..35ceb487 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -138,5 +138,23 @@ public void TestUpdateWithTimestamp() 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); + } } } \ No newline at end of file diff --git a/Simple.Data/Commands/GetCountCommand.cs b/Simple.Data/Commands/GetCountCommand.cs index 5f88ad9d..947148d0 100644 --- a/Simple.Data/Commands/GetCountCommand.cs +++ b/Simple.Data/Commands/GetCountCommand.cs @@ -32,12 +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(); + throw new ArgumentException("GetCount expects an expression or no parameters."); } } } diff --git a/Simple.Data/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 54b045fa..51b04494 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -137,6 +137,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); diff --git a/Simple.Data/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 7fe95ad4..16a0472a 100644 --- a/Simple.Data/ObjectReference.cs +++ b/Simple.Data/ObjectReference.cs @@ -175,6 +175,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/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 0b9705a9..5ae82af1 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -698,22 +698,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) @@ -728,22 +728,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) From 54581ae53bc077787d28a0f784eb7192c9e23c01 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 6 Nov 2012 14:49:33 +0000 Subject: [PATCH 090/160] Implemented proper, join-aware paging in SQL Server --- Simple.Data.Ado.Test/ProviderHelperTest.cs | 212 +++++++++--------- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 14 +- Simple.Data.Ado/IQueryPager.cs | 2 +- Simple.Data.Ado/Joiner.cs | 28 ++- Simple.Data.SqlCe40/SqlCe40QueryPager.cs | 2 +- .../SqlCe40QueryPagerTest.cs | 4 +- Simple.Data.SqlServer/SqlQueryPager.cs | 32 +-- Simple.Data.SqlServer/SqlSchemaProvider.cs | 24 +- Simple.Data.SqlTest/QueryTest.cs | 15 ++ .../Resources/DatabaseReset.txt | 3 +- Simple.Data.SqlTest/SqlQueryPagerTest.cs | 52 ++--- 11 files changed, 216 insertions(+), 172 deletions(-) diff --git a/Simple.Data.Ado.Test/ProviderHelperTest.cs b/Simple.Data.Ado.Test/ProviderHelperTest.cs index 24b74a82..a5af8079 100644 --- a/Simple.Data.Ado.Test/ProviderHelperTest.cs +++ b/Simple.Data.Ado.Test/ProviderHelperTest.cs @@ -1,118 +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); - } - - [Test] +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 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/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 2dd50b14..a62a76c1 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -151,7 +151,7 @@ private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, In } else { - ApplyPaging(commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); + ApplyPaging(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); } } return commandBuilders.ToArray(); @@ -168,7 +168,7 @@ private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuild commandBuilders.Add(commandBuilder); } - private void ApplyPaging(List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) + private void ApplyPaging(SimpleQuery query, List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) { const int maxInt = 2147483646; @@ -179,9 +179,17 @@ private void ApplyPaging(List commandBuilders, ICommandBuilder } else { + var table = _adapter.GetSchema().FindTable(query.TableName); + if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) + { + throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); + } + var 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, skip, take); + commandTexts = queryPager.ApplyPaging(mainCommandBuilder.Text, keys, skip, take); } commandBuilders.AddRange( 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/Joiner.cs b/Simple.Data.Ado/Joiner.cs index d514b4f0..c69a896e 100644 --- a/Simple.Data.Ado/Joiner.cs +++ b/Simple.Data.Ado/Joiner.cs @@ -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.SqlCe40/SqlCe40QueryPager.cs b/Simple.Data.SqlCe40/SqlCe40QueryPager.cs index 28ed1e3a..3a777498 100644 --- a/Simple.Data.SqlCe40/SqlCe40QueryPager.cs +++ b/Simple.Data.SqlCe40/SqlCe40QueryPager.cs @@ -19,7 +19,7 @@ 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) + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) { if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0) { diff --git a/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs b/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs index a38dbcd8..e99887b6 100644 --- a/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs +++ b/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs @@ -29,7 +29,7 @@ public void ShouldApplyPagingUsingOrderBy() 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 pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 5, 10); var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant()); Assert.IsTrue(expected.SequenceEqual(modified)); @@ -42,7 +42,7 @@ public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() 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 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/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 057facbc..4e4c7862 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -19,7 +19,7 @@ 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) + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) { var builder = new StringBuilder("WITH __Data AS (SELECT "); @@ -27,20 +27,31 @@ public IEnumerable ApplyPaging(string sql, int skip, int take) var columns = match.Groups[1].Value.Trim(); var fromEtc = match.Groups[2].Value.Trim(); - builder.Append(columns); + builder.Append(string.Join(",", keys)); - var orderBy = ExtractOrderBy(columns, ref fromEtc); + var orderBy = ExtractOrderBy(columns, keys, 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); + builder.AppendFormat("SELECT {0} FROM __Data ", columns); + builder.AppendFormat("JOIN {0} ON ", + keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase))); + builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); + var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); + builder.Append(rest); + + builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take); 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(',') @@ -48,7 +59,7 @@ private static string DequalifyColumns(string original) return string.Join(",", q); } - private static string ExtractOrderBy(string columns, ref string fromEtc) + private static string ExtractOrderBy(string columns, string[] keys, ref string fromEtc) { string orderBy; int index = fromEtc.IndexOf("ORDER BY", StringComparison.InvariantCultureIgnoreCase); @@ -59,14 +70,7 @@ private static string ExtractOrderBy(string columns, ref string fromEtc) } else { - orderBy = "ORDER BY " + columns.Split(',').First().Trim(); - - var aliasIndex = orderBy.IndexOf(" AS [", StringComparison.InvariantCultureIgnoreCase); - - if (aliasIndex > -1) - { - orderBy = orderBy.Substring(0, aliasIndex); - } + orderBy = "ORDER BY " + string.Join(", ", keys); } return orderBy; } diff --git a/Simple.Data.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index d7061fe4..b664c795 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -109,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) @@ -176,8 +181,21 @@ private DataTable GetForeignKeys() private DataTable GetPrimaryKeys(string tableName) { var primaryKeys = GetPrimaryKeys(); - var dataTable = primaryKeys.AsEnumerable().Where(row => row["TABLE_NAME"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)).CopyToDataTable(); - return dataTable; + 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) diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index bcfa395d..df32f1dc 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -413,6 +413,21 @@ public void WithClauseShouldCastToStaticTypeWithEmptyCollection() 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() { diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 21a78413..646ae128 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -160,7 +160,8 @@ BEGIN CREATE TABLE [dbo].[PagingTest] ([Id] int not null, [Dummy] int) - CREATE CLUSTERED INDEX [ci_azure_fixup_dbo_PagingTest] ON [dbo].[PagingTest] ( [Id] ASC ) + ALTER TABLE [dbo].[PagingTest] + ADD CONSTRAINT [PK_PagingTest] PRIMARY KEY CLUSTERED ([Id] ASC) ; CREATE TABLE [dbo].[Blobs]( [Id] [int] NOT NULL, diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index 4a490af7..be5971c2 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -28,57 +28,43 @@ public void ShouldApplyLimitUsingTop() [Test] public void ShouldApplyPagingUsingOrderBy() { - var sql = "select a,b,c from d where a = 1 order by c"; + 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 c) as [_#_] from d where a = 1)" - + " select a,b,c from __data where [_#_] between 6 and 15"}; + "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, 5, 10); - 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 ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() + public void ShouldApplyPagingUsingOrderByKeysIfNotAlreadyOrdered() { - 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"; 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].[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, 10, 20); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[d].[a]"}, 10, 20); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - Assert.IsTrue(expected.SequenceEqual(modified)); + 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"}; - - var pagedSql = new SqlQueryPager().ApplyPaging(sql, 20, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - - [Test] - public void ShouldCopeWithAliasedDefaultSortColumn() - { - 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"}; + "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, 30, 10); - 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]); } } } From 3299f809cee11e5fa3db95ecd95ecf3f675319c9 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 6 Nov 2012 15:08:00 +0000 Subject: [PATCH 091/160] Added BadExpressionException in Join.On, fixes #233 --- .../Query/ExplicitJoinTest.cs | 14 ++++++++++++++ Simple.Data/SimpleQuery.cs | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs index e673e542..62af5f58 100644 --- a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs +++ b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs @@ -276,6 +276,20 @@ 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)); + } + class Activity { public int ID_Activity { get; set; } diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 5ae82af1..2b051032 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -514,7 +514,13 @@ public SimpleQuery OuterJoin(ObjectReference objectReference, out dynamic queryO public SimpleQuery On(SimpleExpression joinExpression) { if (_tempJoinWaitingForOn == null) + { 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)); } @@ -561,9 +567,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 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)); } From 52f477c741cca6be03a6f99f998677cd4e594063 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 6 Nov 2012 15:17:06 +0000 Subject: [PATCH 092/160] Make ToScalar just return first value of first row; fixes issue #230 --- Simple.Data/SimpleQuery.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 2b051032..b9545d9e 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -635,15 +635,11 @@ public dynamic ToScalar() { throw new SimpleDataException("Query returned no rows; cannot return scalar value."); } - if (data.Length != 1) + if (data[0].Count == 0) { - throw new SimpleDataException("Query returned multiple rows; cannot return scalar value."); + throw new SimpleDataException("Selected row contains no values; cannot return scalar value."); } - if (data[0].Count > 1) - { - throw new SimpleDataException("Selected row contains multiple values; cannot return scalar value."); - } - return data[0].Single().Value; + return data[0].First().Value; } public dynamic ToScalarOrDefault() @@ -653,15 +649,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() From 149841f0de44f6c78282f9ba62292d7681983413 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 6 Nov 2012 15:28:20 +0000 Subject: [PATCH 093/160] Fix for issue #228 --- Simple.Data.BehaviourTest/GetCountTest.cs | 14 ++++++++++++++ Simple.Data/Commands/GetCountByCommand.cs | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Simple.Data.BehaviourTest/GetCountTest.cs b/Simple.Data.BehaviourTest/GetCountTest.cs index 5e745f12..17801951 100644 --- a/Simple.Data.BehaviourTest/GetCountTest.cs +++ b/Simple.Data.BehaviourTest/GetCountTest.cs @@ -24,6 +24,20 @@ public void GetCountBasic() 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() { diff --git a/Simple.Data/Commands/GetCountByCommand.cs b/Simple.Data/Commands/GetCountByCommand.cs index 6ed8b233..0bd4a245 100644 --- a/Simple.Data/Commands/GetCountByCommand.cs +++ b/Simple.Data/Commands/GetCountByCommand.cs @@ -27,7 +27,14 @@ 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)); + if (binder.Name.Equals("GetCountBy") || binder.Name.Equals("get_count_by")) + { + ArgumentHelper.CheckFindArgs(args, binder); + } + + 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(); } } From c027ed76855c4724ea13e866e45ae8faac694791 Mon Sep 17 00:00:00 2001 From: Richard Hopton Date: Tue, 27 Nov 2012 14:28:34 +0000 Subject: [PATCH 094/160] Fix for issue #246 --- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 540 +++++++++++------------ 1 file changed, 270 insertions(+), 270 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index a62a76c1..6c4de3e9 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -1,271 +1,271 @@ -namespace Simple.Data.Ado -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Diagnostics; - using System.Linq; - - internal class AdoAdapterQueryRunner - { - private readonly AdoAdapter _adapter; - private readonly AdoAdapterTransaction _transaction; - - public AdoAdapterQueryRunner(AdoAdapter adapter) : this(adapter, null) - { - } - - public AdoAdapterQueryRunner(AdoAdapter adapter, AdoAdapterTransaction transaction) - { - _adapter = adapter; - _transaction = transaction; - } - - public IEnumerable> RunQuery(SimpleQuery query, - out IEnumerable - unhandledClauses) - { - IEnumerable> result; - - if (query.Clauses.OfType().Any()) return RunQueryWithCount(query, out unhandledClauses); - - ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(ref query, out unhandledClauses); - IDbConnection connection = _adapter.CreateConnection(); - if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) - { - var command = - new CommandBuilder(_adapter.GetSchema()).CreateCommand( - _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), - commandBuilders, - 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, _adapter.AdoOptions).ToEnumerable(_adapter.CreateConnection)); - } - - if (query.Clauses.OfType().Any()) - { - result = new EagerLoadingEnumerable(result); - } - - return result; - } - - public IObservable> RunQueryAsObservable(SimpleQuery query, - out - IEnumerable - - unhandledClauses) - { - IDbConnection connection = _adapter.CreateConnection(); - return new QueryBuilder(_adapter).Build(query, out unhandledClauses) - .GetCommand(connection, _adapter.AdoOptions) - .ToObservable(connection, _adapter); - } - - private IEnumerable> RunQueryWithCount(SimpleQuery query, - out IEnumerable - unhandledClauses) - { - WithCountClause withCountClause; - try - { - withCountClause = query.Clauses.OfType().First(); - } - catch (InvalidOperationException) - { - // Rethrow with meaning. - throw new InvalidOperationException("No WithCountClause specified."); - } - - query = query.ClearWithTotalCount(); - var countQuery = query.ClearSkip() - .ClearTake() - .ClearOrderBy() - .ClearWith() - .ClearForUpdate() - .ReplaceSelect(new CountSpecialReference()); - var unhandledClausesList = new List> - { - Enumerable.Empty(), - Enumerable.Empty() - }; - - using (var enumerator = RunQueries(new[] {countQuery, query}, unhandledClausesList).GetEnumerator()) - { - unhandledClauses = unhandledClausesList[1]; - if (!enumerator.MoveNext()) - { - throw new InvalidOperationException(); - } - IDictionary countRow = enumerator.Current.Single(); - withCountClause.SetCount((int) countRow.First().Value); - if (!enumerator.MoveNext()) - { - throw new InvalidOperationException(); - } - return enumerator.Current; - } - } - - private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, - out IEnumerable unhandledClauses) - { - return GetPagedQueryCommandBuilders(ref query, -1, out unhandledClauses); - } - - private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, - out IEnumerable unhandledClauses) - { - var commandBuilders = new List(); - var unhandledClausesList = new List(); - unhandledClauses = unhandledClausesList; - - IEnumerable unhandledClausesForPagedQuery; - ICommandBuilder mainCommandBuilder = new QueryBuilder(_adapter, bulkIndex).Build(query, - out - unhandledClausesForPagedQuery); - unhandledClausesList.AddRange(unhandledClausesForPagedQuery); - - SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); - TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); - - if (skipClause != null || takeClause != null) - { - var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); - if (queryPager == null) - { - Trace.TraceWarning("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(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); - } - } - return commandBuilders.ToArray(); - } - - private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, - List unhandledClausesList) - { - unhandledClausesList.AddRange(query.OfType()); - unhandledClausesList.AddRange(query.OfType()); - query = query.ClearSkip().ClearTake(); - var commandBuilder = new CommandBuilder(mainCommandBuilder.Text, _adapter.GetSchema(), - mainCommandBuilder.Parameters); - commandBuilders.Add(commandBuilder); - } - - 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 && !hasWithClause) - { - commandTexts = queryPager.ApplyLimit(mainCommandBuilder.Text, takeClause.Count); - } - else - { - var table = _adapter.GetSchema().FindTable(query.TableName); - if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) - { - throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); - } - var 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( - commandTexts.Select( - commandText => - new CommandBuilder(commandText, _adapter.GetSchema(), mainCommandBuilder.Parameters))); - } - - private ICommandBuilder[] GetQueryCommandBuilders(ref SimpleQuery query, - out IEnumerable unhandledClauses) - { - if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) - { - return GetPagedQueryCommandBuilders(ref query, out unhandledClauses); - } - return new[] {new QueryBuilder(_adapter).Build(query, out unhandledClauses)}; - } - - private IEnumerable GetQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, - out IEnumerable - unhandledClauses) - { - if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) - { - return GetPagedQueryCommandBuilders(ref query, bulkIndex, out unhandledClauses); - } - return new[] {new QueryBuilder(_adapter, bulkIndex).Build(query, out unhandledClauses)}; - } - - public IEnumerable>> RunQueries(SimpleQuery[] queries, - List - < - IEnumerable - > - unhandledClauses) - { - if (_adapter.ProviderSupportsCompoundStatements && queries.Length > 1) - { - var commandBuilders = new List(); - for (int i = 0; i < queries.Length; i++) - { - IEnumerable unhandledClausesForThisQuery; - commandBuilders.AddRange(GetQueryCommandBuilders(ref queries[i], i, out unhandledClausesForThisQuery)); - unhandledClauses.Add(unhandledClausesForThisQuery); - } - IDbConnection connection; - if (_transaction != null) - { - connection = _transaction.DbTransaction.Connection; - } - else - { - connection = _adapter.CreateConnection(); - } - IDbCommand command = - new CommandBuilder(_adapter.GetSchema()).CreateCommand( - _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), - commandBuilders.ToArray(), connection, _adapter.AdoOptions); - if (_transaction != null) - { - command.Transaction = _transaction.DbTransaction; - } - foreach (var item in command.ToEnumerables(connection)) - { - yield return item.ToList(); - } - } - else - { - foreach (SimpleQuery t in queries) - { - IEnumerable unhandledClausesForThisQuery; - yield return RunQuery(t, out unhandledClausesForThisQuery); - unhandledClauses.Add(unhandledClausesForThisQuery); - } - } - } - } +namespace Simple.Data.Ado +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Diagnostics; + using System.Linq; + + internal class AdoAdapterQueryRunner + { + private readonly AdoAdapter _adapter; + private readonly AdoAdapterTransaction _transaction; + + public AdoAdapterQueryRunner(AdoAdapter adapter) : this(adapter, null) + { + } + + public AdoAdapterQueryRunner(AdoAdapter adapter, AdoAdapterTransaction transaction) + { + _adapter = adapter; + _transaction = transaction; + } + + public IEnumerable> RunQuery(SimpleQuery query, + out IEnumerable + unhandledClauses) + { + IEnumerable> result; + + if (query.Clauses.OfType().Any()) return RunQueryWithCount(query, out unhandledClauses); + + ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(ref query, out unhandledClauses); + IDbConnection connection = _adapter.CreateConnection(); + if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) + { + var command = + new CommandBuilder(_adapter.GetSchema()).CreateCommand( + _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), + commandBuilders, + 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, _adapter.AdoOptions).ToEnumerable(_adapter.CreateConnection)); + } + + if (query.Clauses.OfType().Any()) + { + result = new EagerLoadingEnumerable(result); + } + + return result; + } + + public IObservable> RunQueryAsObservable(SimpleQuery query, + out + IEnumerable + + unhandledClauses) + { + IDbConnection connection = _adapter.CreateConnection(); + return new QueryBuilder(_adapter).Build(query, out unhandledClauses) + .GetCommand(connection, _adapter.AdoOptions) + .ToObservable(connection, _adapter); + } + + private IEnumerable> RunQueryWithCount(SimpleQuery query, + out IEnumerable + unhandledClauses) + { + WithCountClause withCountClause; + try + { + withCountClause = query.Clauses.OfType().First(); + } + catch (InvalidOperationException) + { + // Rethrow with meaning. + throw new InvalidOperationException("No WithCountClause specified."); + } + + query = query.ClearWithTotalCount(); + var countQuery = query.ClearSkip() + .ClearTake() + .ClearOrderBy() + .ClearWith() + .ClearForUpdate() + .ReplaceSelect(new CountSpecialReference()); + var unhandledClausesList = new List> + { + Enumerable.Empty(), + Enumerable.Empty() + }; + + using (var enumerator = RunQueries(new[] {countQuery, query}, unhandledClausesList).GetEnumerator()) + { + unhandledClauses = unhandledClausesList[1]; + if (!enumerator.MoveNext()) + { + throw new InvalidOperationException(); + } + IDictionary countRow = enumerator.Current.Single(); + withCountClause.SetCount((int) countRow.First().Value); + if (!enumerator.MoveNext()) + { + throw new InvalidOperationException(); + } + return enumerator.Current; + } + } + + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, + out IEnumerable unhandledClauses) + { + return GetPagedQueryCommandBuilders(ref query, -1, out unhandledClauses); + } + + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, + out IEnumerable unhandledClauses) + { + var commandBuilders = new List(); + var unhandledClausesList = new List(); + unhandledClauses = unhandledClausesList; + + IEnumerable unhandledClausesForPagedQuery; + ICommandBuilder mainCommandBuilder = new QueryBuilder(_adapter, bulkIndex).Build(query, + out + unhandledClausesForPagedQuery); + unhandledClausesList.AddRange(unhandledClausesForPagedQuery); + + SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); + TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); + + if (skipClause != null || takeClause != null) + { + var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); + if (queryPager == null) + { + Trace.TraceWarning("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(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); + } + } + return commandBuilders.ToArray(); + } + + private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, + List unhandledClausesList) + { + 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(SimpleQuery query, List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) + { + const int maxInt = 2147483646; + + IEnumerable commandTexts; + if (skipClause == null && !hasWithClause) + { + commandTexts = queryPager.ApplyLimit(mainCommandBuilder.Text, takeClause.Count); + } + else + { + var table = _adapter.GetSchema().FindTable(query.TableName); + if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) + { + throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); + } + var 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( + commandTexts.Select( + commandText => + new CommandBuilder(commandText, _adapter.GetSchema(), mainCommandBuilder.Parameters))); + } + + private ICommandBuilder[] GetQueryCommandBuilders(ref SimpleQuery query, + out IEnumerable unhandledClauses) + { + if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) + { + return GetPagedQueryCommandBuilders(ref query, out unhandledClauses); + } + return new[] {new QueryBuilder(_adapter).Build(query, out unhandledClauses)}; + } + + private IEnumerable GetQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, + out IEnumerable + unhandledClauses) + { + if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) + { + return GetPagedQueryCommandBuilders(ref query, bulkIndex, out unhandledClauses); + } + return new[] {new QueryBuilder(_adapter, bulkIndex).Build(query, out unhandledClauses)}; + } + + public IEnumerable>> RunQueries(SimpleQuery[] queries, + List + < + IEnumerable + > + unhandledClauses) + { + if (_adapter.ProviderSupportsCompoundStatements && queries.Length > 1) + { + var commandBuilders = new List(); + for (int i = 0; i < queries.Length; i++) + { + IEnumerable unhandledClausesForThisQuery; + commandBuilders.AddRange(GetQueryCommandBuilders(ref queries[i], i, out unhandledClausesForThisQuery)); + unhandledClauses.Add(unhandledClausesForThisQuery); + } + IDbConnection connection; + if (_transaction != null) + { + connection = _transaction.DbTransaction.Connection; + } + else + { + connection = _adapter.CreateConnection(); + } + IDbCommand command = + new CommandBuilder(_adapter.GetSchema()).CreateCommand( + _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), + commandBuilders.ToArray(), connection, _adapter.AdoOptions); + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + } + foreach (var item in command.ToEnumerables(connection)) + { + yield return item.ToList(); + } + } + else + { + foreach (SimpleQuery t in queries) + { + IEnumerable unhandledClausesForThisQuery; + yield return RunQuery(t, out unhandledClausesForThisQuery); + unhandledClauses.Add(unhandledClausesForThisQuery); + } + } + } + } } \ No newline at end of file From 0f9d3c32a5ed16c48751203aac74157b088fbbfa Mon Sep 17 00:00:00 2001 From: Richard Hopton Date: Tue, 27 Nov 2012 14:30:09 +0000 Subject: [PATCH 095/160] Fix for issue #244 I assumed that SQL Ce supports DISTINCT when doing the regex replace - to be fair it won't break anything if it doesn't! --- Simple.Data.SqlCe40/SqlCe40QueryPager.cs | 68 ++++---- .../SqlCe40QueryPagerTest.cs | 114 +++++++------ Simple.Data.SqlServer/SqlCommandOptimizer.cs | 46 +++--- Simple.Data.SqlServer/SqlQueryPager.cs | 156 +++++++++--------- Simple.Data.SqlTest/SqlQueryPagerTest.cs | 152 +++++++++-------- 5 files changed, 280 insertions(+), 256 deletions(-) diff --git a/Simple.Data.SqlCe40/SqlCe40QueryPager.cs b/Simple.Data.SqlCe40/SqlCe40QueryPager.cs index 3a777498..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, 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); - } - } -} +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.SqlCe40Test/SqlCe40QueryPagerTest.cs b/Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs index e99887b6..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, 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)); - } - } -} +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/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/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 4e4c7862..d1d73ac5 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -1,78 +1,78 @@ -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, string[] keys, 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(string.Join(",", keys)); - - var orderBy = ExtractOrderBy(columns, keys, ref fromEtc); - - builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy); - builder.AppendLine(); - builder.Append(fromEtc); - builder.AppendLine(")"); - builder.AppendFormat("SELECT {0} FROM __Data ", columns); - builder.AppendFormat("JOIN {0} ON ", - keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase))); - builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); - var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); - builder.Append(rest); - - builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take); - - 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; - } - } -} +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*(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) + { + 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(fromEtc); + builder.AppendLine(")"); + builder.AppendFormat("SELECT {0} FROM __Data ", columns); + builder.AppendFormat("JOIN {0} ON ", + keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase))); + builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); + var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); + builder.Append(rest); + + builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take); + + 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; + } + } +} diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index be5971c2..a5c902e3 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -1,70 +1,82 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using NUnit.Framework; -using Simple.Data.SqlServer; - -namespace Simple.Data.SqlTest -{ - [TestFixture] - public class SqlQueryPagerTest - { - 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 SqlQueryPager().ApplyLimit(sql, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - - [Test] - public void ShouldApplyPagingUsingOrderBy() - { - 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 [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, new[] {"[dbo].[d].[a]"}, 5, 10); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - - 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 [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 [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, new[]{"[dbo].[d].[a]"}, 20, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - - Assert.AreEqual(expected[0], modified[0]); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Simple.Data.SqlServer; + +namespace Simple.Data.SqlTest +{ + [TestFixture] + public class SqlQueryPagerTest + { + 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 SqlQueryPager().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 SqlQueryPager().ApplyLimit(sql, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + + [Test] + public void ShouldApplyPagingUsingOrderBy() + { + 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 [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, new[] {"[dbo].[d].[a]"}, 5, 10); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); + + 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 [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 [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, new[]{"[dbo].[d].[a]"}, 20, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); + + Assert.AreEqual(expected[0], modified[0]); + } + } +} From b6d7af2cc323b0e3a17a9ca345341eb65b84f92d Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 27 Nov 2012 15:50:12 +0000 Subject: [PATCH 096/160] Added CompoundKey tables to DatabaseReset for SqlTest --- .../Resources/DatabaseReset.txt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 646ae128..483ca67e 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -28,6 +28,10 @@ IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GroupT 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] @@ -282,6 +286,7 @@ BEGIN 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 @@ -405,3 +410,31 @@ 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 From 61bf4ed91b16f6e536d32a2f9d5f6b78412eca88 Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 29 Nov 2012 19:31:44 +0000 Subject: [PATCH 097/160] Fix for MySQL WithTotalCount --- Simple.Data.Ado/AdoAdapter.cs | 2 +- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 6 +- Simple.Data.Ado/AdoAdapterUpserter.cs | 4 +- Simple.Data.SqlTest/UpdateTests.cs | 4 +- Simple.Data/Ado/AdoAdapter.cs | 150 ------------- Simple.Data/Ado/AdoAdapterException.cs | 64 ------ Simple.Data/Ado/AdoAdapterFinder.cs | 58 ----- Simple.Data/Ado/AdoAdapterInserter.cs | 93 -------- Simple.Data/Ado/AdoAdapterRelatedFinder.cs | 75 ------- Simple.Data/Ado/CommandBuilder.cs | 72 ------- Simple.Data/Ado/CommandHelper.cs | 61 ------ Simple.Data/Ado/DataReaderExtensions.cs | 51 ----- Simple.Data/Ado/DataReaderObservableRunner.cs | 79 ------- Simple.Data/Ado/DataRecordExtensions.cs | 32 --- Simple.Data/Ado/DbCommandExtensions.cs | 26 --- Simple.Data/Ado/DbCommandObservableEx.cs | 24 --- Simple.Data/Ado/DeleteHelper.cs | 41 ---- Simple.Data/Ado/ExpressionFormatter.cs | 85 -------- Simple.Data/Ado/FindHelper.cs | 41 ---- Simple.Data/Ado/ICommandBuilder.cs | 11 - Simple.Data/Ado/IConnectionProvider.cs | 13 -- Simple.Data/Ado/IExpressionFormatter.cs | 7 - Simple.Data/Ado/JoinType.cs | 13 -- Simple.Data/Ado/Joiner.cs | 106 --------- Simple.Data/Ado/ObservableDataReader.cs | 29 --- Simple.Data/Ado/ProviderHelper.cs | 54 ----- .../Schema/AmbiguousObjectNameException.cs | 48 ----- Simple.Data/Ado/Schema/Column.cs | 89 -------- Simple.Data/Ado/Schema/ColumnCollection.cs | 39 ---- Simple.Data/Ado/Schema/DatabaseSchema.cs | 59 ----- Simple.Data/Ado/Schema/ForeignKey.cs | 40 ---- .../Ado/Schema/ForeignKeyCollection.cs | 19 -- Simple.Data/Ado/Schema/ISchemaProvider.cs | 14 -- Simple.Data/Ado/Schema/Key.cs | 29 --- .../Schema/SchemaSpecificStringExtensions.cs | 8 - Simple.Data/Ado/Schema/Table.cs | 202 ------------------ Simple.Data/Ado/Schema/TableCollection.cs | 61 ------ Simple.Data/Ado/Schema/TableJoin.cs | 40 ---- Simple.Data/Ado/Schema/TableType.cs | 8 - Simple.Data/Ado/SchemaResolutionException.cs | 32 --- Simple.Data/Ado/UpdateHelper.cs | 45 ---- 41 files changed, 9 insertions(+), 1925 deletions(-) delete mode 100644 Simple.Data/Ado/AdoAdapter.cs delete mode 100644 Simple.Data/Ado/AdoAdapterException.cs delete mode 100644 Simple.Data/Ado/AdoAdapterFinder.cs delete mode 100644 Simple.Data/Ado/AdoAdapterInserter.cs delete mode 100644 Simple.Data/Ado/AdoAdapterRelatedFinder.cs delete mode 100644 Simple.Data/Ado/CommandBuilder.cs delete mode 100644 Simple.Data/Ado/CommandHelper.cs delete mode 100644 Simple.Data/Ado/DataReaderExtensions.cs delete mode 100644 Simple.Data/Ado/DataReaderObservableRunner.cs delete mode 100644 Simple.Data/Ado/DataRecordExtensions.cs delete mode 100644 Simple.Data/Ado/DbCommandExtensions.cs delete mode 100644 Simple.Data/Ado/DbCommandObservableEx.cs delete mode 100644 Simple.Data/Ado/DeleteHelper.cs delete mode 100644 Simple.Data/Ado/ExpressionFormatter.cs delete mode 100644 Simple.Data/Ado/FindHelper.cs delete mode 100644 Simple.Data/Ado/ICommandBuilder.cs delete mode 100644 Simple.Data/Ado/IConnectionProvider.cs delete mode 100644 Simple.Data/Ado/IExpressionFormatter.cs delete mode 100644 Simple.Data/Ado/JoinType.cs delete mode 100644 Simple.Data/Ado/Joiner.cs delete mode 100644 Simple.Data/Ado/ObservableDataReader.cs delete mode 100644 Simple.Data/Ado/ProviderHelper.cs delete mode 100644 Simple.Data/Ado/Schema/AmbiguousObjectNameException.cs delete mode 100644 Simple.Data/Ado/Schema/Column.cs delete mode 100644 Simple.Data/Ado/Schema/ColumnCollection.cs delete mode 100644 Simple.Data/Ado/Schema/DatabaseSchema.cs delete mode 100644 Simple.Data/Ado/Schema/ForeignKey.cs delete mode 100644 Simple.Data/Ado/Schema/ForeignKeyCollection.cs delete mode 100644 Simple.Data/Ado/Schema/ISchemaProvider.cs delete mode 100644 Simple.Data/Ado/Schema/Key.cs delete mode 100644 Simple.Data/Ado/Schema/SchemaSpecificStringExtensions.cs delete mode 100644 Simple.Data/Ado/Schema/Table.cs delete mode 100644 Simple.Data/Ado/Schema/TableCollection.cs delete mode 100644 Simple.Data/Ado/Schema/TableJoin.cs delete mode 100644 Simple.Data/Ado/Schema/TableType.cs delete mode 100644 Simple.Data/Ado/SchemaResolutionException.cs delete mode 100644 Simple.Data/Ado/UpdateHelper.cs diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index 281c46ed..d2043d2b 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -342,4 +342,4 @@ protected override void OnReset() _schema = DatabaseSchema.Get(_connectionProvider, _providerHelper); } } -} \ No newline at end of file +} diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 6c4de3e9..c8809d83 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -109,8 +109,10 @@ out IEnumerable { throw new InvalidOperationException(); } - IDictionary countRow = enumerator.Current.Single(); - withCountClause.SetCount((int) countRow.First().Value); + IDictionary countRow = enumerator.Current.Single(); + var value = countRow.First().Value; + int count = value is int ? (int) value : Convert.ToInt32(value); + withCountClause.SetCount(count); if (!enumerator.MoveNext()) { throw new InvalidOperationException(); diff --git a/Simple.Data.Ado/AdoAdapterUpserter.cs b/Simple.Data.Ado/AdoAdapterUpserter.cs index 109fd17e..1db94d45 100644 --- a/Simple.Data.Ado/AdoAdapterUpserter.cs +++ b/Simple.Data.Ado/AdoAdapterUpserter.cs @@ -95,7 +95,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) @@ -136,4 +136,4 @@ private static SimpleExpression GetCriteria(string tableName, IEnumerable() { + var user = new Dictionary + { {"Id", 0}, {"Age", 1}, {"Name", "X"}, 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); - } - } -} From d791b716651722b682031ec8c33103417009f8dc Mon Sep 17 00:00:00 2001 From: markrendle Date: Thu, 29 Nov 2012 23:57:32 +0000 Subject: [PATCH 098/160] 1.0 RC3 --- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index c0b4a5ec..8d5b7365 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.nuspec +++ b/Simple.Data.Ado/Simple.Data.Ado.nuspec @@ -2,7 +2,7 @@ Simple.Data.Ado - 0.18.2.1 + 1.0.0-rc3 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 9e0b91b8..51f56d60 100644 --- a/Simple.Data.Mocking/Simple.Data.Mocking.nuspec +++ b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec @@ -2,7 +2,7 @@ Simple.Data.Mocking - 0.18.2.1 + 1.0.0-rc3 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 331bc6a4..77e05e58 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 0.18.2.1 + 1.0.0-rc3 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,7 +12,7 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index fbf9932e..73d16f14 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlServer - 0.18.2.1 + 1.0.0-rc3 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index c33e3825..5e599c32 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 0.18.2.1 + 1.0.0-rc3 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From 4ef5f4bb4ede5f68b0a98742938a3648068a4990 Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 3 Dec 2012 19:36:25 +0000 Subject: [PATCH 099/160] Fix for issue #249 --- Simple.Data.Ado/TypeHelper.cs | 5 +++-- Simple.Data.SqlServer/SqlTypeResolver.cs | 2 +- Simple.Data.SqlTest/InsertTests.cs | 13 +++++++++++++ Simple.Data.SqlTest/Resources/DatabaseReset.txt | 10 ++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) 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.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/InsertTests.cs b/Simple.Data.SqlTest/InsertTests.cs index 5fd62439..f9826318 100644 --- a/Simple.Data.SqlTest/InsertTests.cs +++ b/Simple.Data.SqlTest/InsertTests.cs @@ -7,6 +7,8 @@ namespace Simple.Data.SqlTest { + using System; + [TestFixture] public class InsertTests { @@ -293,5 +295,16 @@ public void TestInsertWithTimestampColumn() 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/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 483ca67e..01dd22f2 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -70,6 +70,8 @@ BEGIN 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')) @@ -190,6 +192,14 @@ BEGIN 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 From 927cc4ccdcd0cb425f2a8414f1ee04803635157b Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 3 Dec 2012 19:41:39 +0000 Subject: [PATCH 100/160] NuGet 0.18.3.1 --- CommonAssemblyInfo.cs | 4 ++-- Simple.Data.Ado/Simple.Data.Ado.nuspec | 4 ++-- Simple.Data.Mocking/Simple.Data.Mocking.nuspec | 4 ++-- Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec | 4 ++-- Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec | 4 ++-- Simple.Data/Simple.Data.nuspec | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index e45ecdc0..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("0.18.3.0")] -[assembly: AssemblyFileVersion("0.18.3.0")] +[assembly: AssemblyVersion("0.18.3.1")] +[assembly: AssemblyFileVersion("0.18.3.1")] diff --git a/Simple.Data.Ado/Simple.Data.Ado.nuspec b/Simple.Data.Ado/Simple.Data.Ado.nuspec index 8d5b7365..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-rc3 + 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.Mocking/Simple.Data.Mocking.nuspec b/Simple.Data.Mocking/Simple.Data.Mocking.nuspec index 51f56d60..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-rc3 + 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.SqlCe40/Simple.Data.SqlCe40.nuspec b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec index 77e05e58..e405cd3b 100644 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.nuspec @@ -2,7 +2,7 @@ Simple.Data.SqlCompact40 - 1.0.0-rc3 + 0.18.3.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php @@ -12,7 +12,7 @@ SQL Server Compact 4.0 ADO provider for the Simple.Data data access library. sqlserver compact sqlce database data ado .net40 - + diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec b/Simple.Data.SqlServer/Simple.Data.SqlServer.nuspec index 73d16f14..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-rc3 + 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/Simple.Data.nuspec b/Simple.Data/Simple.Data.nuspec index 5e599c32..402443a2 100644 --- a/Simple.Data/Simple.Data.nuspec +++ b/Simple.Data/Simple.Data.nuspec @@ -2,7 +2,7 @@ Simple.Data.Core - 1.0.0-rc3 + 0.18.3.1 Mark Rendle Mark Rendle http://www.opensource.org/licenses/mit-license.php From bd4d0f40cd64c10f7b7d9e20e0402968d21a54c4 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 4 Dec 2012 15:27:10 +0000 Subject: [PATCH 101/160] Fixed issue with SimpleQuery joins... it was complicated. --- Simple.Data.InMemoryTest/InMemoryTests.cs | 26 ++++++++ .../DictionaryQueryRunnerTest.cs | 40 ++++++------- Simple.Data/InMemoryAdapter.cs | 2 +- .../QueryPolyfills/DictionaryQueryRunner.cs | 12 ++-- .../QueryPolyfills/WhereClauseHandler.cs | 20 ++++++- Simple.Data/SimpleQuery.cs | 59 ++++++++++++++++++- 6 files changed, 131 insertions(+), 28 deletions(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index 33694e41..4954d5ee 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -697,5 +697,31 @@ public void JoinTest() 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); + } } } 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/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 5e1b9c86..5de7c830 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -64,7 +64,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)); } diff --git a/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs b/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs index 033e946c..1649061d 100644 --- a/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs +++ b/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs @@ -17,20 +17,22 @@ private static readonly { typeof(OrderByClause), (c, d) => new OrderByClauseHandler((OrderByClause)c).Run(d) } }; + 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()) { } @@ -63,7 +65,7 @@ private IEnumerable> RunWhereClauses(IEnumerable()) { - source = new WhereClauseHandler(whereClause).Run(source); + source = new WhereClauseHandler(_mainTableName, whereClause).Run(source); } return source; } @@ -99,7 +101,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/WhereClauseHandler.cs b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs index da562cd4..7f15d7da 100644 --- a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs +++ b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs @@ -5,15 +5,18 @@ 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 string _mainTableName; private readonly WhereClause _whereClause; - public WhereClauseHandler(WhereClause whereClause) + public WhereClauseHandler(string mainTableName, WhereClause whereClause) { + _mainTableName = mainTableName; _whereClause = whereClause; _expressionFormatters = new Dictionary, bool>>> { @@ -147,6 +150,21 @@ private IList Resolve(IDictionary dict, object operand, 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] }; diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index b9545d9e..fdb0529e 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -312,7 +312,7 @@ protected IEnumerable Run() var unhandledClausesList = unhandledClauses.ToList(); if (unhandledClausesList.Count > 0) { - result = new DictionaryQueryRunner(result, unhandledClausesList).Run(); + result = new DictionaryQueryRunner(_tableName, result, unhandledClausesList).Run(); } } @@ -511,6 +511,55 @@ 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) @@ -542,6 +591,14 @@ public SimpleQuery WithTotalCount(out Promise count) private SimpleQuery ParseJoin(InvokeMemberBinder binder, object[] args) { var tableToJoin = args[0] as ObjectReference; + if (ReferenceEquals(tableToJoin, null)) + { + var dynamicTable = args[0] as DynamicTable; + if (!ReferenceEquals(dynamicTable, null)) + { + tableToJoin = dynamicTable.ToObjectReference(); + } + } if (tableToJoin == null) throw new InvalidOperationException(); SimpleExpression joinExpression = null; From 24b35f7662854d517071e90d20187591c2eb57ac Mon Sep 17 00:00:00 2001 From: markrendle Date: Mon, 10 Dec 2012 13:13:29 +0000 Subject: [PATCH 102/160] Added static ConnectionCreated event to AdoAdapter --- .../ConnectionModifierTest.cs | 75 +++++++++++++++++++ Simple.Data.Ado/AdoAdapter.cs | 33 +++++++- Simple.Data.Ado/EventHandlerEx.cs | 19 +++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 Simple.Data.Ado/EventHandlerEx.cs diff --git a/Simple.Data.Ado.Test/ConnectionModifierTest.cs b/Simple.Data.Ado.Test/ConnectionModifierTest.cs index 7d181535..540f8e46 100644 --- a/Simple.Data.Ado.Test/ConnectionModifierTest.cs +++ b/Simple.Data.Ado.Test/ConnectionModifierTest.cs @@ -3,6 +3,8 @@ namespace Simple.Data.Ado.Test { + using System; + [TestFixture] public class ConnectionModifierTest { @@ -24,6 +26,29 @@ public void ClearsConnection() 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; @@ -73,5 +98,55 @@ public void Open() 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/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index d2043d2b..b0c0b1f6 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -307,7 +307,14 @@ public void StopUsingSharedConnection() public IDbConnection CreateConnection() { - return _sharedConnection ?? _connectionModifier(_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() @@ -341,5 +348,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; + } } } 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 From 48b39a6c70914ab307e3c7c803486513c6b1d75e Mon Sep 17 00:00:00 2001 From: Peter Ritchie Date: Sun, 23 Dec 2012 13:30:59 -0500 Subject: [PATCH 103/160] Added EventHandlerEx to Simple.Data.Ado project Added EventHandlerEx to Simple.Data.Ado project to fix build error --- Simple.Data.Ado/Simple.Data.Ado.csproj | 347 +++++++++++++------------ 1 file changed, 174 insertions(+), 173 deletions(-) diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index f0c148cf..9aaa0903 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -1,174 +1,175 @@ - - - - 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 From 916e108bb9a1aad789e4ede8fb93076445f27882 Mon Sep 17 00:00:00 2001 From: Peter Ritchie Date: Sun, 23 Dec 2012 14:38:52 -0500 Subject: [PATCH 104/160] Added axiomatic support for passing null sproc params Added support or null parameters to stored procedures. My local SqlCe install doesn't seem to be working, so not tested with SqlCe... --- Simple.Data.SqlTest/ProcedureTest.cs | 318 +++--- .../Resources/DatabaseReset.txt | 909 +++++++++--------- Simple.Data/BinderHelper.cs | 110 +-- 3 files changed, 685 insertions(+), 652 deletions(-) diff --git a/Simple.Data.SqlTest/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index 38294b6f..4e7be2b4 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -1,148 +1,170 @@ -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() - { - 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); - } -#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 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() + { + 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); + } +#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/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 01dd22f2..9f79e115 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -1,450 +1,461 @@ -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].[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] - - 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 - )) - - 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) - 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); -GO -CREATE CLUSTERED INDEX [ci_azure_fixup_test_SchemaTable] ON [test].[SchemaTable] ( [Id] ASC) -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 [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 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]) - +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] + + 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 + )) + + 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) + 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); +GO +CREATE CLUSTERED INDEX [ci_azure_fixup_test_SchemaTable] ON [test].[SchemaTable] ( [Id] ASC) +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].[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 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/BinderHelper.cs b/Simple.Data/BinderHelper.cs index 0a40435f..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(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) : 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); - } - } -} +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); + } + } +} From 75b294d3ff04f9a95a039e3a3293b42d19280691 Mon Sep 17 00:00:00 2001 From: Daniel Hegner Date: Tue, 8 Jan 2013 10:29:42 +0100 Subject: [PATCH 105/160] Changed InMemoryTests.cs so that it fails when using a dabase schema --- ...erformanceTestConsole.csproj.DotSettings.user | 2 ++ .../ProfilingApp.csproj.DotSettings.user | 2 ++ ...imple.Data.AdapterApi.csproj.DotSettings.user | 2 ++ .../Simple.Data.Ado.Test.csproj.DotSettings.user | 2 ++ .../Simple.Data.Ado.csproj.DotSettings.user | 2 ++ ...le.Data.BehaviourTest.csproj.DotSettings.user | 2 ++ Simple.Data.InMemoryTest/InMemoryTests.cs | 16 ++++++++-------- ...ple.Data.InMemoryTest.csproj.DotSettings.user | 2 ++ ...ple.Data.Mocking.Test.csproj.DotSettings.user | 2 ++ .../Simple.Data.Mocking.csproj.DotSettings.user | 2 ++ .../Simple.Data.SqlCe40.csproj.DotSettings.user | 2 ++ ...mple.Data.SqlCe40Test.csproj.DotSettings.user | 2 ++ ...Simple.Data.SqlServer.csproj.DotSettings.user | 2 ++ .../Simple.Data.SqlTest.csproj.DotSettings.user | 2 ++ ...imple.Data.TestHelper.csproj.DotSettings.user | 2 ++ .../Simple.Data.UnitTest.csproj.DotSettings.user | 2 ++ Simple.Data/Simple.Data.csproj.DotSettings.user | 2 ++ 17 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user create mode 100644 ProfilingApp/ProfilingApp.csproj.DotSettings.user create mode 100644 Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user create mode 100644 Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user create mode 100644 Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user create mode 100644 Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user create mode 100644 Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user create mode 100644 Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user create mode 100644 Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user create mode 100644 Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user create mode 100644 Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user create mode 100644 Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user create mode 100644 Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user create mode 100644 Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user create mode 100644 Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user create mode 100644 Simple.Data/Simple.Data.csproj.DotSettings.user diff --git a/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user b/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/ProfilingApp/ProfilingApp.csproj.DotSettings.user b/ProfilingApp/ProfilingApp.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/ProfilingApp/ProfilingApp.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user b/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user b/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index 4954d5ee..e1adb998 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -90,10 +90,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); } @@ -103,10 +103,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); } diff --git a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user b/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user b/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user b/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user b/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Simple.Data/Simple.Data.csproj.DotSettings.user b/Simple.Data/Simple.Data.csproj.DotSettings.user new file mode 100644 index 00000000..62a2a250 --- /dev/null +++ b/Simple.Data/Simple.Data.csproj.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file From 3bbf3df7243b210a325b56058a392994aeffcd39 Mon Sep 17 00:00:00 2001 From: Daniel Hegner Date: Tue, 8 Jan 2013 10:47:22 +0100 Subject: [PATCH 106/160] Removed accidently added files --- .../PerformanceTestConsole.csproj.DotSettings.user | 2 -- ProfilingApp/ProfilingApp.csproj.DotSettings.user | 2 -- .../Simple.Data.AdapterApi.csproj.DotSettings.user | 2 -- .../Simple.Data.Ado.Test.csproj.DotSettings.user | 2 -- Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user | 2 -- .../Simple.Data.BehaviourTest.csproj.DotSettings.user | 2 -- .../Simple.Data.InMemoryTest.csproj.DotSettings.user | 2 -- .../Simple.Data.Mocking.Test.csproj.DotSettings.user | 2 -- Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user | 2 -- Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user | 2 -- .../Simple.Data.SqlCe40Test.csproj.DotSettings.user | 2 -- .../Simple.Data.SqlServer.csproj.DotSettings.user | 2 -- Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user | 2 -- .../Simple.Data.TestHelper.csproj.DotSettings.user | 2 -- .../Simple.Data.UnitTest.csproj.DotSettings.user | 2 -- Simple.Data/Simple.Data.csproj.DotSettings.user | 2 -- 16 files changed, 32 deletions(-) delete mode 100644 PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user delete mode 100644 ProfilingApp/ProfilingApp.csproj.DotSettings.user delete mode 100644 Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user delete mode 100644 Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user delete mode 100644 Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user delete mode 100644 Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user delete mode 100644 Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user delete mode 100644 Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user delete mode 100644 Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user delete mode 100644 Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user delete mode 100644 Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user delete mode 100644 Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user delete mode 100644 Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user delete mode 100644 Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user delete mode 100644 Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user delete mode 100644 Simple.Data/Simple.Data.csproj.DotSettings.user diff --git a/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user b/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/PerformanceTestConsole/PerformanceTestConsole.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/ProfilingApp/ProfilingApp.csproj.DotSettings.user b/ProfilingApp/ProfilingApp.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/ProfilingApp/ProfilingApp.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user b/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.AdapterApi/Simple.Data.AdapterApi.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user b/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.Ado.Test/Simple.Data.Ado.Test.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user b/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user b/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.BehaviourTest/Simple.Data.BehaviourTest.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user b/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.InMemoryTest/Simple.Data.InMemoryTest.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user b/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.Mocking.Test/Simple.Data.Mocking.Test.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user b/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.Mocking/Simple.Data.Mocking.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user b/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.SqlCe40/Simple.Data.SqlCe40.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user b/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.SqlCe40Test/Simple.Data.SqlCe40Test.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user b/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.SqlTest/Simple.Data.SqlTest.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user b/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.TestHelper/Simple.Data.TestHelper.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user b/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data.UnitTest/Simple.Data.UnitTest.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Simple.Data/Simple.Data.csproj.DotSettings.user b/Simple.Data/Simple.Data.csproj.DotSettings.user deleted file mode 100644 index 62a2a250..00000000 --- a/Simple.Data/Simple.Data.csproj.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file From 557526f9c95ba8982dba238d8472b6ebd0783ce9 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 9 Jan 2013 22:07:46 +0000 Subject: [PATCH 107/160] Added JetBrains DotSettings file to gitignore (no pull request without further changes) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ee5e5292..204bdf4a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ 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/ \ No newline at end of file +packages/jQuery.Validation.1.9.0.1/ +Simple.Data.sln.DotSettings From b9da6117cebd3ff82587524758c90813151b6da4 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 25 Jan 2013 13:11:28 +0000 Subject: [PATCH 108/160] Updated QueryWith tests --- .gitignore | 3 ++- Simple.Data.Ado/Simple.Data.Ado.csproj | 1 + Simple.Data.BehaviourTest/Query/WithTest.cs | 22 +++++++++++++++++++++ Simple.Data.SqlTest/QueryTest.cs | 14 +++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ee5e5292..38132003 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ 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/ \ No newline at end of file +packages/jQuery.Validation.1.9.0.1/ +Simple.Data.sln.ide diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index f0c148cf..9bddf20c 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -87,6 +87,7 @@ + diff --git a/Simple.Data.BehaviourTest/Query/WithTest.cs b/Simple.Data.BehaviourTest/Query/WithTest.cs index 28a85064..d74b618b 100644 --- a/Simple.Data.BehaviourTest/Query/WithTest.cs +++ b/Simple.Data.BehaviourTest/Query/WithTest.cs @@ -14,6 +14,7 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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" }); @@ -29,6 +30,9 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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" }, @@ -44,6 +48,7 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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} ); @@ -52,6 +57,7 @@ protected override void SetSchema(MockSchemaProvider schemaProvider) 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 @@ -232,6 +238,22 @@ public void CustomerWithOrdersAndNotes() 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 /// diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index df32f1dc..9773a8c2 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -346,6 +346,20 @@ public void FindAllWithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollect Assert.AreEqual(1, orderItems.Count); } + [Test] + 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", (ICollection)result.Keys); + var orders = result["Orders"] as IList>; + Assert.IsNotNull(orders); + Assert.AreEqual(1, orders.Count); + var order = orders[0]; + Assert.Contains("OrderItems", (ICollection)order.Keys); + } + [Test] public void GetWithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollection() { From e61bd7ca7b87ec2c5f92a5aaadbcd636fc4db866 Mon Sep 17 00:00:00 2001 From: markrendle Date: Fri, 25 Jan 2013 14:18:39 +0000 Subject: [PATCH 109/160] Fixed problems with Update using objects without keys and schema-qualified tables in InMemoryAdapter --- Simple.Data.Ado/AdoAdapter.cs | 8 +- Simple.Data.Ado/AdoAdapterUpserter.cs | 14 +- Simple.Data.Ado/QueryBuilderBase.cs | 756 +++++++++--------- Simple.Data.SqlTest/QueryTest.cs | 4 +- Simple.Data.SqlTest/UpdateTests.cs | 18 + Simple.Data/AdapterFactory.cs | 2 +- Simple.Data/Commands/UpsertCommand.cs | 1 + Simple.Data/Extensions/ObjectEx.cs | 15 +- .../QueryPolyfills/WhereClauseHandler.cs | 8 + 9 files changed, 439 insertions(+), 387 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index b0c0b1f6..bdb5ce0d 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -7,6 +7,8 @@ namespace Simple.Data.Ado { + using Extensions; + [Export("Ado", typeof (Adapter))] public partial class AdoAdapter : Adapter, ICloneable { @@ -81,8 +83,10 @@ 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 diff --git a/Simple.Data.Ado/AdoAdapterUpserter.cs b/Simple.Data.Ado/AdoAdapterUpserter.cs index 1db94d45..956695ec 100644 --- a/Simple.Data.Ado/AdoAdapterUpserter.cs +++ b/Simple.Data.Ado/AdoAdapterUpserter.cs @@ -82,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) { diff --git a/Simple.Data.Ado/QueryBuilderBase.cs b/Simple.Data.Ado/QueryBuilderBase.cs index 93c4c405..66309607 100644 --- a/Simple.Data.Ado/QueryBuilderBase.cs +++ b/Simple.Data.Ado/QueryBuilderBase.cs @@ -1,384 +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; +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 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."); - } - } -} + 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.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 9773a8c2..a445e3e1 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -352,12 +352,12 @@ 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", (ICollection)result.Keys); + 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", (ICollection)order.Keys); + Assert.Contains("OrderItems", order.Keys.ToArray()); } [Test] diff --git a/Simple.Data.SqlTest/UpdateTests.cs b/Simple.Data.SqlTest/UpdateTests.cs index a8ff2d7c..17de059f 100644 --- a/Simple.Data.SqlTest/UpdateTests.cs +++ b/Simple.Data.SqlTest/UpdateTests.cs @@ -156,5 +156,23 @@ public void TestUpdateByInputIsNotMutated() 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/AdapterFactory.cs b/Simple.Data/AdapterFactory.cs index 7a8438c1..c1c52bfb 100644 --- a/Simple.Data/AdapterFactory.cs +++ b/Simple.Data/AdapterFactory.cs @@ -26,7 +26,7 @@ public Adapter Create(object settings) public Adapter Create(string adapterName, object settings) { - return Create(adapterName, settings.ObjectToDictionary()); + return Create(adapterName, settings.AnonymousObjectToDictionary()); } public Adapter Create(IEnumerable> settings) diff --git a/Simple.Data/Commands/UpsertCommand.cs b/Simple.Data/Commands/UpsertCommand.cs index e37416f6..b6555d47 100644 --- a/Simple.Data/Commands/UpsertCommand.cs +++ b/Simple.Data/Commands/UpsertCommand.cs @@ -50,6 +50,7 @@ private static object UpsertUsingKeys(DataStrategy dataStrategy, DynamicTable ta 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.Run.Upsert(table.GetQualifiedName(), dict, criteria, isResultRequired); } diff --git a/Simple.Data/Extensions/ObjectEx.cs b/Simple.Data/Extensions/ObjectEx.cs index ba64b94d..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)), diff --git a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs index 7f15d7da..43bee4a8 100644 --- a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs +++ b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs @@ -145,6 +145,14 @@ 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(); From e85dedf773a6913d4b2035a373ef6e2897f38f1b Mon Sep 17 00:00:00 2001 From: Matt Wolin Date: Tue, 12 Feb 2013 17:16:33 -0600 Subject: [PATCH 110/160] Update Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs Running order by clauses in reverse order to fix issue with Order By/Then by for in-memory adapter. #252: https://github.com/markrendle/Simple.Data/issues/252 --- .../QueryPolyfills/DictionaryQueryRunner.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs b/Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs index 1649061d..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,8 +13,7 @@ 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; @@ -47,6 +46,8 @@ public IEnumerable> Run() source = list; } + source = RunOrderByClauses(source); + foreach (var clause in _clauses) { Func>, IEnumerable>> handler; @@ -55,12 +56,22 @@ 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()) From 5af65f4201e5a2ce12d44c38d0b72eae8eaf1c2f Mon Sep 17 00:00:00 2001 From: Matt Wolin Date: Tue, 12 Feb 2013 17:21:58 -0600 Subject: [PATCH 111/160] Update Simple.Data.InMemoryTest/InMemoryTests.cs Added order by/then by test --- Simple.Data.InMemoryTest/InMemoryTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index e1adb998..e6f2ea3c 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -250,7 +250,25 @@ 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 TestSkip() { From 36d77be14337f27b849474959d02c67ac9be3b8e Mon Sep 17 00:00:00 2001 From: Master Garrett Date: Mon, 25 Feb 2013 21:03:55 +0100 Subject: [PATCH 112/160] Added triggers to SchemaTable. --- .../Resources/DatabaseReset.txt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index 9f79e115..bdbeb79c 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -285,6 +285,17 @@ BEGIN 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 @@ -323,11 +334,11 @@ GO CREATE SCHEMA [test] AUTHORIZATION [dbo] GO -CREATE TABLE [test].[SchemaTable] ([Id] int not null, [Description] nvarchar(100) not null); +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') +INSERT INTO [test].[SchemaTable] VALUES (2, 'Pass', null) GO CREATE PROCEDURE [dbo].[GetCustomers] @@ -403,6 +414,13 @@ CREATE FUNCTION [dbo].[VarcharAndReturnInt] (@AValue varchar(50)) RETURNS INT AS RETURN 42 END GO +CREATE TRIGGER [test].trg_schematable_after_insert ON [Simple.Data].[test].[SchemaTable] AFTER INSERT +AS +BEGIN + SET NOCOUNT ON; + UPDATE [Simple.Data].[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 From faf79a164477e1eda7c5e27ce184f0a497dbef65 Mon Sep 17 00:00:00 2001 From: Master Garrett Date: Mon, 25 Feb 2013 22:33:49 +0100 Subject: [PATCH 113/160] Added test to verify trigger execution. --- Simple.Data.SqlTest/BulkInsertTest.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Simple.Data.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs index 1379214f..5c1bff94 100644 --- a/Simple.Data.SqlTest/BulkInsertTest.cs +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -1,4 +1,6 @@ -namespace Simple.Data.SqlTest +using Simple.Data.Ado; + +namespace Simple.Data.SqlTest { using System.Collections.Generic; using System.Diagnostics; @@ -31,6 +33,25 @@ public void BulkInsertUsesSchema() 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); + } + private static IEnumerable GenerateItems() { for (int i = 0; i < 1000; i++) From a851871242d9325e15f76c3644e437c319ca16ec Mon Sep 17 00:00:00 2001 From: Master Garrett Date: Mon, 25 Feb 2013 21:04:50 +0100 Subject: [PATCH 114/160] Added AdoOption for triggers. --- Simple.Data.Ado/AdoOptions.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Simple.Data.Ado/AdoOptions.cs b/Simple.Data.Ado/AdoOptions.cs index ffe1a8f9..995ae62e 100644 --- a/Simple.Data.Ado/AdoOptions.cs +++ b/Simple.Data.Ado/AdoOptions.cs @@ -4,11 +4,13 @@ public class AdoOptions : OptionsBase { private readonly int _commandTimeout; private readonly bool _identityInsert; + private bool _fireTriggersOnBulkInserts; - public AdoOptions(int commandTimeout = -1, bool identityInsert = false) + public AdoOptions(int commandTimeout = -1, bool identityInsert = false, bool fireTriggersOnBulkInserts = false) { _commandTimeout = commandTimeout; _identityInsert = identityInsert; + _fireTriggersOnBulkInserts = fireTriggersOnBulkInserts; } public int CommandTimeout @@ -20,5 +22,10 @@ public bool IdentityInsert { get { return _identityInsert; } } + + public bool FireTriggersOnBulkInserts + { + get { return _fireTriggersOnBulkInserts; } + } } } \ No newline at end of file From 78f45520f4e734ece67b1c6bc3597fd2d7d0bddc Mon Sep 17 00:00:00 2001 From: Master Garrett Date: Mon, 25 Feb 2013 21:05:25 +0100 Subject: [PATCH 115/160] Added implementation which builds SqlBulkCopyOptions. --- Simple.Data.SqlServer/SqlBulkInserter.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Simple.Data.SqlServer/SqlBulkInserter.cs b/Simple.Data.SqlServer/SqlBulkInserter.cs index a5e234dd..a7563636 100644 --- a/Simple.Data.SqlServer/SqlBulkInserter.cs +++ b/Simple.Data.SqlServer/SqlBulkInserter.cs @@ -25,16 +25,17 @@ 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).QualifiedName; @@ -67,6 +68,20 @@ public IEnumerable> Insert(AdoAdapter adapter, strin return null; } + 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, ICollection keys, SqlBulkCopy bulkCopy) { var table = adapter.GetSchema().FindTable(tableName); From 1ef4906669f70b9007eca108d0e7eb1832ad1a59 Mon Sep 17 00:00:00 2001 From: ealfonso Date: Thu, 4 Apr 2013 14:31:09 -0400 Subject: [PATCH 116/160] fixed bug where paging didnt work for tables with multiple primary keys --- Simple.Data.SqlServer/SqlQueryPager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index d1d73ac5..e7429205 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -38,7 +38,10 @@ public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int builder.AppendFormat("SELECT {0} FROM __Data ", columns); builder.AppendFormat("JOIN {0} ON ", keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase))); - builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); + if (keys.Length > 1) + builder.AppendFormat(string.Join(" AND ", keys.Select(MakeDataJoin))); + else + builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); builder.Append(rest); From 92b89886abc36df8b9d7aa4fd051d59545719906 Mon Sep 17 00:00:00 2001 From: Athari Date: Sun, 7 Apr 2013 23:25:20 +0400 Subject: [PATCH 117/160] Added ReSharper overrides, ignored ReSharper settings --- .gitignore | 1 + Simple.Data.sln.DotSettings | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 Simple.Data.sln.DotSettings diff --git a/.gitignore b/.gitignore index 38132003..009ed1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ obj bin deploy *.csproj.user +*.DotSettings.user *.suo *.cache *.nupkg 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 From fa98bd7d1ea33f2d84ba0950a9cd8b4790d709b7 Mon Sep 17 00:00:00 2001 From: Athari Date: Sun, 7 Apr 2013 23:26:51 +0400 Subject: [PATCH 118/160] Added SimpleDataTraceSources, changed logging from Trace to TraceSource --- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 539 ++++++++++++----------- Simple.Data.Ado/BulkInserterHelper.cs | 5 +- Simple.Data.Ado/ProcedureExecutor.cs | 2 +- Simple.Data.Ado/TraceHelper.cs | 31 +- Simple.Data/Database.cs | 83 +++- Simple.Data/DynamicTable.cs | 3 +- Simple.Data/MefHelper.cs | 17 +- Simple.Data/Properties/AssemblyInfo.cs | 3 +- Simple.Data/Simple.Data.csproj | 1 + Simple.Data/SimpleDataTraceSources.cs | 25 ++ Simple.Data/SimpleTransaction.cs | 3 +- 11 files changed, 392 insertions(+), 320 deletions(-) create mode 100644 Simple.Data/SimpleDataTraceSources.cs diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index c8809d83..51d6c2f1 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -1,273 +1,274 @@ -namespace Simple.Data.Ado -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Diagnostics; - using System.Linq; - - internal class AdoAdapterQueryRunner - { - private readonly AdoAdapter _adapter; - private readonly AdoAdapterTransaction _transaction; - - public AdoAdapterQueryRunner(AdoAdapter adapter) : this(adapter, null) - { - } - - public AdoAdapterQueryRunner(AdoAdapter adapter, AdoAdapterTransaction transaction) - { - _adapter = adapter; - _transaction = transaction; - } - - public IEnumerable> RunQuery(SimpleQuery query, - out IEnumerable - unhandledClauses) - { - IEnumerable> result; - - if (query.Clauses.OfType().Any()) return RunQueryWithCount(query, out unhandledClauses); - - ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(ref query, out unhandledClauses); - IDbConnection connection = _adapter.CreateConnection(); - if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) - { - var command = - new CommandBuilder(_adapter.GetSchema()).CreateCommand( - _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), - commandBuilders, - 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, _adapter.AdoOptions).ToEnumerable(_adapter.CreateConnection)); - } - - if (query.Clauses.OfType().Any()) - { - result = new EagerLoadingEnumerable(result); - } - - return result; - } - - public IObservable> RunQueryAsObservable(SimpleQuery query, - out - IEnumerable - - unhandledClauses) - { - IDbConnection connection = _adapter.CreateConnection(); - return new QueryBuilder(_adapter).Build(query, out unhandledClauses) - .GetCommand(connection, _adapter.AdoOptions) - .ToObservable(connection, _adapter); - } - - private IEnumerable> RunQueryWithCount(SimpleQuery query, - out IEnumerable - unhandledClauses) - { - WithCountClause withCountClause; - try - { - withCountClause = query.Clauses.OfType().First(); - } - catch (InvalidOperationException) - { - // Rethrow with meaning. - throw new InvalidOperationException("No WithCountClause specified."); - } - - query = query.ClearWithTotalCount(); - var countQuery = query.ClearSkip() - .ClearTake() - .ClearOrderBy() - .ClearWith() - .ClearForUpdate() - .ReplaceSelect(new CountSpecialReference()); - var unhandledClausesList = new List> - { - Enumerable.Empty(), - Enumerable.Empty() - }; - - using (var enumerator = RunQueries(new[] {countQuery, query}, unhandledClausesList).GetEnumerator()) - { - unhandledClauses = unhandledClausesList[1]; - if (!enumerator.MoveNext()) - { - throw new InvalidOperationException(); - } +namespace Simple.Data.Ado +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Diagnostics; + using System.Linq; + + internal class AdoAdapterQueryRunner + { + private readonly AdoAdapter _adapter; + private readonly AdoAdapterTransaction _transaction; + + public AdoAdapterQueryRunner(AdoAdapter adapter) : this(adapter, null) + { + } + + public AdoAdapterQueryRunner(AdoAdapter adapter, AdoAdapterTransaction transaction) + { + _adapter = adapter; + _transaction = transaction; + } + + public IEnumerable> RunQuery(SimpleQuery query, + out IEnumerable + unhandledClauses) + { + IEnumerable> result; + + if (query.Clauses.OfType().Any()) return RunQueryWithCount(query, out unhandledClauses); + + ICommandBuilder[] commandBuilders = GetQueryCommandBuilders(ref query, out unhandledClauses); + IDbConnection connection = _adapter.CreateConnection(); + if (_adapter.ProviderSupportsCompoundStatements || commandBuilders.Length == 1) + { + var command = + new CommandBuilder(_adapter.GetSchema()).CreateCommand( + _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), + commandBuilders, + 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, _adapter.AdoOptions).ToEnumerable(_adapter.CreateConnection)); + } + + if (query.Clauses.OfType().Any()) + { + result = new EagerLoadingEnumerable(result); + } + + return result; + } + + public IObservable> RunQueryAsObservable(SimpleQuery query, + out + IEnumerable + + unhandledClauses) + { + IDbConnection connection = _adapter.CreateConnection(); + return new QueryBuilder(_adapter).Build(query, out unhandledClauses) + .GetCommand(connection, _adapter.AdoOptions) + .ToObservable(connection, _adapter); + } + + private IEnumerable> RunQueryWithCount(SimpleQuery query, + out IEnumerable + unhandledClauses) + { + WithCountClause withCountClause; + try + { + withCountClause = query.Clauses.OfType().First(); + } + catch (InvalidOperationException) + { + // Rethrow with meaning. + throw new InvalidOperationException("No WithCountClause specified."); + } + + query = query.ClearWithTotalCount(); + var countQuery = query.ClearSkip() + .ClearTake() + .ClearOrderBy() + .ClearWith() + .ClearForUpdate() + .ReplaceSelect(new CountSpecialReference()); + var unhandledClausesList = new List> + { + Enumerable.Empty(), + Enumerable.Empty() + }; + + using (var enumerator = RunQueries(new[] {countQuery, query}, unhandledClausesList).GetEnumerator()) + { + unhandledClauses = unhandledClausesList[1]; + if (!enumerator.MoveNext()) + { + throw new InvalidOperationException(); + } IDictionary countRow = enumerator.Current.Single(); var value = countRow.First().Value; int count = value is int ? (int) value : Convert.ToInt32(value); - withCountClause.SetCount(count); - if (!enumerator.MoveNext()) - { - throw new InvalidOperationException(); - } - return enumerator.Current; - } - } - - private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, - out IEnumerable unhandledClauses) - { - return GetPagedQueryCommandBuilders(ref query, -1, out unhandledClauses); - } - - private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, - out IEnumerable unhandledClauses) - { - var commandBuilders = new List(); - var unhandledClausesList = new List(); - unhandledClauses = unhandledClausesList; - - IEnumerable unhandledClausesForPagedQuery; - ICommandBuilder mainCommandBuilder = new QueryBuilder(_adapter, bulkIndex).Build(query, - out - unhandledClausesForPagedQuery); - unhandledClausesList.AddRange(unhandledClausesForPagedQuery); - - SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); - TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); - - if (skipClause != null || takeClause != null) - { - var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); - if (queryPager == null) - { - Trace.TraceWarning("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(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); - } - } - return commandBuilders.ToArray(); - } - - private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, - List unhandledClausesList) - { - 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(SimpleQuery query, List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) - { - const int maxInt = 2147483646; - - IEnumerable commandTexts; - if (skipClause == null && !hasWithClause) - { - commandTexts = queryPager.ApplyLimit(mainCommandBuilder.Text, takeClause.Count); - } - else - { - var table = _adapter.GetSchema().FindTable(query.TableName); - if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) - { - throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); - } - var 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( - commandTexts.Select( - commandText => - new CommandBuilder(commandText, _adapter.GetSchema(), mainCommandBuilder.Parameters))); - } - - private ICommandBuilder[] GetQueryCommandBuilders(ref SimpleQuery query, - out IEnumerable unhandledClauses) - { - if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) - { - return GetPagedQueryCommandBuilders(ref query, out unhandledClauses); - } - return new[] {new QueryBuilder(_adapter).Build(query, out unhandledClauses)}; - } - - private IEnumerable GetQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, - out IEnumerable - unhandledClauses) - { - if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) - { - return GetPagedQueryCommandBuilders(ref query, bulkIndex, out unhandledClauses); - } - return new[] {new QueryBuilder(_adapter, bulkIndex).Build(query, out unhandledClauses)}; - } - - public IEnumerable>> RunQueries(SimpleQuery[] queries, - List - < - IEnumerable - > - unhandledClauses) - { - if (_adapter.ProviderSupportsCompoundStatements && queries.Length > 1) - { - var commandBuilders = new List(); - for (int i = 0; i < queries.Length; i++) - { - IEnumerable unhandledClausesForThisQuery; - commandBuilders.AddRange(GetQueryCommandBuilders(ref queries[i], i, out unhandledClausesForThisQuery)); - unhandledClauses.Add(unhandledClausesForThisQuery); - } - IDbConnection connection; - if (_transaction != null) - { - connection = _transaction.DbTransaction.Connection; - } - else - { - connection = _adapter.CreateConnection(); - } - IDbCommand command = - new CommandBuilder(_adapter.GetSchema()).CreateCommand( - _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), - commandBuilders.ToArray(), connection, _adapter.AdoOptions); - if (_transaction != null) - { - command.Transaction = _transaction.DbTransaction; - } - foreach (var item in command.ToEnumerables(connection)) - { - yield return item.ToList(); - } - } - else - { - foreach (SimpleQuery t in queries) - { - IEnumerable unhandledClausesForThisQuery; - yield return RunQuery(t, out unhandledClausesForThisQuery); - unhandledClauses.Add(unhandledClausesForThisQuery); - } - } - } - } + withCountClause.SetCount(count); + if (!enumerator.MoveNext()) + { + throw new InvalidOperationException(); + } + return enumerator.Current; + } + } + + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, + out IEnumerable unhandledClauses) + { + return GetPagedQueryCommandBuilders(ref query, -1, out unhandledClauses); + } + + private ICommandBuilder[] GetPagedQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, + out IEnumerable unhandledClauses) + { + var commandBuilders = new List(); + var unhandledClausesList = new List(); + unhandledClauses = unhandledClausesList; + + IEnumerable unhandledClausesForPagedQuery; + ICommandBuilder mainCommandBuilder = new QueryBuilder(_adapter, bulkIndex).Build(query, + out + unhandledClausesForPagedQuery); + unhandledClausesList.AddRange(unhandledClausesForPagedQuery); + + SkipClause skipClause = query.Clauses.OfType().FirstOrDefault(); + TakeClause takeClause = query.Clauses.OfType().FirstOrDefault(); + + if (skipClause != null || takeClause != null) + { + var queryPager = _adapter.ProviderHelper.GetCustomProvider(_adapter.ConnectionProvider); + if (queryPager == null) + { + 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(query, commandBuilders, mainCommandBuilder, skipClause, takeClause, query.Clauses.OfType().Any(), queryPager); + } + } + return commandBuilders.ToArray(); + } + + private void DeferPaging(ref SimpleQuery query, ICommandBuilder mainCommandBuilder, List commandBuilders, + List unhandledClausesList) + { + 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(SimpleQuery query, List commandBuilders, ICommandBuilder mainCommandBuilder, SkipClause skipClause, TakeClause takeClause, bool hasWithClause, IQueryPager queryPager) + { + const int maxInt = 2147483646; + + IEnumerable commandTexts; + if (skipClause == null && !hasWithClause) + { + commandTexts = queryPager.ApplyLimit(mainCommandBuilder.Text, takeClause.Count); + } + else + { + var table = _adapter.GetSchema().FindTable(query.TableName); + if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) + { + throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); + } + var 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( + commandTexts.Select( + commandText => + new CommandBuilder(commandText, _adapter.GetSchema(), mainCommandBuilder.Parameters))); + } + + private ICommandBuilder[] GetQueryCommandBuilders(ref SimpleQuery query, + out IEnumerable unhandledClauses) + { + if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) + { + return GetPagedQueryCommandBuilders(ref query, out unhandledClauses); + } + return new[] {new QueryBuilder(_adapter).Build(query, out unhandledClauses)}; + } + + private IEnumerable GetQueryCommandBuilders(ref SimpleQuery query, Int32 bulkIndex, + out IEnumerable + unhandledClauses) + { + if (query.Clauses.OfType().Any() || query.Clauses.OfType().Any()) + { + return GetPagedQueryCommandBuilders(ref query, bulkIndex, out unhandledClauses); + } + return new[] {new QueryBuilder(_adapter, bulkIndex).Build(query, out unhandledClauses)}; + } + + public IEnumerable>> RunQueries(SimpleQuery[] queries, + List + < + IEnumerable + > + unhandledClauses) + { + if (_adapter.ProviderSupportsCompoundStatements && queries.Length > 1) + { + var commandBuilders = new List(); + for (int i = 0; i < queries.Length; i++) + { + IEnumerable unhandledClausesForThisQuery; + commandBuilders.AddRange(GetQueryCommandBuilders(ref queries[i], i, out unhandledClausesForThisQuery)); + unhandledClauses.Add(unhandledClausesForThisQuery); + } + IDbConnection connection; + if (_transaction != null) + { + connection = _transaction.DbTransaction.Connection; + } + else + { + connection = _adapter.CreateConnection(); + } + IDbCommand command = + new CommandBuilder(_adapter.GetSchema()).CreateCommand( + _adapter.ProviderHelper.GetCustomProvider(_adapter.SchemaProvider), + commandBuilders.ToArray(), connection, _adapter.AdoOptions); + if (_transaction != null) + { + command.Transaction = _transaction.DbTransaction; + } + foreach (var item in command.ToEnumerables(connection)) + { + yield return item.ToList(); + } + } + else + { + foreach (SimpleQuery t in queries) + { + IEnumerable unhandledClausesForThisQuery; + yield return RunQuery(t, out unhandledClausesForThisQuery); + unhandledClauses.Add(unhandledClausesForThisQuery); + } + } + } + } } \ No newline at end of file diff --git a/Simple.Data.Ado/BulkInserterHelper.cs b/Simple.Data.Ado/BulkInserterHelper.cs index 14777af5..d00874af 100644 --- a/Simple.Data.Ado/BulkInserterHelper.cs +++ b/Simple.Data.Ado/BulkInserterHelper.cs @@ -144,9 +144,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); } } } diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index 2f03a126..e3046747 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -95,7 +95,7 @@ public IEnumerable ExecuteReader(IDbCommand command) private static IEnumerable ExecuteNonQuery(IDbCommand command) { #if(DEBUG) - Trace.TraceInformation("ExecuteNonQuery", "Simple.Data.SqlTest"); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Verbose, SimpleDataTraceSources.DebugMessageId, "Simple.Data.SqlTest ExecuteNonQuery"); #endif command.Connection.OpenIfClosed(); command.TryExecuteNonQuery(); diff --git a/Simple.Data.Ado/TraceHelper.cs b/Simple.Data.Ado/TraceHelper.cs index 5faac08b..506e5203 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("Parameters:{0}", 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}", parameter.ParameterName, parameter.DbType, strValue, EOL); + } } - - 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/Database.cs b/Simple.Data/Database.cs index c8d1e5e0..15a66768 100644 --- a/Simple.Data/Database.cs +++ b/Simple.Data/Database.cs @@ -1,15 +1,12 @@ using System; -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.Data; - 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. @@ -20,28 +17,66 @@ public sealed partial class Database : DataStrategy 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(); - LoadTraceLevelFromConfig(); } - private static void LoadTraceLevelFromConfig() + private static void EnsureLoadedTraceLevelFromConfig() { - _configuration = - (SimpleDataConfigurationSection) ConfigurationManager.GetSection("simpleData/simpleDataConfiguration"); + if (_loadedConfig) + return; + _loadedConfig = true; + _configuration = (SimpleDataConfigurationSection)ConfigurationManager.GetSection("simpleData/simpleDataConfiguration"); if (_configuration != null) { - Trace.TraceWarning("SimpleDataConfiguration section is obsolete; use system.diagnostics switches instead."); - TraceLevel = _configuration.TraceLevel; + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Warning, SimpleDataTraceSources.ObsoleteWarningMessageId, + "SimpleDataConfiguration section is obsolete; use system.diagnostics switches instead."); + SimpleDataTraceSources.TraceSource.Switch.Level = TraceLevelToSourceLevels(_configuration.TraceLevel); } - else + } + + private static SourceLevels TraceLevelToSourceLevels(TraceLevel traceLevel) + { + switch (traceLevel) { - var traceSwitch = new TraceSwitch("Simple.Data", "", TraceLevel.Info.ToString()); - TraceLevel = traceSwitch.Level; + 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 } } @@ -96,7 +131,7 @@ public SimpleTransaction BeginTransaction(IsolationLevel isolationLevel) return SimpleTransaction.Begin(this, isolationLevel); } - protected internal override bool ExecuteFunction(out object result, Commands.ExecuteFunctionCommand command) + protected internal override bool ExecuteFunction(out object result, ExecuteFunctionCommand command) { return command.Execute(out result); } @@ -114,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() @@ -137,11 +172,15 @@ public static void StopUsingMockAdapter() Data.DatabaseOpener.StopUsingMock(); } - private static TraceLevel? _traceLevel; + [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 diff --git a/Simple.Data/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 51b04494..1d5930a5 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -129,7 +129,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; } diff --git a/Simple.Data/MefHelper.cs b/Simple.Data/MefHelper.cs index dab3d1af..00c9328f 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -53,7 +53,8 @@ public override T Compose(string contractName) } catch (ReflectionTypeLoadException ex) { - Trace.WriteLine(ex.Message); + SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Error, SimpleDataTraceSources.GenericErrorMessageId, + "Compose failed: {0}", ex.Message); throw; } } @@ -71,15 +72,15 @@ public static T GetAdjacentComponent(Type knownSiblingType) private static CompositionContainer CreateFolderContainer() { - var path = GetSimpleDataAssemblyPath (); + 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")) - { - var catalog = new AssemblyCatalog(file); - aggregateCatalog.Catalogs.Add(catalog); - } + 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); + } return new CompositionContainer(aggregateCatalog); } 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/Simple.Data.csproj b/Simple.Data/Simple.Data.csproj index 73f78b88..7bdf50df 100644 --- a/Simple.Data/Simple.Data.csproj +++ b/Simple.Data/Simple.Data.csproj @@ -137,6 +137,7 @@ + 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/SimpleTransaction.cs b/Simple.Data/SimpleTransaction.cs index 08338dde..5af48cf3 100644 --- a/Simple.Data/SimpleTransaction.cs +++ b/Simple.Data/SimpleTransaction.cs @@ -127,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); } } From d841211f2325e8f7463c8249705544142b1ea361 Mon Sep 17 00:00:00 2001 From: Athari Date: Sun, 7 Apr 2013 23:27:32 +0400 Subject: [PATCH 119/160] Fixed NameResolutionTests.CompaniesPluralizationIsResolved test (fixed culture to "en-us") --- Simple.Data.BehaviourTest/NameResolutionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From fb89e8daec311cff59cb6ff133e4adec765d825f Mon Sep 17 00:00:00 2001 From: Athari Date: Sun, 7 Apr 2013 23:38:34 +0400 Subject: [PATCH 120/160] Fixed SQL log message format --- Simple.Data.Ado/TraceHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simple.Data.Ado/TraceHelper.cs b/Simple.Data.Ado/TraceHelper.cs index 506e5203..562c570a 100644 --- a/Simple.Data.Ado/TraceHelper.cs +++ b/Simple.Data.Ado/TraceHelper.cs @@ -21,11 +21,11 @@ public static void WriteTrace (this IDbCommand command) sb.AppendFormat("SQL command (CommandType={0}):{1} {2}", command.CommandType, EOL, command.CommandText); if (command.Parameters.Count > 0) { - sb.AppendFormat("Parameters:{0}", EOL); + 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}", parameter.ParameterName, parameter.DbType, strValue, EOL); + sb.AppendFormat("{0} {1} ({2}) = {3}", EOL, parameter.ParameterName, parameter.DbType, strValue); } } SimpleDataTraceSources.TraceSource.TraceEvent(TraceEventType.Information, SimpleDataTraceSources.SqlMessageId, sb.ToString()); From 0ab6de644b54123c8b42661d793eee7ae2dcfc77 Mon Sep 17 00:00:00 2001 From: Athari Date: Mon, 8 Apr 2013 00:01:39 +0400 Subject: [PATCH 121/160] Improved UnresolvableObjectException messages --- Simple.Data.Ado/ProcedureExecutor.cs | 2 +- Simple.Data.Ado/Schema/ColumnCollection.cs | 2 +- Simple.Data.Ado/Schema/ProcedureCollection.cs | 5 +++-- Simple.Data.Ado/Schema/Table.cs | 3 ++- Simple.Data.Ado/Schema/TableCollection.cs | 5 +++-- Simple.Data.Mocking/XmlMockAdapter.cs | 2 +- Simple.Data/SimpleRecord.cs | 4 ++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index e3046747..5708e66b 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -40,7 +40,7 @@ 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; 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/ProcedureCollection.cs b/Simple.Data.Ado/Schema/ProcedureCollection.cs index d66407e0..a0d601e7 100644 --- a/Simple.Data.Ado/Schema/ProcedureCollection.cs +++ b/Simple.Data.Ado/Schema/ProcedureCollection.cs @@ -33,7 +33,7 @@ public Procedure Find(string procedureName) if (procedure == null) { - throw new UnresolvableObjectException(procedureName, "No matching procedure found, or insufficient permissions."); + throw new UnresolvableObjectException(procedureName, string.Format("Procedure '{0}' not found, or insufficient permissions.", procedureName)); } return procedure; @@ -83,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; diff --git a/Simple.Data.Ado/Schema/Table.cs b/Simple.Data.Ado/Schema/Table.cs index 546ea7e9..a659dac3 100644 --- a/Simple.Data.Ado/Schema/Table.cs +++ b/Simple.Data.Ado/Schema/Table.cs @@ -88,7 +88,8 @@ 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); } } 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.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/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index e7440526..e1965675 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -73,9 +73,9 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) return true; } } - catch (UnresolvableObjectException) + catch (UnresolvableObjectException e) { - throw new UnresolvableObjectException("Column not found."); + throw new UnresolvableObjectException(e.ObjectName, string.Format("Column '{0}' not found.", e.ObjectName)); } } return base.TryGetMember(binder, out result); From 4b9b994f5a98f2c277817b11d1a92aed6d99ac6f Mon Sep 17 00:00:00 2001 From: Athari Date: Mon, 8 Apr 2013 00:03:45 +0400 Subject: [PATCH 122/160] Improved SchemaResolutionException messages --- Simple.Data.Ado/Joiner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simple.Data.Ado/Joiner.cs b/Simple.Data.Ado/Joiner.cs index c69a896e..79b8e6ac 100644 --- a/Simple.Data.Ado/Joiner.cs +++ b/Simple.Data.Ado/Joiner.cs @@ -111,7 +111,7 @@ private static ForeignKey GetForeignKey(Table table1, Table table2) 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; } From 3fd096fdc21c009ef87edec9a7963f12129d4be7 Mon Sep 17 00:00:00 2001 From: Athari Date: Mon, 8 Apr 2013 00:20:54 +0400 Subject: [PATCH 123/160] Improved AdoAdapterException messages, ensured stack trace is not cut off --- .../AdoAdapter.IAdapterWithTransactions.cs | 2 +- Simple.Data.Ado/AdoAdapterException.cs | 60 +++++++++---------- Simple.Data.Ado/AdoAdapterFinder.cs | 4 +- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 2 +- Simple.Data.Ado/AdoAdapterRelatedFinder.cs | 2 +- Simple.Data.Ado/DbCommandExtensions.cs | 2 +- Simple.Data.Ado/ProcedureExecutor.cs | 2 +- 7 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs index 05d61dba..915beb12 100644 --- a/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs +++ b/Simple.Data.Ado/AdoAdapter.IAdapterWithTransactions.cs @@ -58,7 +58,7 @@ public int UpdateMany(string tableName, IEnumerable> 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); } 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 370e1caa..5441b4f5 100644 --- a/Simple.Data.Ado/AdoAdapterFinder.cs +++ b/Simple.Data.Ado/AdoAdapterFinder.cs @@ -139,7 +139,7 @@ private IEnumerable> TryExecuteQuery(IDbConnection c } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command); + throw new AdoAdapterException(ex.Message, command, ex); } } @@ -151,7 +151,7 @@ private IEnumerable> TryExecuteQuery(IDbConnection c } catch (DbException ex) { - throw new AdoAdapterException(ex.Message, command); + throw new AdoAdapterException(ex.Message, command, ex); } } diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 51d6c2f1..14d51064 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -185,7 +185,7 @@ private void ApplyPaging(SimpleQuery query, List commandBuilder var table = _adapter.GetSchema().FindTable(query.TableName); if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) { - throw new AdoAdapterException("Cannot apply paging to a table with no primary key."); + throw new AdoAdapterException(string.Format("Cannot apply paging to table '{0}' with no primary key.", table.ActualName)); } var keys = table.PrimaryKey.AsEnumerable() .Select(k => string.Format("{0}.{1}", table.QualifiedName, _adapter.GetSchema().QuoteObjectName(k))) 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/DbCommandExtensions.cs b/Simple.Data.Ado/DbCommandExtensions.cs index 7adbfba0..31b0a7ab 100644 --- a/Simple.Data.Ado/DbCommandExtensions.cs +++ b/Simple.Data.Ado/DbCommandExtensions.cs @@ -87,7 +87,7 @@ private static AdoAdapterException CreateAdoAdapterException(IDbCommand command, { return new AdoAdapterException(ex.Message, command.CommandText, command.Parameters.Cast() - .ToDictionary(p => p.ParameterName, p => p.Value)); + .ToDictionary(p => p.ParameterName, p => p.Value), ex); } internal static void DisposeCommandAndReader(IDbConnection connection, IDbCommand command, IDataReader reader) diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index 5708e66b..e82670c7 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -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); } } } From 4438c822dfeb276fd1a055bb0b60a948ef1c19ad Mon Sep 17 00:00:00 2001 From: Athari Date: Mon, 8 Apr 2013 00:28:04 +0400 Subject: [PATCH 124/160] Improved SimpleDataException messages --- Simple.Data.Ado/AdoAdapterInserter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simple.Data.Ado/AdoAdapterInserter.cs b/Simple.Data.Ado/AdoAdapterInserter.cs index dd44cf44..46afd760 100644 --- a/Simple.Data.Ado/AdoAdapterInserter.cs +++ b/Simple.Data.Ado/AdoAdapterInserter.cs @@ -98,7 +98,7 @@ private void CheckInsertablePropertiesAreAvailable(Table table, IEnumerable Date: Mon, 8 Apr 2013 00:32:07 +0400 Subject: [PATCH 125/160] Changed BadExpressionException's base class --- Simple.Data/BadExpressionException.cs | 30 +++++++-------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/Simple.Data/BadExpressionException.cs b/Simple.Data/BadExpressionException.cs index 740f2c82..84f9dec9 100644 --- a/Simple.Data/BadExpressionException.cs +++ b/Simple.Data/BadExpressionException.cs @@ -1,37 +1,21 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text; namespace Simple.Data { [Serializable] - public class BadExpressionException : Exception + public class BadExpressionException : ArgumentException { - // - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - // - 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) - { - } + protected BadExpressionException(SerializationInfo info, StreamingContext context) : base(info, context) + {} } -} +} \ No newline at end of file From 1b3c37d766583e25b51112a837edf0e0195fce92 Mon Sep 17 00:00:00 2001 From: Athari Date: Mon, 8 Apr 2013 01:12:16 +0400 Subject: [PATCH 126/160] Misc exception messages improved (AE, ANE, IOE), ensured stack trace not cut off --- Simple.Data.Ado/AdoAdapterGetter.cs | 4 ++-- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 4 ++-- Simple.Data.Ado/ProviderHelper.cs | 4 ++-- Simple.Data.Ado/Schema/DatabaseSchema.cs | 2 +- Simple.Data.Ado/Schema/ProcedureCollection.cs | 2 +- Simple.Data.Ado/SimpleReferenceFormatter.cs | 4 +++- Simple.Data.Mocking/Ado/MockSchemaProvider.cs | 2 +- Simple.Data.SqlCe40/SqlCe40SchemaProvider.cs | 2 +- Simple.Data.SqlServer/SqlSchemaProvider.cs | 2 +- Simple.Data/InMemoryAdapter.cs | 2 +- Simple.Data/QueryPolyfills/ObjectMaths.cs | 8 +++++--- Simple.Data/SimpleRecord.cs | 2 +- 12 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterGetter.cs b/Simple.Data.Ado/AdoAdapterGetter.cs index 34141651..ac8feb87 100644 --- a/Simple.Data.Ado/AdoAdapterGetter.cs +++ b/Simple.Data.Ado/AdoAdapterGetter.cs @@ -35,7 +35,7 @@ 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."); @@ -107,7 +107,7 @@ 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."); diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index 14d51064..b1fa6ee9 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -83,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(); diff --git a/Simple.Data.Ado/ProviderHelper.cs b/Simple.Data.Ado/ProviderHelper.cs index 870ebce9..9bc3b054 100644 --- a/Simple.Data.Ado/ProviderHelper.cs +++ b/Simple.Data.Ado/ProviderHelper.cs @@ -68,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(); } @@ -111,7 +111,7 @@ private static IConnectionProvider LoadProviderByConnectionToken(ConnectionToken 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); diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 1290f01d..5929af83 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -139,7 +139,7 @@ public ObjectName BuildObjectName(String text) if (text == null) throw new ArgumentNullException("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]); } diff --git a/Simple.Data.Ado/Schema/ProcedureCollection.cs b/Simple.Data.Ado/Schema/ProcedureCollection.cs index a0d601e7..7608108e 100644 --- a/Simple.Data.Ado/Schema/ProcedureCollection.cs +++ b/Simple.Data.Ado/Schema/ProcedureCollection.cs @@ -56,7 +56,7 @@ 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]); } if (!string.IsNullOrWhiteSpace(_defaultSchema)) diff --git a/Simple.Data.Ado/SimpleReferenceFormatter.cs b/Simple.Data.Ado/SimpleReferenceFormatter.cs index 090b9504..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; @@ -99,7 +101,7 @@ private string MathOperatorToString(MathOperator @operator) case MathOperator.Modulo: return _schema.Operators.Modulo; default: - throw new InvalidOperationException("Invalid MathOperator specified."); + throw new InvalidEnumArgumentException("Invalid MathOperator specified."); } } 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.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.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index b664c795..9d022670 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -150,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; } diff --git a/Simple.Data/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 5de7c830..80353472 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -373,7 +373,7 @@ public bool IsValidFunction(string functionName) public IEnumerable>>> Execute(string functionName, IDictionary parameters) { - if (!_functions.ContainsKey(functionName)) throw new InvalidOperationException("No function found with that name."); + if (!_functions.ContainsKey(functionName)) throw new InvalidOperationException(string.Format("Function '{0}' not found.", functionName)); var obj = _functions[functionName].DynamicInvoke(parameters.Values.ToArray()); var dict = obj as IDictionary; 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/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index e1965675..8af28aaf 100644 --- a/Simple.Data/SimpleRecord.cs +++ b/Simple.Data/SimpleRecord.cs @@ -75,7 +75,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) } catch (UnresolvableObjectException e) { - throw new UnresolvableObjectException(e.ObjectName, string.Format("Column '{0}' not found.", e.ObjectName)); + throw new UnresolvableObjectException(e.ObjectName, string.Format("Column '{0}' not found.", e.ObjectName), e); } } return base.TryGetMember(binder, out result); From 430f88ce3ed51be67346ed5cbb9eefda9188e0a0 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 11 Apr 2013 19:50:45 +0100 Subject: [PATCH 127/160] added ability to setup mock for procedure with output parameters --- Simple.Data.InMemoryTest/InMemoryTests.cs | 17 +++++++++ Simple.Data/InMemoryAdapter.cs | 42 ++++++++++++++++++----- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index e1adb998..51770843 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -638,6 +638,23 @@ public void ProcedureWithParametersReturningArrayShouldWork() } } + [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() { diff --git a/Simple.Data/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 5de7c830..2b789c58 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -13,7 +13,26 @@ public partial class InMemoryAdapter : Adapter, IAdapterWithFunctions private readonly Dictionary>> _tables; - private readonly Dictionary _functions = new Dictionary(); + [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(); @@ -338,32 +357,37 @@ public JoinConfig Detail(string tableName, string keyName, string propertyNameIn public void AddFunction(string functionName, Func function) { - _functions.Add(functionName, function); + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); } public void AddFunction(string functionName, Func function) { - _functions.Add(functionName, function); + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); } public void AddFunction(string functionName, Func function) { - _functions.Add(functionName, function); + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); } public void AddFunction(string functionName, Func function) { - _functions.Add(functionName, function); + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); } public void AddFunction(string functionName, Func function) { - _functions.Add(functionName, function); + _functions.Add(functionName, new FunctionInfo(function, FunctionFlags.None)); } public void AddDelegate(string functionName, Delegate function) { - _functions.Add(functionName, 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) @@ -374,7 +398,9 @@ public bool IsValidFunction(string functionName) public IEnumerable>>> Execute(string functionName, IDictionary parameters) { if (!_functions.ContainsKey(functionName)) throw new InvalidOperationException("No function found with that name."); - var obj = _functions[functionName].DynamicInvoke(parameters.Values.ToArray()); + 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 } }; From 65ec558e79b2c4d42e8107f61058ea909036b249 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 11 Apr 2013 19:55:38 +0100 Subject: [PATCH 128/160] added line from .gitignore lost in merge --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 08c5d3ba..bffe325c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ 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/ \ No newline at end of file +packages/jQuery.Validation.1.9.0.1/ +Simple.Data.sln.ide \ No newline at end of file From 03891c39238e1d395c9a7242d17a0e51a0ead0f8 Mon Sep 17 00:00:00 2001 From: Mark Rendle Date: Fri, 10 May 2013 00:01:17 +0100 Subject: [PATCH 129/160] Fixed DatabaseReset and ignored a test too far --- Simple.Data.SqlTest/QueryTest.cs | 2 +- Simple.Data.SqlTest/Resources/DatabaseReset.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index a445e3e1..32f03a1d 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -346,7 +346,7 @@ public void FindAllWithClauseWithJoinCriteriaShouldPreselectDetailTableAsCollect Assert.AreEqual(1, orderItems.Count); } - [Test] + [Test, Ignore] public void FindAllWithClauseWithNestedDetailTable() { var db = DatabaseHelper.Open(); diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index bdbeb79c..e01b00e6 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -414,11 +414,11 @@ CREATE FUNCTION [dbo].[VarcharAndReturnInt] (@AValue varchar(50)) RETURNS INT AS RETURN 42 END GO -CREATE TRIGGER [test].trg_schematable_after_insert ON [Simple.Data].[test].[SchemaTable] AFTER INSERT +CREATE TRIGGER [test].trg_schematable_after_insert ON [test].[SchemaTable] AFTER INSERT AS BEGIN SET NOCOUNT ON; - UPDATE [Simple.Data].[test].[SchemaTable] SET [Optional] = 'Modified By Trigger' WHERE [Id] in (select Id from inserted) + UPDATE [test].[SchemaTable] SET [Optional] = 'Modified By Trigger' WHERE [Id] in (select Id from inserted) END GO CREATE TABLE [dbo].[GroupTestMaster] ( From 20dc705751cd946652af1843fa5cc4274a5f595c Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 11:24:31 +0100 Subject: [PATCH 130/160] Fixes issue #256 --- Simple.Data.Ado/DataReaderEnumerable.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Simple.Data.Ado/DataReaderEnumerable.cs b/Simple.Data.Ado/DataReaderEnumerable.cs index a6881a78..313554ec 100644 --- a/Simple.Data.Ado/DataReaderEnumerable.cs +++ b/Simple.Data.Ado/DataReaderEnumerable.cs @@ -112,6 +112,11 @@ public bool MoveNext() if (_reader == null) return false; } + if (_reader.IsClosed) + { + return false; + } + return _reader.Read() ? SetCurrent() : EndRead(); } From 586cb43873c1620221c8135f593833e030ff214d Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 11:24:46 +0100 Subject: [PATCH 131/160] Fixed tests that use Trace listener --- Simple.Data.Mocking/Ado/MockDataReader.cs | 5 +++-- Simple.Data.SqlTest/ProcedureTest.cs | 5 +++-- Simple.Data.SqlTest/QueryTest.cs | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) 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.SqlTest/ProcedureTest.cs b/Simple.Data.SqlTest/ProcedureTest.cs index 4e7be2b4..f8e699a9 100644 --- a/Simple.Data.SqlTest/ProcedureTest.cs +++ b/Simple.Data.SqlTest/ProcedureTest.cs @@ -79,14 +79,15 @@ public void GetCustomerCountAsOutputTest() [Test] public void GetCustomerCountSecondCallExecutesNonQueryTest() { + SimpleDataTraceSources.TraceSource.Switch.Level = SourceLevels.All; var listener = new TestTraceListener(); - Trace.Listeners.Add(listener); + 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")); - Trace.Listeners.Remove(listener); + SimpleDataTraceSources.TraceSource.Listeners.Remove(listener); } #endif diff --git a/Simple.Data.SqlTest/QueryTest.cs b/Simple.Data.SqlTest/QueryTest.cs index 32f03a1d..3b28658a 100644 --- a/Simple.Data.SqlTest/QueryTest.cs +++ b/Simple.Data.SqlTest/QueryTest.cs @@ -461,14 +461,16 @@ public void SelfJoinShouldNotThrowException() [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(); - Trace.Listeners.Remove(traceListener); + SimpleDataTraceSources.TraceSource.Listeners.Remove(traceListener); Assert.Greater(traceListener.Output.IndexOf("order by [manager].[name]", StringComparison.OrdinalIgnoreCase), 0); } From 98e303e0f4533c38df2fbbbf12c5c412220cf778 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 12:26:35 +0100 Subject: [PATCH 132/160] Fixes issue #250 --- Simple.Data.SqlServer/SqlQueryPager.cs | 7 ++++--- Simple.Data.SqlTest/SqlQueryPagerTest.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index e7429205..6985ebcc 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -11,7 +11,7 @@ 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 ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(\sFROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase); private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*(DISTINCT)?", RegexOptions.IgnoreCase); public IEnumerable ApplyLimit(string sql, int take) @@ -19,8 +19,9 @@ 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) - { + public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int take) + { + sql = sql.Replace(Environment.NewLine, " "); var builder = new StringBuilder("WITH __Data AS (SELECT "); var match = ColumnExtract.Match(sql); diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index a5c902e3..0a6e813f 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -77,6 +77,21 @@ public void ShouldCopeWithAliasedColumns() var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); Assert.AreEqual(expected[0], modified[0]); + } + + [Test] + public void ShouldCopeWithColumnsThatEndInFrom() + { + 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 expected = @"WITH __Data AS (SELECT [dbo].[PromoPosts].[Id],[dbo].[PromoPosts].[ActiveFrom],[dbo].[PromoPosts].[ActiveTo],[dbo].[PromoPosts].[Created],[dbo].[PromoPosts].[Updated],ROW_NUMBER() OVER(ORDER BY [dbo].[PromoPosts].[ActiveFrom]) AS [_#_] from [dbo].[PromoPosts]) SELECT [Id],[ActiveFrom],[ActiveTo],[Created],[Updated] FROM __Data WHERE [_#_] BETWEEN 1 AND 25"; + expected = expected.ToLowerInvariant(); + + var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[PromoPosts].[Id]"}, 0, 25).Single(); + var modified = Normalize.Replace(pagedSql, " ").ToLowerInvariant(); + Assert.AreEqual(expected, modified); } } } From 547845625eb2e329130a499562f0860c61d194a8 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 12:43:45 +0100 Subject: [PATCH 133/160] Partial fix for issue #232 --- .../Query/ExplicitJoinTest.cs | 7 +++++++ Simple.Data.SqlTest/SqlQueryPagerTest.cs | 2 +- Simple.Data/BadExpressionException.cs | 16 ++++++++++++++++ Simple.Data/SimpleQuery.cs | 8 ++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs index 62af5f58..1b2f9371 100644 --- a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs +++ b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs @@ -290,6 +290,13 @@ public void PassingObjectReferenceToOnThrowsBadExpressionException() () => _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.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index 0a6e813f..bfd60dcd 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -86,7 +86,7 @@ public void ShouldCopeWithColumnsThatEndInFrom() from [dbo].[PromoPosts] ORDER BY [dbo].[PromoPosts].[ActiveFrom]"; - var expected = @"WITH __Data AS (SELECT [dbo].[PromoPosts].[Id],[dbo].[PromoPosts].[ActiveFrom],[dbo].[PromoPosts].[ActiveTo],[dbo].[PromoPosts].[Created],[dbo].[PromoPosts].[Updated],ROW_NUMBER() OVER(ORDER BY [dbo].[PromoPosts].[ActiveFrom]) AS [_#_] from [dbo].[PromoPosts]) SELECT [Id],[ActiveFrom],[ActiveTo],[Created],[Updated] FROM __Data WHERE [_#_] BETWEEN 1 AND 25"; + 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(); var pagedSql = new SqlQueryPager().ApplyPaging(sql, new[] {"[dbo].[PromoPosts].[Id]"}, 0, 25).Single(); diff --git a/Simple.Data/BadExpressionException.cs b/Simple.Data/BadExpressionException.cs index 84f9dec9..59af45d8 100644 --- a/Simple.Data/BadExpressionException.cs +++ b/Simple.Data/BadExpressionException.cs @@ -18,4 +18,20 @@ public BadExpressionException(string message, Exception inner) : base(message, i 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/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index fdb0529e..bd483584 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -599,7 +599,11 @@ private SimpleQuery ParseJoin(InvokeMemberBinder binder, object[] args) tableToJoin = dynamicTable.ToObjectReference(); } } - if (tableToJoin == null) throw new InvalidOperationException(); + 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; @@ -613,7 +617,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); From 4a98b944fbbff8dd22fd16e3abfdfd67a6b3c950 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:13:24 +0100 Subject: [PATCH 134/160] Fixes issue #266 --- Simple.Data.BehaviourTest/GetCountTest.cs | 7 +++++++ .../Query/ExplicitJoinTest.cs | 2 +- Simple.Data/Commands/ExistsCommand.cs | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Simple.Data.BehaviourTest/GetCountTest.cs b/Simple.Data.BehaviourTest/GetCountTest.cs index 17801951..e33b5940 100644 --- a/Simple.Data.BehaviourTest/GetCountTest.cs +++ b/Simple.Data.BehaviourTest/GetCountTest.cs @@ -24,6 +24,13 @@ public void GetCountBasic() 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() { diff --git a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs index 1b2f9371..a580d8b1 100644 --- a/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs +++ b/Simple.Data.BehaviourTest/Query/ExplicitJoinTest.cs @@ -293,7 +293,7 @@ public void PassingObjectReferenceToOnThrowsBadExpressionException() [Test] public void PassingTablesWrongWayRoundThrowsBadExpressionException() { - Assert.Throws( + Assert.Throws( () => _db.Activity.All().Join(_db.Activity, LocationId: _db.Location.ID_Location)); } diff --git a/Simple.Data/Commands/ExistsCommand.cs b/Simple.Data/Commands/ExistsCommand.cs index e5cad65a..d94398b8 100644 --- a/Simple.Data/Commands/ExistsCommand.cs +++ b/Simple.Data/Commands/ExistsCommand.cs @@ -29,9 +29,21 @@ 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(); From 8dfe60f83c36df12b71b4b2309d7085287285150 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:19:01 +0100 Subject: [PATCH 135/160] Fixes #268 --- Simple.Data.InMemoryTest/InMemoryTests.cs | 30 +++++++++++++++++++++++ Simple.Data/Commands/ExistsByCommand.cs | 5 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index 92b6ea86..0bb4b623 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -532,6 +532,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() diff --git a/Simple.Data/Commands/ExistsByCommand.cs b/Simple.Data/Commands/ExistsByCommand.cs index faade45b..daf31a78 100644 --- a/Simple.Data/Commands/ExistsByCommand.cs +++ b/Simple.Data/Commands/ExistsByCommand.cs @@ -30,7 +30,10 @@ 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)); + var criteriaDictionary = ArgumentHelper.CreateCriteriaDictionary(binder, args, "ExistsBy", "exists_by", "AnyBy", "any_by"); + if (criteriaDictionary == null) return null; + + var criteria = ExpressionHelper.CriteriaDictionaryToExpression(table.GetQualifiedName(), criteriaDictionary); return new SimpleQuery(dataStrategy, table.GetQualifiedName()).Where(criteria).Exists(); } } From 6a58fa5f147efa5b00e557a40fa241675914f214 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:25:21 +0100 Subject: [PATCH 136/160] Fixes #270 --- Simple.Data.BehaviourTest/Query/QueryTest.cs | 12 ++++++++++++ Simple.Data/SimpleQuery.cs | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/Simple.Data.BehaviourTest/Query/QueryTest.cs b/Simple.Data.BehaviourTest/Query/QueryTest.cs index de520d46..a088b549 100644 --- a/Simple.Data.BehaviourTest/Query/QueryTest.cs +++ b/Simple.Data.BehaviourTest/Query/QueryTest.cs @@ -228,5 +228,17 @@ public void SpecifyingJoinTableShouldCreateDirectQuery() GeneratedSqlIs("select [dbo].[userbio].[userid],[dbo].[userbio].[text] from [dbo].[userbio]" + " 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)); + } } } diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index bd483584..8264df1d 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -650,9 +650,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)); From ec5fde89c9d04d8131f3f3e945f416e6092da4f2 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:35:39 +0100 Subject: [PATCH 137/160] Fixes #271 and #272 --- Simple.Data.InMemoryTest/InMemoryTests.cs | 20 +++++++++++++++++++- Simple.Data/SimpleQuery.cs | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index 0bb4b623..a924cbc3 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -251,7 +251,7 @@ public void TestOrderBy() Assert.AreEqual(9, records[0].Id); } - [Test] + [Test] public void TestOrderByThenBy() { Database.UseMockAdapter(new InMemoryAdapter()); @@ -269,6 +269,24 @@ public void TestOrderByThenBy() 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() { diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 8264df1d..c7c98301 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -242,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))); @@ -327,6 +341,10 @@ 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; } From 506125b7a783021bc1d567875d83219cd189af32 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:40:35 +0100 Subject: [PATCH 138/160] Fixes #278 --- Simple.Data/SimpleQuery.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index c7c98301..0d7e5d7a 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -270,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); } @@ -278,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) @@ -686,6 +681,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)) { From a1b859b0023b892b7f3486b380ce924f413ca142 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:42:10 +0100 Subject: [PATCH 139/160] Fixes #279 --- Simple.Data/SimpleQuery.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 0d7e5d7a..7446e60d 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -345,6 +345,10 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } 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; } From 22cc012b8869553ecadbb9a74f6c8aaf1ae082cd Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:45:08 +0100 Subject: [PATCH 140/160] Fixes #283 --- Simple.Data/SimpleQuery.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 7446e60d..28de3f4d 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -458,6 +458,10 @@ private SimpleQuery ParseWith(InvokeMemberBinder binder, object[] args) } 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)); } From c16801b7229da2e1ef28342f7ee2763d9f02b5f7 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:47:58 +0100 Subject: [PATCH 141/160] Fixes #284 --- Simple.Data/SimpleQuery.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 28de3f4d..1a1ae7f8 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -455,6 +455,8 @@ 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); From 1f035a793e6d506810569969264cb2f7e3f0e826 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:49:48 +0100 Subject: [PATCH 142/160] Fixes #287 --- Simple.Data/SimpleQuery.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 1a1ae7f8..58f7ba7e 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -375,6 +375,10 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o result = new SimpleQuery(this, _clauses.Append(new HavingClause(expression))); return true; } + else + { + throw new ArgumentException("Having requires an expression"); + } } if (binder.Name.StartsWith("with", StringComparison.OrdinalIgnoreCase) && !binder.Name.Equals("WithTotalCount", StringComparison.OrdinalIgnoreCase)) { From 3ea81b89037d6b9d95ec9012a94bcd510ed3e07b Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 13:51:30 +0100 Subject: [PATCH 143/160] Fixes #288 --- Simple.Data/SimpleQuery.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Simple.Data/SimpleQuery.cs b/Simple.Data/SimpleQuery.cs index 58f7ba7e..81496cef 100644 --- a/Simple.Data/SimpleQuery.cs +++ b/Simple.Data/SimpleQuery.cs @@ -369,16 +369,21 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o } if (binder.Name.Equals("having", StringComparison.OrdinalIgnoreCase)) { - var expression = args.SingleOrDefault() as SimpleExpression; - if (expression != null) + SimpleExpression expression; + try { - result = new SimpleQuery(this, _clauses.Append(new HavingClause(expression))); - return true; + expression = args.SingleOrDefault() as SimpleExpression; } - else + 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) && !binder.Name.Equals("WithTotalCount", StringComparison.OrdinalIgnoreCase)) { From 7bf50d866ed72cf52cdf0d9696c14fe196f06670 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 14:10:54 +0100 Subject: [PATCH 144/160] Fixes #289 --- Simple.Data.InMemoryTest/InMemoryTests.cs | 2 +- Simple.Data/DataStrategy.cs | 2 +- Simple.Data/DynamicTable.cs | 4 ++- Simple.Data/ObjectReference.cs | 31 ++++++++++++++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index a924cbc3..a04261ab 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -750,7 +750,7 @@ public void UpsertShouldUpdateExistingRecord() } [Test] - public void UpsertWithoutDefinedKeyColumnsSHouldThrowMeaningfulException() + public void UpsertWithoutDefinedKeyColumnsShouldThrowMeaningfulException() { var adapter = new InMemoryAdapter(); Database.UseMockAdapter(adapter); diff --git a/Simple.Data/DataStrategy.cs b/Simple.Data/DataStrategy.cs index 8ec5b7c9..e1f2990b 100644 --- a/Simple.Data/DataStrategy.cs +++ b/Simple.Data/DataStrategy.cs @@ -101,7 +101,7 @@ internal DynamicTable SetMemberAsTable(ObjectReference reference, DynamicTable t 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()]; } diff --git a/Simple.Data/DynamicTable.cs b/Simple.Data/DynamicTable.cs index 1d5930a5..4c98eb90 100644 --- a/Simple.Data/DynamicTable.cs +++ b/Simple.Data/DynamicTable.cs @@ -100,7 +100,9 @@ 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) diff --git a/Simple.Data/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 16a0472a..83e94833 100644 --- a/Simple.Data/ObjectReference.cs +++ b/Simple.Data/ObjectReference.cs @@ -119,10 +119,20 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o if (_dataStrategy != null) { 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... @@ -140,9 +150,16 @@ 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); - table.TryInvokeMember(binder, args, out result); + 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 @@ -158,7 +175,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)); } /// From 169ca4949ad6674fe4a1d269b7a5fe4a5b6b058d Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 14:19:21 +0100 Subject: [PATCH 145/160] Fixes #264 --- Simple.Data.SqlServer/SqlQueryPager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 6985ebcc..08c55d6e 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -42,7 +42,7 @@ public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int if (keys.Length > 1) builder.AppendFormat(string.Join(" AND ", keys.Select(MakeDataJoin))); else - builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin))); + builder.AppendFormat(MakeDataJoin(keys[0])); var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); builder.Append(rest); From 7203b26a6019f7e17bc4ec3510d9b945ebbb695f Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 14:30:33 +0100 Subject: [PATCH 146/160] Fixes #291 --- Simple.Data.BehaviourTest/Query/QueryTest.cs | 6 ++++++ Simple.Data/ObjectReference.cs | 13 ++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Simple.Data.BehaviourTest/Query/QueryTest.cs b/Simple.Data.BehaviourTest/Query/QueryTest.cs index a088b549..c4749644 100644 --- a/Simple.Data.BehaviourTest/Query/QueryTest.cs +++ b/Simple.Data.BehaviourTest/Query/QueryTest.cs @@ -240,5 +240,11 @@ public void SpecifyOrderByDescendingWithoutReferenceThrowsException() { Assert.Throws(() => _db.Users.All().OrderByDescending(1)); } + + [Test] + public void CallingFirstOnTableShouldThrowInvalidOperationException() + { + Assert.Throws(() => _db.Users.First()); + } } } diff --git a/Simple.Data/ObjectReference.cs b/Simple.Data/ObjectReference.cs index 83e94833..22fcaaee 100644 --- a/Simple.Data/ObjectReference.cs +++ b/Simple.Data/ObjectReference.cs @@ -137,10 +137,17 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o // Or it could be a schema reference... var schema = new DynamicSchema(_name, _dataStrategy); - if (schema.TryInvokeMember(binder, args, out result)) + try + { + if (schema.TryInvokeMember(binder, args, out result)) + { + _dataStrategy.SetMemberAsSchema(this); + return true; + } + } + catch (KeyNotFoundException) { - _dataStrategy.SetMemberAsSchema(this); - return true; + throw new InvalidOperationException(string.Format("Method {0} not recognised", binder.Name)); } } From b507becf23683d648c028b137e35fbd43be5cba3 Mon Sep 17 00:00:00 2001 From: markrendle Date: Tue, 21 May 2013 14:45:29 +0100 Subject: [PATCH 147/160] Fixes #295 --- Simple.Data.SqlServer/SqlQueryPager.cs | 3 +- Simple.Data.SqlTest/SqlQueryPagerTest.cs | 183 +++++++++++++---------- 2 files changed, 103 insertions(+), 83 deletions(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 08c55d6e..ccf83ab2 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -13,6 +13,7 @@ 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) { @@ -34,7 +35,7 @@ public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy); builder.AppendLine(); - builder.Append(fromEtc); + builder.Append(LeftJoinMatch.Replace(fromEtc, "")); builder.AppendLine(")"); builder.AppendFormat("SELECT {0} FROM __Data ", columns); builder.AppendFormat("JOIN {0} ON ", diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index bfd60dcd..5a23dd79 100644 --- a/Simple.Data.SqlTest/SqlQueryPagerTest.cs +++ b/Simple.Data.SqlTest/SqlQueryPagerTest.cs @@ -1,82 +1,82 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using NUnit.Framework; -using Simple.Data.SqlServer; - -namespace Simple.Data.SqlTest -{ - [TestFixture] - public class SqlQueryPagerTest - { - 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 SqlQueryPager().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 SqlQueryPager().ApplyLimit(sql, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); - - Assert.IsTrue(expected.SequenceEqual(modified)); - } - - [Test] - public void ShouldApplyPagingUsingOrderBy() - { - 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 [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, new[] {"[dbo].[d].[a]"}, 5, 10); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - - 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 [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 [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, new[]{"[dbo].[d].[a]"}, 20, 5); - var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); - - Assert.AreEqual(expected[0], modified[0]); +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Simple.Data.SqlServer; + +namespace Simple.Data.SqlTest +{ + [TestFixture] + public class SqlQueryPagerTest + { + 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 SqlQueryPager().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 SqlQueryPager().ApplyLimit(sql, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()); + + Assert.IsTrue(expected.SequenceEqual(modified)); + } + + [Test] + public void ShouldApplyPagingUsingOrderBy() + { + 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 [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, new[] {"[dbo].[d].[a]"}, 5, 10); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); + + 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 [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 [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, new[]{"[dbo].[d].[a]"}, 20, 5); + var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant()).ToArray(); + + Assert.AreEqual(expected[0], modified[0]); } [Test] @@ -92,6 +92,25 @@ from [dbo].[PromoPosts] 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); + } + } +} From 4f0228b9507e1d1df168e6732a02899d8badf569 Mon Sep 17 00:00:00 2001 From: Denis Bakharev Date: Fri, 7 Jun 2013 23:40:30 +0400 Subject: [PATCH 148/160] fix #297 --- Simple.Data.SqlServer/SqlDbTypeEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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[])}, From 203c60ad413c9983935108f4d0c4c084853b2891 Mon Sep 17 00:00:00 2001 From: Tim Bourguignon Date: Wed, 12 Jun 2013 12:54:54 +0200 Subject: [PATCH 149/160] Added a link to the SimpleFx (documentation) Github project --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aabdbd80..ddaac9b5 100644 --- a/README.md +++ b/README.md @@ -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 From 356e37037b189237c01dac161ce5844586c86b68 Mon Sep 17 00:00:00 2001 From: Richard Hopton Date: Wed, 7 Aug 2013 12:13:03 +0100 Subject: [PATCH 150/160] Fixes #301 --- Simple.Data.SqlServer/SqlQueryPager.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index ccf83ab2..b1d815ef 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -44,11 +44,12 @@ public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int 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); - + builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1} ", skip + 1, skip + take); + builder.Append(groupBy); yield return builder.ToString(); } @@ -79,5 +80,17 @@ private static string ExtractOrderBy(string columns, string[] keys, ref string f } 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; + } } } From ea19abe2ed93f67a65b65bac9f4a278e20b5a7b7 Mon Sep 17 00:00:00 2001 From: Richard Hopton Date: Thu, 15 Aug 2013 17:49:24 +0100 Subject: [PATCH 151/160] Fixes #301 - Whitespace issue --- Simple.Data.SqlServer/SqlQueryPager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index b1d815ef..3f409031 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -48,8 +48,9 @@ public IEnumerable ApplyPaging(string sql, string[] keys, int skip, int var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @""); builder.Append(rest); - builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1} ", skip + 1, skip + take); - builder.Append(groupBy); + builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take); + if (!string.IsNullOrWhiteSpace(groupBy)) + builder.AppendFormat(" {0}", groupBy); yield return builder.ToString(); } From 9d2d35de0fa68dfc62814797a3b72c2411c95b40 Mon Sep 17 00:00:00 2001 From: ptrstpp950 Date: Tue, 17 Sep 2013 15:09:41 +0200 Subject: [PATCH 152/160] Solving problem when after using confuser assembly.FullName throws exception --- Simple.Data/MefHelper.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Simple.Data/MefHelper.cs b/Simple.Data/MefHelper.cs index 00c9328f..95cabaaa 100644 --- a/Simple.Data/MefHelper.cs +++ b/Simple.Data/MefHelper.cs @@ -96,7 +96,14 @@ private static CompositionContainer CreateAppDomainContainer() private static bool IsSimpleDataAssembly(Assembly assembly) { - return assembly.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); + try + { + return assembly.GetFullName().StartsWith("Simple.Data.", StringComparison.OrdinalIgnoreCase); + } + catch + { + return false; + } } } } From 28e9b53c55bd77517ed5ec64a9901cfd667ae13c Mon Sep 17 00:00:00 2001 From: Maciej Aniserowicz Date: Tue, 24 Sep 2013 22:23:33 +0200 Subject: [PATCH 153/160] fixes #308 two things to consider: 1) I lost table name in exception message 2) I'm not sure if AdoAdapterException is the right one to throw --- Simple.Data.Ado/AdoAdapterQueryRunner.cs | 11 ++++++----- Simple.Data.SqlServer/SqlQueryPager.cs | 9 +++++++-- Simple.Data.SqlTest/SqlQueryPagerTest.cs | 11 +++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Simple.Data.Ado/AdoAdapterQueryRunner.cs b/Simple.Data.Ado/AdoAdapterQueryRunner.cs index b1fa6ee9..23343cb0 100644 --- a/Simple.Data.Ado/AdoAdapterQueryRunner.cs +++ b/Simple.Data.Ado/AdoAdapterQueryRunner.cs @@ -183,13 +183,14 @@ private void ApplyPaging(SimpleQuery query, List commandBuilder else { var table = _adapter.GetSchema().FindTable(query.TableName); - if (table.PrimaryKey == null || table.PrimaryKey.Length == 0) + var keys = new string[0]; + if (table.PrimaryKey != null && table.PrimaryKey.Length > 0) { - throw new AdoAdapterException(string.Format("Cannot apply paging to table '{0}' with no primary key.", table.ActualName)); + keys = table.PrimaryKey.AsEnumerable() + .Select(k => string.Format("{0}.{1}", table.QualifiedName, _adapter.GetSchema().QuoteObjectName(k))) + .ToArray(); } - var 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); diff --git a/Simple.Data.SqlServer/SqlQueryPager.cs b/Simple.Data.SqlServer/SqlQueryPager.cs index 3f409031..b40a971f 100644 --- a/Simple.Data.SqlServer/SqlQueryPager.cs +++ b/Simple.Data.SqlServer/SqlQueryPager.cs @@ -20,8 +20,13 @@ 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) - { + 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 "); diff --git a/Simple.Data.SqlTest/SqlQueryPagerTest.cs b/Simple.Data.SqlTest/SqlQueryPagerTest.cs index 5a23dd79..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 @@ -112,5 +113,15 @@ public void ShouldExcludeLeftJoinedTablesFromSubSelect() 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() + ); + } } } From 65baeba3c33b9a28ba5fb7e3dd2e513fe9687614 Mon Sep 17 00:00:00 2001 From: jdscolam Date: Wed, 20 Nov 2013 17:00:43 -0600 Subject: [PATCH 154/160] Fix to issue #323 See for more details: https://github.com/markrendle/Simple.Data/issues/323 --- Simple.Data.Ado/BulkInserterHelper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Simple.Data.Ado/BulkInserterHelper.cs b/Simple.Data.Ado/BulkInserterHelper.cs index d00874af..45f0f098 100644 --- a/Simple.Data.Ado/BulkInserterHelper.cs +++ b/Simple.Data.Ado/BulkInserterHelper.cs @@ -1,3 +1,5 @@ +using Simple.Data.Extensions; + namespace Simple.Data.Ado { using System; @@ -157,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) { From 42f651e1e4dad765883bad8346448744ffb64361 Mon Sep 17 00:00:00 2001 From: jdscolam Date: Fri, 22 Nov 2013 15:30:32 -0600 Subject: [PATCH 155/160] Solves Casting String to GUID When retrieving a string from the database and trying to map it to a GUID the safe convert would throw an exception and return null. This allows for converting strings to GUIDS if possible. --- Simple.Data/PropertySetterBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Simple.Data/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 2282f393..35b08478 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace Simple.Data { using System; @@ -357,6 +359,8 @@ internal static object SafeConvert(object source, Type 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); } From eb7578c1d92dcab3f735c8c95b08ccabeffc4487 Mon Sep 17 00:00:00 2001 From: jdscolam Date: Mon, 30 Dec 2013 13:13:55 -0600 Subject: [PATCH 156/160] Adding Schema Support This commit adds schema support to Simple.Data.Ado providers via the new ISchemaConnectionProvider interface, and allows them to support a schema per instance of Database.Open(...). This is primarily used for Oracle databases. In order to use the schema, just add the schema name to the appropriate Database.Open call (e.g. Database.OpenNamedConnection("TestConnectionName", "TEST_SCHEMA_NAME") ). --- Simple.Data.Ado/AdoAdapter.cs | 22 ++++++++-- Simple.Data.Ado/ISchemaConnectionProvider.cs | 9 ++++ Simple.Data.Ado/ProviderHelper.cs | 24 +++++++--- Simple.Data.Ado/Schema/DatabaseSchema.cs | 7 ++- Simple.Data.Ado/Simple.Data.Ado.csproj | 1 + Simple.Data/Database.Open.cs | 18 ++++++-- Simple.Data/DatabaseOpener.cs | 28 ++++++++++-- Simple.Data/DatabaseOpenerMethods.cs | 46 ++++++++++++-------- Simple.Data/IDatabaseOpener.cs | 4 +- Simple.Data/PropertySetterBuilder.cs | 2 +- 10 files changed, 123 insertions(+), 38 deletions(-) create mode 100644 Simple.Data.Ado/ISchemaConnectionProvider.cs diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index bdb5ce0d..bd713a32 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -105,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 { @@ -119,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); 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/ProviderHelper.cs b/Simple.Data.Ado/ProviderHelper.cs index 9bc3b054..482318a8 100644 --- a/Simple.Data.Ado/ProviderHelper.cs +++ b/Simple.Data.Ado/ProviderHelper.cs @@ -82,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) @@ -90,12 +90,12 @@ 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); } @@ -115,6 +115,11 @@ private static IConnectionProvider LoadProviderByConnectionToken(ConnectionToken } provider.SetConnectionString(token.ConnectionString); + + var schemaConnectionProvider = provider as ISchemaConnectionProvider; + if(schemaConnectionProvider != null) + schemaConnectionProvider.SetSchema(token.SchemaName); + return provider; } @@ -272,7 +277,7 @@ public override int GetHashCode() { unchecked { - return (ConnectionString.GetHashCode()*397) ^ ProviderName.GetHashCode(); + return (ConnectionString.GetHashCode()*397) ^ (ProviderName.GetHashCode() * 397) ^ SchemaName.GetHashCode(); } } @@ -288,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 @@ -305,6 +312,11 @@ public string ProviderName { get { return _providerName; } } + + public string SchemaName + { + get { return _schemaName; } + } } } } diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 5929af83..2a1e6265 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -125,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() diff --git a/Simple.Data.Ado/Simple.Data.Ado.csproj b/Simple.Data.Ado/Simple.Data.Ado.csproj index 9aaa0903..8f5e43ed 100644 --- a/Simple.Data.Ado/Simple.Data.Ado.csproj +++ b/Simple.Data.Ado/Simple.Data.Ado.csproj @@ -106,6 +106,7 @@ + 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/DatabaseOpener.cs b/Simple.Data/DatabaseOpener.cs index edf2b1b1..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,11 +98,21 @@ 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)); diff --git a/Simple.Data/DatabaseOpenerMethods.cs b/Simple.Data/DatabaseOpenerMethods.cs index 0d0f4057..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,42 +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 = null; - _openFile = _openConnection = _openNamedConnection = null; + _openDefault = _openFile = _openConnection = _openNamedConnection = null; _open = null; - _openConnectionWithProvider = null; + _openNamedConnectionAndSchema = _openConnectionWithProvider = null; + _openConnectionWithProviderAndSchema = null; } } } \ No newline at end of file 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/PropertySetterBuilder.cs b/Simple.Data/PropertySetterBuilder.cs index 35b08478..1156ef6f 100644 --- a/Simple.Data/PropertySetterBuilder.cs +++ b/Simple.Data/PropertySetterBuilder.cs @@ -359,7 +359,7 @@ internal static object SafeConvert(object source, Type 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); } From 36068114dc8d68c6e8a2a266f09b856f76ed2076 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Wed, 15 Jan 2014 13:37:13 -0500 Subject: [PATCH 157/160] added support for dynamic indexer getter to SimpleRecord --- Simple.Data.UnitTest/DynamicRecordTest.cs | 11 ++++++ Simple.Data/SimpleRecord.cs | 47 +++++++++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) 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/SimpleRecord.cs b/Simple.Data/SimpleRecord.cs index 8af28aaf..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; } @@ -67,9 +77,9 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) try { var relatedAdapter = _database.GetAdapter() as IAdapterWithRelation; - if (relatedAdapter != null && relatedAdapter.IsValidRelation(_tableName, binder.Name)) + if (relatedAdapter != null && relatedAdapter.IsValidRelation(_tableName, name)) { - result = GetRelatedData(binder, relatedAdapter); + result = GetRelatedData(name, relatedAdapter); return true; } } @@ -78,13 +88,15 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) 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) { @@ -94,10 +106,10 @@ private object GetRelatedData(GetMemberBinder binder, IAdapterWithRelation relat else { result = related is IDictionary - ? (object) new SimpleRecord(related as IDictionary, binder.Name, _database) + ? (object) new SimpleRecord(related as IDictionary, name, _database) : ((IEnumerable>) related).Select( - dict => new SimpleRecord(dict, binder.Name, _database)).ToList(); - _data[binder.Name] = result; + dict => new SimpleRecord(dict, name, _database)).ToList(); + _data[name] = result; } return result; @@ -115,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(); From 042e919cad807fbf6bb2991044b9e92d86feae88 Mon Sep 17 00:00:00 2001 From: Sandra Nystrom Date: Thu, 23 Jan 2014 14:40:25 +0100 Subject: [PATCH 158/160] Fixed bug when running IN-query on strings with InMemoryAdapter --- Simple.Data.InMemoryTest/InMemoryTests.cs | 12 ++++++++++++ Simple.Data/QueryPolyfills/WhereClauseHandler.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index a04261ab..9638aad2 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -49,6 +49,18 @@ public void InsertAndFindAllShouldWork() 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() { diff --git a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs index 43bee4a8..40e19e11 100644 --- a/Simple.Data/QueryPolyfills/WhereClauseHandler.cs +++ b/Simple.Data/QueryPolyfills/WhereClauseHandler.cs @@ -100,7 +100,7 @@ private Func, bool> EqualExpressionToWhereClause(Sim d => { var resolvedLeftOperand = Resolve(d, arg.LeftOperand); - if (resolvedLeftOperand.OfType().Any()) + if (resolvedLeftOperand.Any(o => !(o is string) && o is IEnumerable)) { return resolvedLeftOperand.OfType().Any( o => o.Cast().SequenceEqual(((IEnumerable)arg.RightOperand).Cast())); From 7d7a5652c549548d673e8b344caddd511ef40b31 Mon Sep 17 00:00:00 2001 From: Paul Marfleet Date: Sun, 23 Mar 2014 16:35:35 +0000 Subject: [PATCH 159/160] Don't run update if new values equal original Don't run update if new values equal original, and return zero for the number of rows changed --- Simple.Data.InMemoryTest/InMemoryTests.cs | 16 ++++++++++++++++ Simple.Data/DatabaseRunner.cs | 1 + Simple.Data/TransactionRunner.cs | 1 + 3 files changed, 18 insertions(+) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index a04261ab..91e35650 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -806,5 +806,21 @@ public void LeftJoinTest() 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/DatabaseRunner.cs b/Simple.Data/DatabaseRunner.cs index 9e9b0ba3..9c386a85 100644 --- a/Simple.Data/DatabaseRunner.cs +++ b/Simple.Data/DatabaseRunner.cs @@ -44,6 +44,7 @@ internal override int Update(string tableName, IDictionary newVa { SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); + if (changedValuesDict.Count == 0) return 0; return _adapter.Update(tableName, changedValuesDict, criteria); } diff --git a/Simple.Data/TransactionRunner.cs b/Simple.Data/TransactionRunner.cs index 7c3943fa..b7624373 100644 --- a/Simple.Data/TransactionRunner.cs +++ b/Simple.Data/TransactionRunner.cs @@ -96,6 +96,7 @@ internal override int Update(string tableName, IDictionary newVa { SimpleExpression criteria = CreateCriteriaFromOriginalValues(tableName, newValuesDict, originalValuesDict); var changedValuesDict = CreateChangedValuesDict(newValuesDict, originalValuesDict); + if (changedValuesDict.Count == 0) return 0; return _adapter.Update(tableName, changedValuesDict, criteria, _adapterTransaction); } From 8b7bf0b1aa421c83fba8cfb1aa156f13c2fe6200 Mon Sep 17 00:00:00 2001 From: Matt Richard Date: Tue, 25 Mar 2014 14:42:54 -0500 Subject: [PATCH 160/160] Fixes issue #338. (SqlBulkInserter breaks when the records given to it aren't all the same.) --- Simple.Data.SqlServer/SqlBulkInserter.cs | 22 ++++- Simple.Data.SqlTest/BulkInsertTest.cs | 82 +++++++++++++++++++ .../Resources/DatabaseReset.txt | 12 +++ 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/Simple.Data.SqlServer/SqlBulkInserter.cs b/Simple.Data.SqlServer/SqlBulkInserter.cs index a7563636..7738213e 100644 --- a/Simple.Data.SqlServer/SqlBulkInserter.cs +++ b/Simple.Data.SqlServer/SqlBulkInserter.cs @@ -44,13 +44,15 @@ public IEnumerable> Insert(AdoAdapter adapter, strin 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) { @@ -82,7 +84,7 @@ private SqlBulkCopyOptions BuildBulkCopyOptions(AdoAdapter adapter) return options; } - private DataTable CreateDataTable(AdoAdapter adapter, string tableName, ICollection keys, SqlBulkCopy bulkCopy) + private DataTable CreateDataTable(AdoAdapter adapter, string tableName, IEnumerable keys, SqlBulkCopy bulkCopy) { var table = adapter.GetSchema().FindTable(tableName); var dataTable = new DataTable(table.ActualName); @@ -104,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.SqlTest/BulkInsertTest.cs b/Simple.Data.SqlTest/BulkInsertTest.cs index 5c1bff94..9294ed4a 100644 --- a/Simple.Data.SqlTest/BulkInsertTest.cs +++ b/Simple.Data.SqlTest/BulkInsertTest.cs @@ -52,6 +52,59 @@ public void BulkInsertUsesSchemaAndFireTriggers() 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++) @@ -61,6 +114,35 @@ private static IEnumerable GenerateItems() } } + 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) diff --git a/Simple.Data.SqlTest/Resources/DatabaseReset.txt b/Simple.Data.SqlTest/Resources/DatabaseReset.txt index e01b00e6..1315bcc5 100644 --- a/Simple.Data.SqlTest/Resources/DatabaseReset.txt +++ b/Simple.Data.SqlTest/Resources/DatabaseReset.txt @@ -86,6 +86,8 @@ BEGIN 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, @@ -245,6 +247,16 @@ BEGIN [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')