commit 6996111becf28b6dd16c193d28396d9a5a11d940 Author: grzegorz.russek Date: Fri Aug 10 10:14:49 2012 +0000 Initial commit of first working version that is used in production environment. diff --git a/DynamORM.Mono.sln b/DynamORM.Mono.sln new file mode 100644 index 0000000..c059c37 --- /dev/null +++ b/DynamORM.Mono.sln @@ -0,0 +1,74 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamORM", "DynamORM\DynamORM.csproj", "{63963ED7-9C78-4672-A4D4-339B6E825503}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamORM.Tests.Mono", "DynamORM.Tests\DynamORM.Tests.Mono.csproj", "{D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {63963ED7-9C78-4672-A4D4-339B6E825503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Release|Any CPU.Build.0 = Release|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = DynamORM\DynamORM.csproj + Policies = $0 + $0.DotNetNamingPolicy = $1 + $1.DirectoryNamespaceAssociation = None + $1.ResourceNamePolicy = FileFormatDefault + $0.TextStylePolicy = $2 + $2.FileWidth = 120 + $2.RemoveTrailingWhitespace = True + $2.EolMarker = Windows + $2.inheritsSet = VisualStudio + $2.inheritsScope = text/plain + $2.scope = text/x-csharp + $0.CSharpFormattingPolicy = $3 + $3.inheritsSet = Mono + $3.inheritsScope = text/x-csharp + $3.scope = text/x-csharp + $0.TextStylePolicy = $4 + $4.FileWidth = 120 + $4.RemoveTrailingWhitespace = True + $4.EolMarker = Windows + $4.inheritsSet = VisualStudio + $4.inheritsScope = text/plain + $4.scope = text/plain + $0.TextStylePolicy = $5 + $5.FileWidth = 120 + $5.TabWidth = 2 + $5.RemoveTrailingWhitespace = True + $5.EolMarker = Windows + $5.inheritsSet = VisualStudio + $5.inheritsScope = text/plain + $5.scope = text/microsoft-resx + $0.XmlFormattingPolicy = $6 + $6.inheritsSet = null + $6.scope = text/microsoft-resx + $0.TextStylePolicy = $7 + $7.FileWidth = 120 + $7.TabWidth = 2 + $7.RemoveTrailingWhitespace = True + $7.EolMarker = Windows + $7.inheritsSet = VisualStudio + $7.inheritsScope = text/plain + $7.scope = application/xml + $0.XmlFormattingPolicy = $8 + $8.inheritsSet = Mono + $8.inheritsScope = application/xml + $8.scope = application/xml + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DynamORM.Tests/DynamORM.Tests.Mono.csproj b/DynamORM.Tests/DynamORM.Tests.Mono.csproj new file mode 100644 index 0000000..7480045 --- /dev/null +++ b/DynamORM.Tests/DynamORM.Tests.Mono.csproj @@ -0,0 +1,87 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764} + Library + Properties + DynamORM.Tests + DynamORM.Tests + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE;MONO + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;MONO + prompt + 4 + + + + + + + + + + + False + nunit + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {63963ED7-9C78-4672-A4D4-339B6E825503} + DynamORM + + + + + \ No newline at end of file diff --git a/DynamORM.Tests/DynamORM.Tests.csproj b/DynamORM.Tests/DynamORM.Tests.csproj new file mode 100644 index 0000000..a31eb30 --- /dev/null +++ b/DynamORM.Tests/DynamORM.Tests.csproj @@ -0,0 +1,88 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764} + Library + Properties + DynamORM.Tests + DynamORM.Tests + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + lib\System.Data.SQLite.dll + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {63963ED7-9C78-4672-A4D4-339B6E825503} + DynamORM + + + + + \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/AttachToDebugger.cs b/DynamORM.Tests/Helpers/AttachToDebugger.cs new file mode 100644 index 0000000..3d0b140 --- /dev/null +++ b/DynamORM.Tests/Helpers/AttachToDebugger.cs @@ -0,0 +1,57 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Diagnostics; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers +{ + /// Class responsible for users operations testing. + [TestFixture] + public class AttachToDebugger + { + /// Attach to debugger. + [Test] + [Explicit("Test for attaching debugger to NUnit test framework")] + public void Attach() + { + if (!Debugger.IsAttached) + Debugger.Launch(); + } + + /// Test anonymous type compatybility. + [Test] + public void TestAnonType() + { + var a = new { x = 1, y = 2 }; + var b = new { x = 3, y = 4 }; + + Assert.AreEqual(a.GetType(), b.GetType()); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/AttachToDebugger.cs.bak b/DynamORM.Tests/Helpers/AttachToDebugger.cs.bak new file mode 100644 index 0000000..d3eec73 --- /dev/null +++ b/DynamORM.Tests/Helpers/AttachToDebugger.cs.bak @@ -0,0 +1,57 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Diagnostics; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers +{ + /// Class responsible for users operations testing. + [TestFixture] + public class AttachToDebugger + { + /// Attach to debugger. + [Test] + [Explicit("Test for attaching debugger to NUnit test framework")] + public void Attach() + { + if (!Debugger.IsAttached) + Debugger.Launch(); + } + + /// Test anonymous type compatybility. + [Test] + public void TestAnonType () + { + var a = new { x = 1, y = 2 }; + var b = new { x = 3, y = 4 }; + + Assert.AreEqual (a.GetType (), b.GetType ()); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/PoolingTests.cs b/DynamORM.Tests/Helpers/PoolingTests.cs new file mode 100644 index 0000000..aa5a72d --- /dev/null +++ b/DynamORM.Tests/Helpers/PoolingTests.cs @@ -0,0 +1,101 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers +{ + /// Pooling tests. + [TestFixture] + public class PoolingTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Test single mode command disposing. + [Test] + public void TestSingleModeCommand() + { + CreateDynamicDatabase(); + + var cmd = Database.Open().CreateCommand(); + + cmd.SetCommand("SELECT COUNT(0) FROM sqlite_master;"); + + Database.Dispose(); + Database = null; + + Assert.Throws(() => cmd.ExecuteScalar()); + } + + /// Test single mode transaction disposing. + [Test] + public void TestSingleModeTransaction() + { + try + { + CreateDynamicDatabase(); + + using (var conn = Database.Open()) + using (var trans = conn.BeginTransaction()) + using (var cmd = conn.CreateCommand()) + { + Assert.AreEqual(1, cmd.SetCommand("INSERT INTO \"users\" (\"code\") VALUES ('999');").ExecuteNonQuery()); + + Database.Dispose(); + Database = null; + + trans.Commit(); + } + + // Verify (rollback) + CreateDynamicDatabase(); + Assert.AreEqual(0, Database.Table("users").Count(columns: "id", code: "999")); + } + finally + { + // Remove for next tests + Database.Dispose(); + Database = null; + } + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/Users.cs b/DynamORM.Tests/Helpers/Users.cs new file mode 100644 index 0000000..fe47a5c --- /dev/null +++ b/DynamORM.Tests/Helpers/Users.cs @@ -0,0 +1,73 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Mapper; + +namespace DynamORM.Tests.Helpers +{ + /// Users table representation. + [Table(Name = "users", Override = true)] + public class Users + { + /// Gets or sets id column value. + [Column("id", true)] + public long Id { get; set; } + + /// Gets or sets code column value. + [Column("code")] + public string Code { get; set; } + + /// Gets or sets login column value. + [Column("login")] + public string Login { get; set; } + + /// Gets or sets first columnvalue. + [Column("first")] + public string First { get; set; } + + /// Gets or sets last column value. + [Column("last")] + public string Last { get; set; } + + /// Gets or sets password column value. + [Column("password")] + public string Password { get; set; } + + /// Gets or sets email column value. + [Column("email")] + public string Email { get; set; } + + /// Gets or sets quote column value. + [Column("quote")] + public string Quote { get; set; } + + /// Gets or sets value of aggregate fields. + [Ignore] + public object AggregateField { get; set; } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/UsersBareBoneClass.cs b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs new file mode 100644 index 0000000..7879bfb --- /dev/null +++ b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs @@ -0,0 +1,67 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Diagnostics.CodeAnalysis; +using DynamORM.Mapper; + +namespace DynamORM.Tests.Helpers +{ + /// Users table representation. + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Bare bone table mapping.")] + public class users + { + /// Gets or sets id column value. + [Column("id", true)] + public long id { get; set; } + + /// Gets or sets code column value. + public string code { get; set; } + + /// Gets or sets login column value. + public string login { get; set; } + + /// Gets or sets first columnvalue. + public string first { get; set; } + + /// Gets or sets last column value. + public string last { get; set; } + + /// Gets or sets password column value. + public string password { get; set; } + + /// Gets or sets email column value. + public string email { get; set; } + + /// Gets or sets quote column value. + public string quote { get; set; } + + /// Gets or sets value of aggregate fields. + [Ignore] + public object aggregatefield { get; set; } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Modify/DynamicModificationTests.cs b/DynamORM.Tests/Modify/DynamicModificationTests.cs new file mode 100644 index 0000000..5b9a9af --- /dev/null +++ b/DynamORM.Tests/Modify/DynamicModificationTests.cs @@ -0,0 +1,390 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. + [TestFixture] + public class DynamicModificationTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table("users"); + } + + #region Insert + + /// Test row insertion by dynamic arguments. + [Test] + public void TestInsertByArguments() + { + Assert.AreEqual(1, GetTestTable().Insert(code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + + // Verify + var o = GetTestTable().Single(code: 201); + Assert.Less(200, o.id); + Assert.AreEqual("201", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row insertion by dynamic object. + [Test] + public void TestInsertByDynamicObjects() + { + Assert.AreEqual(1, GetTestTable().Insert(values: new { code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + var o = GetTestTable().Single(code: 202); + Assert.Less(200, o.id); + Assert.AreEqual("202", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row insertion by mapped object. + [Test] + public void TestInsertByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Insert(values: new Users + { + Id = u.Max(columns: "id") + 1, + Code = "203", + First = "Juri", + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: 203); + Assert.Less(200, o.id); + Assert.AreEqual("203", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row insertion by basic object. + [Test] + public void TestInsertByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Insert(values: new users + { + id = u.Max(columns: "id") + 1, + code = "204", + first = "Juri", + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: 204); + Assert.Less(200, o.id); + Assert.AreEqual("204", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + #endregion Insert + + #region Update + + /// Test row updating by dynamic arguments. + [Test] + public void TestUpdateByArguments() + { + Assert.AreEqual(1, GetTestTable().Update(id: 1, code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + + // Verify + var o = GetTestTable().Single(code: 201); + Assert.AreEqual(1, o.id); + Assert.AreEqual("201", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by dynamic objects. + [Test] + public void TestUpdateByDynamicObject() + { + Assert.AreEqual(1, GetTestTable().Update(update: new { id = 2, code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + var o = GetTestTable().Single(code: 202); + Assert.AreEqual(2, o.id); + Assert.AreEqual("202", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by mapped object. + [Test] + public void TestUpdateByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(update: new Users + { + Id = 3, + Code = "203", + First = "Juri", + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: 203); + Assert.AreEqual(3, o.id); + Assert.AreEqual("203", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by basic object. + [Test] + public void TestUpdateByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(update: new users + { + id = 4, + code = "204", + first = "Juri", + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: 204); + Assert.AreEqual(4, o.id); + Assert.AreEqual("204", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by dynamic objects. + [Test] + public void TestUpdateByDynamicObjects() + { + Assert.AreEqual(1, GetTestTable().Update(values: new { code = 205, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 })); + + // Verify + var o = GetTestTable().Single(code: 205); + Assert.AreEqual(5, o.id); + Assert.AreEqual("205", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by mapped objects. + [Test] + public void TestUpdateByMappedObjects() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(values: new Users + { + Id = 6, + Code = "206", + First = "Juri", + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + }, id: 6)); + + // Verify + var o = u.Single(code: 206); + Assert.AreEqual(6, o.id); + Assert.AreEqual("206", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + /// Test row updating by basic objects. + [Test] + public void TestUpdateByBasicObjects() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(values: new users + { + id = 7, + code = "207", + first = "Juri", + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + }, id: 7)); + + // Verify + var o = u.Single(code: 207); + Assert.AreEqual(7, o.id); + Assert.AreEqual("207", o.code); + Assert.AreEqual("Juri", o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.AreEqual(null, o.password); + } + + #endregion Update + + #region Delete + + /// Test row deleting by dynamic arguments. + [Test] + public void TestDeleteByArguments() + { + Assert.AreEqual(1, GetTestTable().Delete(code: 10)); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(code: 10)); + } + + /// Test row deleting by dynamic objects (all except ID should be ignored). + [Test] + public void TestDeleteyDynamicObject() + { + Assert.AreEqual(1, GetTestTable().Delete(delete: new { id = 11, code = 11, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 11)); + } + + /// Test row deleting by mapped object. + [Test] + public void TestDeleteByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Delete(delete: new Users + { + Id = 12, + Code = "12", + First = "Juri", + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 12)); + } + + /// Test row deleting by basic object. + [Test] + public void TestDeleteByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Delete(delete: new users + { + id = 13, + code = "13", + first = "Juri", + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 13)); + } + + /// Test row deleting by dynamic objects (all except ID should be ignored). + [Test] + public void TestDeleteyDynamicObjectWhere() + { + Assert.AreEqual(1, GetTestTable().Delete(where: new { id = 14, code = 14 })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 14)); + } + + #endregion Delete + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs b/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs new file mode 100644 index 0000000..05a13df --- /dev/null +++ b/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs @@ -0,0 +1,55 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicNoSchemaModificationTests : DynamicModificationTests + { + /// Setup test parameters. + [TestFixtureSetUp] + public override void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table("users", new string[] { "id" }); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs b/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs new file mode 100644 index 0000000..1794586 --- /dev/null +++ b/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs @@ -0,0 +1,45 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicTypeSchemaModificationTests : DynamicModificationTests + { + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table(); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Properties/AssemblyInfo.cs b/DynamORM.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c6b3373 --- /dev/null +++ b/DynamORM.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,65 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * See: http://opensource.org/licenses/bsd-license.php +*/ + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DynamORM.Tests")] +[assembly: AssemblyDescription("Dynamic Object-Relational Mapping tests library.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RUSSEK Software")] +[assembly: AssemblyProduct("DynamORM")] +[assembly: AssemblyCopyright("Copyright © RUSSEK Software 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1d8b751b-b3ec-481d-9f7f-14d6e6eb0fde")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.1")] +[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file diff --git a/DynamORM.Tests/Properties/Resources.Designer.cs b/DynamORM.Tests/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6c5a129 --- /dev/null +++ b/DynamORM.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.269 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DynamORM.Tests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DynamORM.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); + ///INSERT INTO users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); + ///INSERT INTO users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); + ///INSERT INTO users (code,first,last,email,quote) VALUES ('3',' [rest of string was truncated]";. + /// + internal static string UsersTable { + get { + return ResourceManager.GetString("UsersTable", resourceCulture); + } + } + } +} diff --git a/DynamORM.Tests/Properties/Resources.resx b/DynamORM.Tests/Properties/Resources.resx new file mode 100644 index 0000000..f0356bb --- /dev/null +++ b/DynamORM.Tests/Properties/Resources.resx @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); +INSERT INTO users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('3','Dai','Marks','dictum.augue@venenatis.ca','nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent'); +INSERT INTO users (code,first,last,email,quote) VALUES ('4','Forrest','Hendricks','justo.sit.amet@odioa.edu','auctor. Mauris vel turpis. Aliquam adipiscing lobortis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('5','Blossom','Dunlap','libero.mauris@Phasellusfermentumconvallis.ca','luctus sit amet, faucibus ut, nulla. Cras eu tellus eu'); +INSERT INTO users (code,first,last,email,quote) VALUES ('6','George','Rios','vitae@sodales.ca','elit. Aliquam auctor, velit eget laoreet'); +INSERT INTO users (code,first,last,email,quote) VALUES ('7','Ivory','Henderson','elit.Aliquam.auctor@Nullamvelitdui.ca','Fusce diam nunc, ullamcorper'); +INSERT INTO users (code,first,last,email,quote) VALUES ('8','Inez','Goodwin','consectetuer.mauris@nibhPhasellus.edu','eu, placerat eget, venenatis a, magna.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('9','Sigourney','Gonzales','lectus.pede.ultrices@sagittislobortismauris.org','egestas hendrerit'); +INSERT INTO users (code,first,last,email,quote) VALUES ('10','Fulton','Terrell','penatibus@euaugue.com','Nulla interdum. Curabitur dictum.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('11','Logan','Freeman','malesuada.malesuada@nullaIntegervulputate.edu','dui, semper et, lacinia'); +INSERT INTO users (code,first,last,email,quote) VALUES ('12','Anne','Irwin','lorem.ut.aliquam@ligula.org','erat, in consectetuer ipsum nunc id enim.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('13','Alexandra','Church','sit@sempererat.org','lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed'); +INSERT INTO users (code,first,last,email,quote) VALUES ('14','Adena','Branch','sit.amet@accumsanlaoreetipsum.org','natoque penatibus et'); +INSERT INTO users (code,first,last,email,quote) VALUES ('15','Lionel','Hoover','ac@Donectempor.ca','at pede. Cras vulputate'); +INSERT INTO users (code,first,last,email,quote) VALUES ('16','Aimee','Strickland','ornare.lectus@tinciduntduiaugue.ca','vitae odio sagittis semper. Nam'); +INSERT INTO users (code,first,last,email,quote) VALUES ('17','Selma','Williamson','metus.In.nec@quamquisdiam.org','id nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('18','Lara','Trujillo','lacus@convallisest.edu','Integer sem elit, pharetra ut,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('19','Ori','Ellis','egestas@at.ca','odio. Nam interdum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('20','Macey','Carey','sed.consequat@ametorciUt.org','magna'); +INSERT INTO users (code,first,last,email,quote) VALUES ('21','Quynn','Randall','Cras.dictum@malesuada.org','egestas. Fusce aliquet magna a neque.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('22','Alec','Robles','Fusce.feugiat@mollisdui.com','a'); +INSERT INTO users (code,first,last,email,quote) VALUES ('23','Jakeem','Bell','ante@laoreetlectusquis.edu','eget massa. Suspendisse eleifend. Cras'); +INSERT INTO users (code,first,last,email,quote) VALUES ('24','Katelyn','Cannon','sit.amet@PhasellusornareFusce.org','nisi dictum augue malesuada malesuada.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('25','Christian','Alford','per@vulputate.ca','turpis.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('26','Leila','Forbes','nec.ante@idblanditat.ca','ac ipsum. Phasellus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('27','Hadley','Gillespie','Lorem.ipsum@Nullamvitaediam.com','vestibulum lorem, sit amet ultricies sem magna nec quam.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('28','Julian','Keith','facilisis@loremvitaeodio.edu','nulla at sem molestie sodales. Mauris blandit enim consequat purus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('29','Allen','Ramos','neque@lobortis.ca','amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat'); +INSERT INTO users (code,first,last,email,quote) VALUES ('30','Hermione','Walsh','dictum.magna@tincidunt.edu','scelerisque'); +INSERT INTO users (code,first,last,email,quote) VALUES ('31','Xena','Graves','eu.dui@tinciduntaliquamarcu.ca','nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('32','Tanisha','Blackburn','aliquam.eu.accumsan@dui.edu','fringilla mi lacinia mattis. Integer eu'); +INSERT INTO users (code,first,last,email,quote) VALUES ('33','Norman','Hobbs','eu.euismod.ac@tinciduntDonecvitae.com','pulvinar arcu et pede. Nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('34','Guy','Molina','non@rutrumurna.org','Vivamus euismod urna.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('35','Rama','Albert','nunc@laciniavitaesodales.com','eu augue porttitor interdum. Sed auctor odio a purus. Duis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('36','Owen','Combs','Nullam.enim.Sed@magnaPhasellusdolor.com','mollis vitae, posuere at, velit. Cras lorem lorem, luctus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('37','Ashely','Graham','eget.odio@enimsitamet.com','commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('38','Paul','Levy','nec@leo.com','augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan'); +INSERT INTO users (code,first,last,email,quote) VALUES ('39','Octavia','Calderon','eu@maurissitamet.edu','est, mollis non, cursus non,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('40','Lenore','Pugh','eu.metus.In@Cumsociisnatoque.com','scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('41','Regan','Clemons','eu.neque.pellentesque@Integerinmagna.ca','Curae;'); +INSERT INTO users (code,first,last,email,quote) VALUES ('42','Zachary','Haynes','eu.erat@ipsum.edu','quis lectus. Nullam'); +INSERT INTO users (code,first,last,email,quote) VALUES ('43','Dane','Hyde','rhoncus@auctor.com','magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim'); +INSERT INTO users (code,first,last,email,quote) VALUES ('44','Mara','Hansen','Nunc.commodo@ultricies.org','magna. Ut tincidunt orci quis lectus. Nullam suscipit, est'); +INSERT INTO users (code,first,last,email,quote) VALUES ('45','Zelda','Parrish','turpis.In@rutrummagnaCras.com','Suspendisse aliquet, sem ut cursus luctus, ipsum leo'); +INSERT INTO users (code,first,last,email,quote) VALUES ('46','Amaya','Richmond','lacinia.at@in.org','ac, fermentum vel, mauris.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('47','Hedda','Murray','ac.fermentum.vel@ipsum.com','tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a'); +INSERT INTO users (code,first,last,email,quote) VALUES ('48','Brendan','Macdonald','dignissim@leoMorbi.com','vestibulum lorem, sit'); +INSERT INTO users (code,first,last,email,quote) VALUES ('49','Gray','Mccall','risus.Donec@atsem.ca','libero est, congue'); +INSERT INTO users (code,first,last,email,quote) VALUES ('50','Shoshana','Tran','nunc.In@aliquet.org','magna sed dui.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('51','Sylvester','Parks','Mauris.molestie@ipsumdolorsit.org','nec, eleifend non, dapibus rutrum, justo.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('52','Kennan','Stokes','nonummy.ut.molestie@varius.org','Ut nec urna et arcu imperdiet ullamcorper. Duis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('53','Quin','Park','porttitor@vulputateeu.com','scelerisque, lorem ipsum sodales'); +INSERT INTO users (code,first,last,email,quote) VALUES ('54','Madison','Bailey','nec.orci.Donec@vestibulumnequesed.org','augue. Sed'); +INSERT INTO users (code,first,last,email,quote) VALUES ('55','Kenyon','Reilly','ultrices.iaculis.odio@sit.ca','enim. Curabitur massa. Vestibulum accumsan neque'); +INSERT INTO users (code,first,last,email,quote) VALUES ('56','Drew','Brooks','molestie.orci@aliquetdiamSed.org','at risus. Nunc ac sem ut dolor dapibus gravida.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('57','Omar','Dunn','vel@Donecsollicitudinadipiscing.ca','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit'); +INSERT INTO users (code,first,last,email,quote) VALUES ('58','Glenna','Lambert','Proin.non@Phasellus.com','neque. In'); +INSERT INTO users (code,first,last,email,quote) VALUES ('59','Aspen','Bailey','in.dolor.Fusce@ipsum.org','a, arcu. Sed et libero. Proin'); +INSERT INTO users (code,first,last,email,quote) VALUES ('60','Adele','Carlson','velit.justo.nec@vehicularisusNulla.edu','risus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('61','Kerry','Zimmerman','luctus.aliquet.odio@urnajusto.edu','purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('62','Hedda','Guthrie','vulputate.risus@Phaselluselit.ca','ac mattis velit justo nec'); +INSERT INTO users (code,first,last,email,quote) VALUES ('63','Wyoming','Blackburn','nec.ante@lorem.org','ultricies'); +INSERT INTO users (code,first,last,email,quote) VALUES ('64','Palmer','Dennis','venenatis.lacus@Donecnonjusto.org','a, arcu. Sed et libero. Proin'); +INSERT INTO users (code,first,last,email,quote) VALUES ('65','Wesley','Reeves','massa.Suspendisse.eleifend@nec.edu','consequat'); +INSERT INTO users (code,first,last,email,quote) VALUES ('66','Mallory','Todd','Duis@Crasloremlorem.edu','nascetur ridiculus mus. Proin vel arcu eu odio'); +INSERT INTO users (code,first,last,email,quote) VALUES ('67','Perry','Kirk','tincidunt.adipiscing@mauris.ca','ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('68','Justina','Horne','enim.nec.tempus@magnanec.com','ipsum ac mi eleifend egestas. Sed pharetra, felis eget'); +INSERT INTO users (code,first,last,email,quote) VALUES ('69','Noel','Santana','Duis.elementum.dui@Nullamvitae.org','Phasellus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('70','Sylvester','Bridges','Sed.neque@ornarelectusante.org','Maecenas libero'); +INSERT INTO users (code,first,last,email,quote) VALUES ('71','Cailin','Baldwin','eu@nibhenim.com','consectetuer'); +INSERT INTO users (code,first,last,email,quote) VALUES ('72','Beatrice','Henson','risus.Nunc.ac@Etiam.ca','nec, imperdiet nec, leo. Morbi neque'); +INSERT INTO users (code,first,last,email,quote) VALUES ('73','Kellie','Curry','quis.turpis.vitae@Quisque.org','ipsum. Suspendisse non leo.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('74','Justine','Stewart','Curabitur@dui.org','natoque penatibus et magnis dis parturient'); +INSERT INTO users (code,first,last,email,quote) VALUES ('75','Dean','Waters','quis.turpis@morbi.ca','Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue'); +INSERT INTO users (code,first,last,email,quote) VALUES ('76','Helen','Porter','molestie.dapibus.ligula@ipsum.com','enim non nisi. Aenean eget'); +INSERT INTO users (code,first,last,email,quote) VALUES ('77','Janna','Acosta','lectus.a@tempusrisus.com','lectus justo'); +INSERT INTO users (code,first,last,email,quote) VALUES ('78','Libby','Vaughn','nisl.sem.consequat@convallis.ca','purus mauris a nunc.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('79','Winifred','Cooke','aliquet.sem.ut@etrutrum.ca','metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec'); +INSERT INTO users (code,first,last,email,quote) VALUES ('80','Taylor','Glass','sit@maurissitamet.com','orci, consectetuer euismod'); +INSERT INTO users (code,first,last,email,quote) VALUES ('81','Melyssa','Palmer','pharetra.sed.hendrerit@fermentum.ca','neque tellus, imperdiet non, vestibulum nec,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('82','Kuame','Holman','Vivamus@Donecnibh.edu','cursus non, egestas a, dui. Cras'); +INSERT INTO users (code,first,last,email,quote) VALUES ('83','Martin','Hughes','magna@nonbibendum.org','et ultrices posuere cubilia'); +INSERT INTO users (code,first,last,email,quote) VALUES ('84','Roanna','Potter','amet.dapibus@aenim.com','est tempor bibendum. Donec'); +INSERT INTO users (code,first,last,email,quote) VALUES ('85','Cleo','Carson','Curabitur.dictum.Phasellus@liberoIntegerin.com','mauris'); +INSERT INTO users (code,first,last,email,quote) VALUES ('86','Adele','Daugherty','vulputate.ullamcorper@elitfermentumrisus.edu','consequat purus. Maecenas'); +INSERT INTO users (code,first,last,email,quote) VALUES ('87','Macon','Todd','et.malesuada@Crasloremlorem.com','montes, nascetur ridiculus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('88','Ira','Merritt','risus@eunullaat.ca','metus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('89','Dustin','Landry','nec.orci.Donec@vulputateeu.com','Nunc pulvinar arcu et pede. Nunc sed'); +INSERT INTO users (code,first,last,email,quote) VALUES ('90','Debra','Shepherd','dolor.elit@ornareFusce.ca','Suspendisse non leo. Vivamus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('91','Channing','Mcknight','tellus@laoreet.ca','non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('92','Abraham','Rodgers','tincidunt.nibh@ipsum.ca','Cum sociis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('93','Abel','Mcintyre','et@turpisvitaepurus.com','at pretium aliquet,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('94','Lysandra','Cotton','malesuada.malesuada.Integer@gravidamolestie.org','commodo hendrerit. Donec'); +INSERT INTO users (code,first,last,email,quote) VALUES ('95','Kane','Bennett','ante.Vivamus@Donec.ca','consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales'); +INSERT INTO users (code,first,last,email,quote) VALUES ('96','Timothy','Rivas','hendrerit.consectetuer@nonarcuVivamus.ca','Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat'); +INSERT INTO users (code,first,last,email,quote) VALUES ('97','Arden','Cote','magnis@sedestNunc.org','purus mauris a nunc.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('98','Brynn','Britt','ac@nibhQuisque.edu','Duis gravida. Praesent eu nulla at sem molestie'); +INSERT INTO users (code,first,last,email,quote) VALUES ('99','Jerome','Kirkland','ac@nibhPhasellus.com','neque'); +INSERT INTO users (code,first,last,email,quote) VALUES ('100','Hoyt','Tran','ullamcorper.viverra@parturientmontesnascetur.org','ac tellus. Suspendisse sed dolor. Fusce mi'); +INSERT INTO users (code,first,last,email,quote) VALUES ('101','Abraham','Downs','velit.eu.sem@neceuismod.ca','suscipit,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('102','Vivien','Fletcher','Nunc.ut@quamPellentesquehabitant.edu','lobortis. Class aptent taciti sociosqu ad'); +INSERT INTO users (code,first,last,email,quote) VALUES ('103','Azalia','Turner','scelerisque@at.edu','odio sagittis semper. Nam tempor diam dictum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('104','Tate','Ellis','velit.Sed.malesuada@Cras.com','sed'); +INSERT INTO users (code,first,last,email,quote) VALUES ('105','Dennis','Walls','Nullam.scelerisque@amifringilla.edu','arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor'); +INSERT INTO users (code,first,last,email,quote) VALUES ('106','Amela','Collins','Quisque@magna.org','Proin'); +INSERT INTO users (code,first,last,email,quote) VALUES ('107','Olivia','Dejesus','scelerisque@vitae.org','Duis cursus, diam at pretium aliquet,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('108','Mechelle','Russo','Fusce.aliquet.magna@Praesent.org','penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin'); +INSERT INTO users (code,first,last,email,quote) VALUES ('109','Hall','Burke','Vivamus.nibh@pellentesque.edu','et libero. Proin mi. Aliquam'); +INSERT INTO users (code,first,last,email,quote) VALUES ('110','Chanda','Ayers','magna.Nam.ligula@NullafacilisiSed.org','dui, semper et, lacinia vitae, sodales at,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('111','Abdul','Dominguez','pretium.aliquet.metus@vellectusCum.com','vitae erat vel pede blandit congue. In scelerisque scelerisque dui.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('112','Wynter','Lynn','tellus.Aenean@ligulatortordictum.ca','adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('113','Molly','Willis','montes.nascetur@sed.org','sem. Nulla interdum. Curabitur dictum. Phasellus in felis.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('114','Shafira','Harper','Phasellus.in.felis@ategestas.edu','Vivamus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('115','Otto','Gentry','in.molestie.tortor@Fusce.edu','est. Mauris eu turpis. Nulla aliquet. Proin velit.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('116','Todd','Riddle','et@nislsem.com','Nulla'); +INSERT INTO users (code,first,last,email,quote) VALUES ('117','Mufutau','Pollard','sit@ridiculusmus.org','accumsan neque et nunc. Quisque ornare tortor at'); +INSERT INTO users (code,first,last,email,quote) VALUES ('118','Noah','Sears','tristique.neque.venenatis@luctuslobortis.org','dictum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('119','Amanda','Clarke','in.consequat@non.org','nisi dictum augue malesuada malesuada. Integer id magna et ipsum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('120','Vladimir','Colon','velit.dui@scelerisquenequeNullam.com','pharetra sed, hendrerit a, arcu. Sed et libero.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('121','Aaron','Hernandez','Quisque.ornare.tortor@magna.ca','sit amet ante. Vivamus non lorem vitae odio'); +INSERT INTO users (code,first,last,email,quote) VALUES ('122','Bert','Gonzales','fermentum@Nullamvelit.ca','quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('123','Bevis','Leblanc','viverra.Donec@dictumaugue.com','neque sed dictum eleifend, nunc risus varius orci, in'); +INSERT INTO users (code,first,last,email,quote) VALUES ('124','Noble','Fisher','Cum@eu.com','Nunc quis arcu'); +INSERT INTO users (code,first,last,email,quote) VALUES ('125','Xaviera','Barton','non.arcu@sagittis.com','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('126','Logan','Roy','Nulla@hendreritconsectetuercursus.com','sed, facilisis vitae, orci. Phasellus dapibus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('127','Wilma','Sweet','Nullam.nisl.Maecenas@gravidasagittis.org','ut, nulla. Cras'); +INSERT INTO users (code,first,last,email,quote) VALUES ('128','Candace','Olsen','sit.amet@lobortisnisi.com','habitant morbi tristique senectus et netus et malesuada'); +INSERT INTO users (code,first,last,email,quote) VALUES ('129','Claire','Alvarado','risus.varius@interdumSed.com','a felis ullamcorper viverra. Maecenas iaculis aliquet'); +INSERT INTO users (code,first,last,email,quote) VALUES ('130','Aurelia','Bean','diam.dictum.sapien@eu.edu','libero. Integer in magna. Phasellus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('131','Carly','Wilcox','massa.non.ante@Aliquam.com','vehicula risus. Nulla eget metus eu erat semper rutrum. Fusce'); +INSERT INTO users (code,first,last,email,quote) VALUES ('132','Xanthus','Graves','vulputate.velit.eu@vitaerisus.com','Cras sed leo. Cras vehicula aliquet libero. Integer in magna.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('133','Uta','Justice','adipiscing@consequatdolor.edu','Nam tempor diam dictum sapien. Aenean massa.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('134','Alika','Parker','faucibus.ut@Fuscemilorem.edu','vel, venenatis vel, faucibus id,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('135','Kasimir','Pugh','sed.turpis@FuscefeugiatLorem.com','justo. Praesent luctus. Curabitur egestas nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('136','Brock','Acevedo','dapibus.rutrum@convallisconvallisdolor.org','eu enim. Etiam'); +INSERT INTO users (code,first,last,email,quote) VALUES ('137','Orla','Hogan','in@Aliquamauctor.com','ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque'); +INSERT INTO users (code,first,last,email,quote) VALUES ('138','Tatyana','Bean','molestie.arcu@sempercursus.edu','pellentesque, tellus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('139','Cadman','Humphrey','auctor@Aeneangravida.edu','eget, dictum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('140','Delilah','Quinn','Pellentesque.habitant@liberoMorbi.edu','lobortis quam a felis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('141','Briar','Prince','euismod@Aeneangravida.com','at, libero.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('142','Hedda','Garrett','eleifend@sitamet.org','ac turpis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('143','Sage','Hardy','semper@acfeugiat.org','Nunc laoreet lectus quis massa.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('144','Iris','Meyers','Duis.a.mi@Donecnibhenim.edu','vitae aliquam eros turpis non enim. Mauris quis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('145','Hayes','Bates','molestie@magnaSuspendisse.com','fringilla. Donec feugiat metus sit amet ante.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('146','Rana','Cain','malesuada@loremsitamet.edu','mi'); +INSERT INTO users (code,first,last,email,quote) VALUES ('147','acqueline','Mays','est.Nunc.laoreet@est.edu','odio. Phasellus at augue id ante dictum cursus. Nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('148','Nichole','Suarez','cursus.purus.Nullam@ac.edu','Sed malesuada augue ut lacus. Nulla'); +INSERT INTO users (code,first,last,email,quote) VALUES ('149','Sasha','Sparks','fermentum@nonenim.edu','Sed dictum. Proin eget odio.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('150','Timon','Lowery','suscipit@Donecat.edu','pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('151','Ivor','Charles','augue.eu@odiovelest.org','quis urna. Nunc quis arcu vel quam dignissim'); +INSERT INTO users (code,first,last,email,quote) VALUES ('152','Joelle','Trevino','eget@diamdictumsapien.ca','tortor, dictum eu, placerat eget, venenatis a,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('153','Aristotle','Wall','at@Pellentesqueutipsum.com','quis,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('154','Amena','Boyd','elit.Etiam@vehiculaet.com','molestie dapibus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('155','Rashad','Osborn','ac@aliquam.ca','sed'); +INSERT INTO users (code,first,last,email,quote) VALUES ('156','Theodore','Williamson','dignissim.magna@volutpat.ca','Fusce fermentum fermentum'); +INSERT INTO users (code,first,last,email,quote) VALUES ('157','Rajah','Logan','enim@Aliquamerat.com','nunc. Quisque ornare tortor'); +INSERT INTO users (code,first,last,email,quote) VALUES ('158','Zane','Perez','aliquet@aliquetPhasellus.com','pede.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('159','Jena','Rios','urna@velitegetlaoreet.org','eu erat semper rutrum.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('160','Amber','Gallagher','conubia@elit.org','Maecenas ornare egestas'); +INSERT INTO users (code,first,last,email,quote) VALUES ('161','Veda','Pittman','habitant.morbi.tristique@aliquamenimnec.ca','risus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('162','Nathan','Lowe','dui.Cum.sociis@Sed.org','sed, est. Nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('163','Matthew','Townsend','commodo.auctor.velit@egestasadui.ca','ligula. Aenean euismod'); +INSERT INTO users (code,first,last,email,quote) VALUES ('164','Oscar','Richards','vulputate.ullamcorper@eueleifendnec.ca','laoreet ipsum. Curabitur consequat, lectus sit'); +INSERT INTO users (code,first,last,email,quote) VALUES ('165','Gareth','Jackson','nec.diam.Duis@nisl.com','iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('166','Alice','Hyde','Sed.auctor.odio@tortor.edu','id, libero. Donec consectetuer mauris id sapien. Cras dolor'); +INSERT INTO users (code,first,last,email,quote) VALUES ('167','Giacomo','Ramos','et.ipsum.cursus@massanon.edu','auctor non, feugiat nec, diam. Duis'); +INSERT INTO users (code,first,last,email,quote) VALUES ('168','Ciara','Jacobson','Donec.egestas.Aliquam@Aeneangravidanunc.edu','molestie tellus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('169','Logan','Hendricks','luctus@vulputatedui.ca','in, cursus et, eros. Proin ultrices. Duis volutpat nunc'); +INSERT INTO users (code,first,last,email,quote) VALUES ('170','MacKenzie','Campos','luctus.ut.pellentesque@pellentesquea.edu','erat,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('171','Nina','Best','purus.sapien@aliquet.ca','dolor sit amet, consectetuer'); +INSERT INTO users (code,first,last,email,quote) VALUES ('172','Chester','Howe','Cum.sociis.natoque@Duis.ca','dui, nec tempus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('173','Nora','Callahan','in.hendrerit@ametconsectetueradipiscing.com','tempus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('174','Molly','Bray','consectetuer@velitSedmalesuada.edu','lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit'); +INSERT INTO users (code,first,last,email,quote) VALUES ('175','Ariel','Osborn','et@Aeneanegestashendrerit.ca','enim. Sed nulla ante, iaculis nec,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('176','Arsenio','Leblanc','Pellentesque.ut.ipsum@nibh.com','elit pede, malesuada vel, venenatis vel, faucibus id, libero.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('177','Deborah','Bowman','enim.Etiam@primisinfaucibus.ca','eu nibh vulputate mauris'); +INSERT INTO users (code,first,last,email,quote) VALUES ('178','Delilah','Horton','tincidunt.nunc@arcuet.ca','nec ante. Maecenas mi'); +INSERT INTO users (code,first,last,email,quote) VALUES ('179','Isaiah','Buckley','Fusce.feugiat@massa.org','ut'); +INSERT INTO users (code,first,last,email,quote) VALUES ('180','Logan','Jacobs','Phasellus@utsemNulla.ca','ullamcorper eu, euismod'); +INSERT INTO users (code,first,last,email,quote) VALUES ('181','Lesley','Brown','fringilla@erateget.com','nostra, per inceptos'); +INSERT INTO users (code,first,last,email,quote) VALUES ('182','Kay','Dodson','a.purus@velpedeblandit.ca','tortor at risus. Nunc ac sem ut'); +INSERT INTO users (code,first,last,email,quote) VALUES ('183','Yeo','Hayes','vitae.posuere.at@scelerisque.com','a, dui. Cras'); +INSERT INTO users (code,first,last,email,quote) VALUES ('184','Keegan','Brock','molestie.tellus@convallisestvitae.ca','mi eleifend egestas. Sed pharetra, felis eget varius ultrices,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('185','Kim','Foley','at@acrisusMorbi.com','scelerisque sed, sapien.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('186','Celeste','Delacruz','ipsum.non.arcu@vulputate.edu','Donec fringilla. Donec feugiat metus'); +INSERT INTO users (code,first,last,email,quote) VALUES ('187','Hilda','Rowe','gravida.sit@nisi.ca','eu nulla at sem molestie sodales. Mauris blandit enim'); +INSERT INTO users (code,first,last,email,quote) VALUES ('188','Fuller','Mclaughlin','purus.gravida.sagittis@arcu.com','erat. Sed nunc est, mollis non, cursus non, egestas a,'); +INSERT INTO users (code,first,last,email,quote) VALUES ('189','Madeline','Henderson','lorem.fringilla@cursusdiam.com','Sed pharetra, felis eget varius ultrices, mauris'); +INSERT INTO users (code,first,last,email,quote) VALUES ('190','Josephine','Osborn','metus@tincidunt.ca','a sollicitudin orci sem eget massa. Suspendisse'); +INSERT INTO users (code,first,last,email,quote) VALUES ('191','Ivana','Jimenez','justo@egestasSedpharetra.org','Duis risus odio, auctor vitae, aliquet nec, imperdiet nec, leo.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('192','Stephanie','Dickerson','aliquet.nec.imperdiet@Aliquamrutrumlorem.com','nec ante. Maecenas mi felis, adipiscing'); +INSERT INTO users (code,first,last,email,quote) VALUES ('193','Yardley','Trevino','lacinia.mattis@porttitoreros.edu','lacus.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('194','Carol','Acosta','Donec@aliquetmolestie.ca','arcu'); +INSERT INTO users (code,first,last,email,quote) VALUES ('195','Lysandra','Mosley','imperdiet@Suspendissesed.org','viverra. Maecenas iaculis aliquet diam.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('196','Tamara','Solis','eleifend.egestas.Sed@duiinsodales.com','sit amet lorem semper auctor. Mauris vel'); +INSERT INTO users (code,first,last,email,quote) VALUES ('197','Palmer','Perez','nibh@nonduinec.edu','lacus vestibulum lorem, sit amet'); +INSERT INTO users (code,first,last,email,quote) VALUES ('198','Maia','Donaldson','gravida.Aliquam.tincidunt@volutpatNulladignissim.edu','sit amet luctus vulputate, nisi'); +INSERT INTO users (code,first,last,email,quote) VALUES ('199','Murphy','Wright','et.pede@aptenttacitisociosqu.ca','non arcu. Vivamus sit amet risus. Donec egestas.'); +INSERT INTO users (code,first,last,email,quote) VALUES ('200','Omar','Campos','nunc.ac.mattis@luctussitamet.edu','parturient'); +UPDATE users SET login = lower(first || '.' || last); + + \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicAccessTests.cs b/DynamORM.Tests/Select/DynamicAccessTests.cs new file mode 100644 index 0000000..3f1cecd --- /dev/null +++ b/DynamORM.Tests/Select/DynamicAccessTests.cs @@ -0,0 +1,322 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. + [TestFixture] + public class DynamicAccessTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table("users"); + } + + #region Select + + /// Test unknown op. + [Test] + public void TestUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(with: "cheese")); + } + + /// Test dynamic Count method. + [Test] + public void TestCount() + { + Assert.AreEqual(200, GetTestTable().Count(columns: "id")); + } + + /// Test count with in steatement. + [Test] + public void TestSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in steatement. + [Test] + public void TestSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test dynamic First method. + [Test] + public void TestFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").id); + } + + /// Test dynamic Last method. + [Test] + public void TestLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); + } + + /// Test dynamic Count method. + [Test] + public void TestCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); + } + + /// Test dynamic Min method. + [Test] + public void TestMin() + { + Assert.AreEqual(1, GetTestTable().Min(columns: "id")); + } + + /// Test dynamic Min method. + [Test] + public void TestMax() + { + Assert.AreEqual(200, GetTestTable().Max(columns: "id")); + } + + /// Test dynamic Min method. + [Test] + public void TesttAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); + } + + /// Test dynamic Sum method. + [Test] + public void TestSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); + } + + /// Test dynamic Scalar method for invalid operation exception. + [Test] + public void TestScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(id: 19)); + } + + /// Test dynamic Scalar method. + [Test] + public void TestScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); + } + + /// Test dynamic Scalar method with SQLite specific aggregate. + [Test] + public void TestScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public void TestScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test something fancy... like: select "first", count("first") occurs from "users" group by "first" order by 2 desc;. + [Test] + public void TestFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:occurs:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().occurs); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().occurs); + Assert.AreEqual(1, v.Take(11).Last().occurs); + Assert.AreEqual(1, v.Last().occurs); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. + [Test] + public void TestAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. + [Test] + public void TestAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); + } + + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. + public void TestFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test dynamic Single multi. + [Test] + public void TestSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + #endregion Select + + #region Where + + /// Test dynamic where expression equal. + [Test] + public void TestWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test dynamic where expression not equal. + [Test] + public void TestWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); + } + + /// Test dynamic where expression like. + [Test] + public void TestWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test dynamic where expression greater. + [Test] + public void TestWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); + } + + /// Test dynamic where expression greater or equal. + [Test] + public void TestWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test dynamic where expression less. + [Test] + public void TestWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); + } + + /// Test dynamic where expression less or equal. + [Test] + public void TestWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test dynamic where expression between. + [Test] + public void TestWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test dynamic where expression in params. + [Test] + public void TestWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test dynamic where expression in array. + [Test] + public void TestWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + #endregion Where + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs b/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs new file mode 100644 index 0000000..56d9090 --- /dev/null +++ b/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs @@ -0,0 +1,55 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicNoSchemaAccessTests : DynamicAccessTests + { + /// Setup test parameters. + [TestFixtureSetUp] + public override void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table("users", new string[] { "id" }); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs b/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs new file mode 100644 index 0000000..73a2c47 --- /dev/null +++ b/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs @@ -0,0 +1,45 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicTypeSchemaAccessTests : DynamicNoSchemaAccessTests + { + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table(); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/RenamedTypedAccessTests.cs b/DynamORM.Tests/Select/RenamedTypedAccessTests.cs new file mode 100644 index 0000000..78eedb4 --- /dev/null +++ b/DynamORM.Tests/Select/RenamedTypedAccessTests.cs @@ -0,0 +1,149 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using System.Linq; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test typed ORM. + public class RenamedTypedAccessTests : TypedAccessTests + { + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. + [Test] + public override void TestTypedFancyAggregateQuery() + { + var v = (GetTestTable().Query(type: typeof(Users), columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().AggregateField); + Assert.AreEqual("Logan", v.First().First); + Assert.AreEqual(2, v.Take(10).Last().AggregateField); + Assert.AreEqual(1, v.Take(11).Last().AggregateField); + Assert.AreEqual(1, v.Last().AggregateField); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. + [Test] + public override void TestGenericFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().AggregateField); + Assert.AreEqual("Logan", v.First().First); + Assert.AreEqual(2, v.Take(10).Last().AggregateField); + Assert.AreEqual(1, v.Take(11).Last().AggregateField); + Assert.AreEqual(1, v.Last().AggregateField); + } + + /// Test typed First method. + [Test] + public override void TestTypedFirst() + { + Assert.AreEqual(1, GetTestTable().First(type: typeof(Users), columns: "id").Id); + } + + /// Test typed Last method. + [Test] + public override void TestTypedLast() + { + Assert.AreEqual(200, GetTestTable().Last(type: typeof(Users), columns: "id").Id); + } + + /// Test typed Single multi. + [Test] + public override void TestTypedSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(type: typeof(Users), columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.Id); + Assert.AreEqual(exp.first, o.First); + Assert.AreEqual(exp.last, o.Last); + } + + /// Test typed where expression equal. + [Test] + public override void TestTypedWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("id").Eq(100)).Login); + } + + /// Test typed where expression like. + [Test] + public override void TestTypedWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("login").Like("Hoyt.%")).Id); + } + + /// Test generic First method. + [Test] + public override void TestGenericFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").Id); + } + + /// Test generic Last method. + [Test] + public override void TestGenericLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").Id); + } + + /// Test generic Single multi. + [Test] + public override void TestGenericSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.Id); + Assert.AreEqual(exp.first, o.First); + Assert.AreEqual(exp.last, o.Last); + } + + /// Test generic where expression equal. + [Test] + public override void TestGenericWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).Login); + } + + /// Test generic where expression like. + [Test] + public override void TestGenericWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).Id); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/TypedAccessTests.cs b/DynamORM.Tests/Select/TypedAccessTests.cs new file mode 100644 index 0000000..81e13cd --- /dev/null +++ b/DynamORM.Tests/Select/TypedAccessTests.cs @@ -0,0 +1,604 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test typed ORM. + /// Type to test. + [TestFixture(typeof(users))] + public class TypedAccessTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + + // Cache table (profiler freaks out) + GetTestTable(); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table(); + } + + #region Select typed + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestTypedGetAll() + { + var list = (GetTestTable().Query(type: typeof(T)) as IEnumerable).Cast().ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test unknown op. + [Test] + public virtual void TestTypedUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(type: typeof(T), with: "cheese")); + } + + /// Test typed Count method. + [Test] + public virtual void TestTypedCount() + { + Assert.AreEqual(200, GetTestTable().Count(type: typeof(T), columns: "id")); + } + + /// Test count with in steatement. + [Test] + public virtual void TestTypedSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in steatement. + [Test] + public virtual void TestTypedSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test typed First method. + [Test] + public virtual void TestTypedFirst() + { + Assert.AreEqual(1, GetTestTable().First(type: typeof(T), columns: "id").id); + } + + /// Test typed Last method. + [Test] + public virtual void TestTypedLast() + { + Assert.AreEqual(200, GetTestTable().Last(type: typeof(T), columns: "id").id); + } + + /// Test typed Count method. + [Test] + public virtual void TestTypedCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(type: typeof(T), first: "Ori")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMin() + { + Assert.AreEqual(1, GetTestTable().Min(type: typeof(T), columns: "id")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMax() + { + Assert.AreEqual(200, GetTestTable().Max(type: typeof(T), columns: "id")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedtAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(type: typeof(T), columns: "id")); + } + + /// Test typed Sum method. + [Test] + public virtual void TestTypedSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(type: typeof(T), columns: "id")); + } + + /// Test typed Scalar method for invalid operation exception. + [Test] + public virtual void TestTypedScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(type: typeof(T), id: 19)); + } + + /// Test typed Scalar method. + [Test] + public virtual void TestTypedScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(type: typeof(T), columns: "first", id: 19)); + } + + /// Test typed Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestTypedScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(type: typeof(T), columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestTypedScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(type: typeof(T), columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. + [Test] + public virtual void TestTypedFancyAggregateQuery() + { + var v = (GetTestTable().Query(type: typeof(T), columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. + [Test] + public virtual void TestTypedAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(type: typeof(T), columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. + [Test] + public virtual void TestTypedAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(type: typeof(T), columns: @"length(""email""):len")); + } + + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. + public virtual void TestTypedFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(type: typeof(T), condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test typed Single multi. + [Test] + public virtual void TestTypedSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(type: typeof(T), columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + #endregion Select typed + + #region Where typed + + /// Test typed where expression equal. + [Test] + public virtual void TestTypedWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(T), where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test typed where expression not equal. + [Test] + public virtual void TestTypedWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Not(100))); + } + + /// Test typed where expression like. + [Test] + public virtual void TestTypedWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(type: typeof(T), where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test typed where expression greater. + [Test] + public virtual void TestTypedWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Greater(100))); + } + + /// Test typed where expression greater or equal. + [Test] + public virtual void TestTypedWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test typed where expression less. + [Test] + public virtual void TestTypedWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Less(100))); + } + + /// Test typed where expression less or equal. + [Test] + public virtual void TestTypedWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test typed where expression between. + [Test] + public virtual void TestTypedWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test typed where expression in params. + [Test] + public virtual void TestTypedWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test typed where expression in array. + [Test] + public virtual void TestTypedWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + #endregion Where typed + + #region Select generic + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestGenericGetAll() + { + var list = (GetTestTable().Query() as IEnumerable).Cast().ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test unknown op. + [Test] + public virtual void TestGenericUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(with: "cheese")); + } + + /// Test generic Count method. + [Test] + public virtual void TestGenericCount() + { + Assert.AreEqual(200, GetTestTable().Count(columns: "id")); + } + + /// Test count with in steatement. + [Test] + public virtual void TestGenericSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in steatement. + [Test] + public virtual void TestGenericSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test generic First method. + [Test] + public virtual void TestGenericFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").id); + } + + /// Test generic Last method. + [Test] + public virtual void TestGenericLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); + } + + /// Test generic Count method. + [Test] + public virtual void TestGenericCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenericMin() + { + Assert.AreEqual(1, GetTestTable().Min(columns: "id")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenericMax() + { + Assert.AreEqual(200, GetTestTable().Max(columns: "id")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenerictAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); + } + + /// Test generic Sum method. + [Test] + public virtual void TestGenericSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); + } + + /// Test generic Scalar method for invalid operation exception. + [Test] + public virtual void TestGenericScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(id: 19)); + } + + /// Test generic Scalar method. + [Test] + public virtual void TestGenericScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); + } + + /// Test generic Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestGenericScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test generic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestGenericScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. + [Test] + public virtual void TestGenericFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. + [Test] + public virtual void TestGenericAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. + [Test] + public virtual void TestGenericAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); + } + + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. + public virtual void TestGenericFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test generic Single multi. + [Test] + public virtual void TestGenericSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + #endregion Select generic + + #region Where generic + + /// Test generic where expression equal. + [Test] + public virtual void TestGenericWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test generic where expression not equal. + [Test] + public virtual void TestGenericWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); + } + + /// Test generic where expression like. + [Test] + public virtual void TestGenericWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test generic where expression not like. + [Test] + public virtual void TestGenericWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test generic where expression greater. + [Test] + public virtual void TestGenericWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); + } + + /// Test generic where expression greater or equal. + [Test] + public virtual void TestGenericWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test generic where expression less. + [Test] + public virtual void TestGenericWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); + } + + /// Test generic where expression less or equal. + [Test] + public virtual void TestGenericWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test generic where expression between. + [Test] + public virtual void TestGenericWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test generic where expression in params. + [Test] + public virtual void TestGenericWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test generic where expression in array. + [Test] + public virtual void TestGenericWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + #endregion Where generic + } +} \ No newline at end of file diff --git a/DynamORM.Tests/TestsBase.cs b/DynamORM.Tests/TestsBase.cs new file mode 100644 index 0000000..414a581 --- /dev/null +++ b/DynamORM.Tests/TestsBase.cs @@ -0,0 +1,119 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Data; +using System.IO; +using SQLiteFactory = +#if MONO + + Mono.Data.Sqlite.SqliteFactory; + +#else + + System.Data.SQLite.SQLiteFactory; + +#endif + +namespace DynamORM.Tests +{ + /// Basic test utilities. + public class TestsBase + { + private string _dbpath = Path.GetTempFileName(); + + /// Gets or sets instance. + public DynamicDatabase Database { get; set; } + + #region ADO.NET initialization + + /// Prepare database with some fixed data for tests using plain old ADO.NET. + public void CreateTestDatabase() + { + Console.Out.Write("Creating database at '{0}'...", _dbpath); + + using (IDbConnection conn = SQLiteFactory.Instance.CreateConnection()) + { + conn.ConnectionString = string.Format("Data Source={0};", _dbpath); + conn.Open(); + + using (IDbTransaction trans = conn.BeginTransaction()) + { + using (IDbCommand cmd = conn.CreateCommand() + .SetCommand(Properties.Resources.UsersTable) + .SetTransaction(trans)) + cmd.ExecuteNonQuery(); + + trans.Commit(); + } + } + + Console.Out.WriteLine(" Done."); + } + + /// Delete test database file. + public void DestroyTestDatabase() + { + File.Delete(_dbpath); + } + + #endregion ADO.NET initialization + + #region DynamORM Initialization + + /// Create with default otions for SQLite. + public void CreateDynamicDatabase() + { + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset | + DynamicDatabaseOptions.SupportSchema); + } + + /// Create with specified options. + /// Database options. + public void CreateDynamicDatabase(DynamicDatabaseOptions options) + { + Database = new DynamicDatabase(SQLiteFactory.Instance, + string.Format("Data Source={0};", _dbpath), options) + { + DumpCommands = true + }; + } + + /// Dispose (and rollback if transaction exist). + public void DestroyDynamicDatabase() + { + if (Database != null) + Database.Dispose(); + } + + #endregion DynamORM Initialization + } +} \ No newline at end of file diff --git a/DynamORM.sln b/DynamORM.sln new file mode 100644 index 0000000..275d16d --- /dev/null +++ b/DynamORM.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +# SharpDevelop 4.2.0.8774-RC +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamORM", "DynamORM\DynamORM.csproj", "{63963ED7-9C78-4672-A4D4-339B6E825503}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamORM.Tests", "DynamORM.Tests\DynamORM.Tests.csproj", "{D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {63963ED7-9C78-4672-A4D4-339B6E825503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63963ED7-9C78-4672-A4D4-339B6E825503}.Release|Any CPU.Build.0 = Release|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DynamORM/Builders/DynamicDeleteQueryBuilder.cs b/DynamORM/Builders/DynamicDeleteQueryBuilder.cs new file mode 100644 index 0000000..741a1b1 --- /dev/null +++ b/DynamORM/Builders/DynamicDeleteQueryBuilder.cs @@ -0,0 +1,65 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Data; +using System.Text; + +namespace DynamORM.Builders +{ + /// Delete query builder. + public class DynamicDeleteQueryBuilder : DynamicQueryBuilder + { + /// Initializes a new instance of the class. + /// Parent dynamic table. + public DynamicDeleteQueryBuilder(DynamicTable table) + : base(table) + { + } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public override IDbCommand FillCommand(IDbCommand command) + { + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat("DELETE FROM {0}", TableName); + + FillWhere(command, sb); + + return command.SetCommand(sb.ToString()); + } + + /// Execute this builder. + /// Number of affected rows. + public override dynamic Execute() + { + return DynamicTable.Execute(this); + } + } +} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicInsertQueryBuilder.cs b/DynamORM/Builders/DynamicInsertQueryBuilder.cs new file mode 100644 index 0000000..f6d0f61 --- /dev/null +++ b/DynamORM/Builders/DynamicInsertQueryBuilder.cs @@ -0,0 +1,192 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using DynamORM.Mapper; + +namespace DynamORM.Builders +{ + /// Insert query builder. + public class DynamicInsertQueryBuilder : DynamicQueryBuilder + { + /// Gets list of columns that will be seected. + public IDictionary ValueColumns { get; private set; } + + /// Initializes a new instance of the class. + /// Parent dynamic table. + public DynamicInsertQueryBuilder(DynamicTable table) + : base(table) + { + ValueColumns = new Dictionary(); + } + + /// Add insert fields. + /// Insert column and value. + /// Builder instance. + public virtual DynamicInsertQueryBuilder Insert(DynamicColumn column) + { + if (ValueColumns.ContainsKey(column.ColumnName.ToLower())) + ValueColumns[column.ColumnName.ToLower()] = column; + else + ValueColumns.Add(column.ColumnName.ToLower(), column); + + return this; + } + + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + public virtual DynamicInsertQueryBuilder Insert(string column, object value) + { + if (value is DynamicColumn) + { + var v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return Insert(v); + } + + if (ValueColumns.ContainsKey(column.ToLower())) + ValueColumns[column.ToLower()].Value = value; + else + ValueColumns.Add(column.ToLower(), new DynamicColumn + { + ColumnName = column, + Value = value + }); + + return this; + } + + /// Add insert fields. + /// Set insert value as properties and values of an object. + /// Builder instance. + public virtual DynamicInsertQueryBuilder Insert(object o) + { + var dict = o.ToDictionary(); + var mapper = DynamicMapperCache.GetMapper(o.GetType()); + + if (mapper != null) + { + foreach (var con in dict) + if (!mapper.Ignored.Contains(con.Key)) + Insert(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); + } + else + foreach (var con in dict) + Insert(con.Key, con.Value); + + return this; + } + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + public override DynamicInsertQueryBuilder Where(DynamicColumn column) + { + throw new NotSupportedException(); + } + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public override DynamicInsertQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + throw new NotSupportedException(); + } + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public override DynamicInsertQueryBuilder Where(string column, object value) + { + throw new NotSupportedException(); + } + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public override DynamicInsertQueryBuilder Where(object conditions, bool schema = false) + { + throw new NotSupportedException(); + } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public override IDbCommand FillCommand(IDbCommand command) + { + if (ValueColumns.Count == 0) + throw new InvalidOperationException("Insert query should contain columns to change."); + + StringBuilder builderColumns = new StringBuilder(); + StringBuilder builderValues = new StringBuilder(); + + bool first = true; + var db = DynamicTable.Database; + + foreach (var v in ValueColumns) + { + int pos = command.Parameters.Count; + + if (!first) + { + builderColumns.Append(", "); + builderValues.Append(", "); + } + + db.DecorateName(builderColumns, v.Value.ColumnName); + db.GetParameterName(builderValues, pos); + + command.AddParameter(this, v.Value); + + first = false; + } + + return command.SetCommand("INSERT INTO {0} ({1}) VALUES ({2})", TableName, builderColumns, builderValues); + } + + /// Execute this builder. + /// Number of affected rows. + public override dynamic Execute() + { + return DynamicTable.Execute(this); + } + } +} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicQueryBuilder.cs b/DynamORM/Builders/DynamicQueryBuilder.cs new file mode 100644 index 0000000..7caaba6 --- /dev/null +++ b/DynamORM/Builders/DynamicQueryBuilder.cs @@ -0,0 +1,381 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using DynamORM.Mapper; + +namespace DynamORM.Builders +{ + /// Base query builder. + /// Return type of methods that should return self. + public abstract class DynamicQueryBuilder : IDynamicQueryBuilder where T : class + { + /// Gets instance. + public DynamicTable DynamicTable { get; private set; } + + /// Gets where conditions. + public List WhereConditions { get; private set; } + + /// Gets table schema. + public Dictionary Schema { get; private set; } + + /// Gets a value indicating whether database supports standard schema. + public bool SupportSchema { get; private set; } + + /// Gets table name. + public string TableName { get; private set; } + + /// Initializes a new instance of the DynamicQueryBuilder class. + /// Parent dynamic table. + public DynamicQueryBuilder(DynamicTable table) + { + DynamicTable = table; + TableName = table.TableName; + + WhereConditions = new List(); + + SupportSchema = (DynamicTable.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + + Schema = DynamicTable.Schema; + } + + /// Set table name. + /// Name of table. + /// Builder instance. + public virtual T Table(string name) + { + if (TableName.ToLower() != name.ToLower()) + { + TableName = name; + + Schema = DynamicTable.Database.GetSchema(TableName); + + if (Schema == null) + throw new InvalidOperationException("Can't assign type as a table for which schema can't be build."); + } + + return this as T; + } + + /// Set table name. + /// Type representing table. + /// Builder instance. + public virtual T Table() + { + return Table(typeof(T)); + } + + /// Set table name. + /// Type representing table. + /// Builder instance. + public virtual T Table(Type type) + { + var mapper = DynamicMapperCache.GetMapper(type); + string name = string.Empty; + + if (mapper == null) + throw new InvalidOperationException("Cant assign unmapable type as a table."); + else + name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + if (TableName.ToLower() != name.ToLower()) + { + TableName = name; + + Schema = DynamicTable.Database.GetSchema(type); + } + + return this as T; + } + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual T Where(DynamicColumn column) + { + WhereConditions.Add(column); + + return this as T; + } + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual T Where(string column, DynamicColumn.CompareOperator op, object value) + { + if (value is DynamicColumn) + { + var v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return Where(v); + } + else if (value is IEnumerable) + { + foreach (var v in (IEnumerable)value) + Where(v); + + return this as T; + } + + WhereConditions.Add(new DynamicColumn + { + ColumnName = column, + Operator = op, + Value = value + }); + + return this as T; + } + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual T Where(string column, object value) + { + return Where(column, DynamicColumn.CompareOperator.Eq, value); + } + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public virtual T Where(object conditions, bool schema = false) + { + if (conditions is DynamicColumn) + return Where((DynamicColumn)conditions); + + var dict = conditions.ToDictionary(); + var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + + foreach (var con in dict) + { + if (mapper.Ignored.Contains(con.Key)) + continue; + + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; + + DynamicSchemaColumn? col = null; + + if (schema) + { + col = Schema.TryGetNullable(colName.ToLower()); + + if (!col.HasValue) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", con.Key)); + + if (!col.Value.IsKey) + continue; + + colName = col.Value.Name; + } + + Where(colName, con.Value); + } + + return this as T; + } + + /// Get string representation of operator. + /// Operator object. + /// String representation of operator. + public string ToOperator(DynamicColumn.CompareOperator op) + { + switch (op) + { + case DynamicColumn.CompareOperator.Eq: return "="; + case DynamicColumn.CompareOperator.Not: return "<>"; + case DynamicColumn.CompareOperator.Like: return "LIKE"; + case DynamicColumn.CompareOperator.NotLike: return "NOT LIKE"; + case DynamicColumn.CompareOperator.Lt: return "<"; + case DynamicColumn.CompareOperator.Lte: return "<="; + case DynamicColumn.CompareOperator.Gt: return ">"; + case DynamicColumn.CompareOperator.Gte: return ">="; + case DynamicColumn.CompareOperator.Between: + case DynamicColumn.CompareOperator.In: + default: + throw new ArgumentException(string.Format("This operator ('{0}') requires more than conversion to string.", op)); + } + } + + /// Fill where part of a query. + /// Command to fill. + /// String builder. + public virtual void FillWhere(IDbCommand command, StringBuilder sb) + { + // Yes, this method qualifies fo refactoring... but it's fast + bool first = true; + var db = DynamicTable.Database; + + foreach (var v in WhereConditions) + { + var col = Schema.TryGetNullable(v.ColumnName); + + string column = col.HasValue ? col.Value.Name : v.ColumnName; + + if ((column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.LeftDecorator) == -1) && + (column.IndexOf('(') == -1 || column.IndexOf(')') == -1)) + column = db.DecorateName(column); + + if (v.Value == null) + { + #region Null operators + + if (v.Operator == DynamicColumn.CompareOperator.Not || v.Operator == DynamicColumn.CompareOperator.Eq) + sb.AppendFormat(" {0} {1} IS{2} NULL", + first ? "WHERE" : "AND", column, + v.Operator == DynamicColumn.CompareOperator.Not ? " NOT" : string.Empty); + else + throw new InvalidOperationException("NULL can only be compared by IS or IS NOT operator."); + + #endregion + } + else if (v.Operator != DynamicColumn.CompareOperator.In && + v.Operator != DynamicColumn.CompareOperator.Between) + { + #region Standard operators + + int pos = command.Parameters.Count; + + sb.AppendFormat(" {0} {1} {2} ", + first ? "WHERE" : "AND", column, + ToOperator(v.Operator)); + + db.GetParameterName(sb, pos); + + command.AddParameter(this, v); + + #endregion + } + else if (((object)v.Value).GetType().IsCollection() || v.Value is IEnumerable) + { + #region In or Between operator + + if (v.Operator == DynamicColumn.CompareOperator.Between) + { + #region Between operator + + var vals = (v.Value as IEnumerable).Take(2).ToList(); + + if (vals == null && v.Value is Array) + vals = ((Array)v.Value).Cast().ToList(); + + if (vals.Count == 2) + { + sb.AppendFormat(" {0} {1} BETWEEN ", + first ? "WHERE" : "AND", column); + + // From parameter + db.GetParameterName(sb, command.Parameters.Count); + v.Value = vals[0]; + command.AddParameter(this, v); + + sb.Append(" AND "); + + // To parameter + db.GetParameterName(sb, command.Parameters.Count); + v.Value = vals[1]; + command.AddParameter(this, v); + + // Reset value + v.Value = vals; + } + else + throw new InvalidOperationException("BETWEEN must have two values."); + + #endregion + } + else if (v.Operator == DynamicColumn.CompareOperator.In) + { + #region In operator + + sb.AppendFormat(" {0} {1} IN(", + first ? "WHERE" : "AND", column); + + bool firstParam = true; + + var vals = v.Value as IEnumerable; + + if (vals == null && v.Value is Array) + vals = ((Array)v.Value).Cast() as IEnumerable; + + foreach (var val in vals) + { + int pos = command.Parameters.Count; + + if (!firstParam) + sb.Append(", "); + + db.GetParameterName(sb, pos); + v.Value = val; + + command.AddParameter(this, v); + + firstParam = false; + } + + v.Value = vals; + + sb.Append(")"); + + #endregion + } + else + throw new Exception("BAZINGA. You have reached unreachable code."); + + #endregion + } + else + throw new InvalidOperationException( + string.Format("Operator was {0}, but value wasn't enumerable. Column: '{1}'", v.Operator.ToString().ToUpper(), col)); + + first = false; + } + } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public abstract IDbCommand FillCommand(IDbCommand command); + + /// Execute this builder. + /// Result of an execution.. + public abstract dynamic Execute(); + } +} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/DynamicSelectQueryBuilder.cs new file mode 100644 index 0000000..2bc684b --- /dev/null +++ b/DynamORM/Builders/DynamicSelectQueryBuilder.cs @@ -0,0 +1,272 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; + +namespace DynamORM.Builders +{ + /// Select query builder. + public class DynamicSelectQueryBuilder : DynamicQueryBuilder + { + /// Gets dictionary of columns that will be seected. + public List Columns { get; private set; } + + /// Gets dictionary of columns that will be used to group query. + public List Group { get; private set; } + + /// Gets dictionary of columns that will be used to order query. + public List Order { get; private set; } + + private int? _top = null; + private int? _limit = null; + private int? _offset = null; + private bool _distinct = false; + + /// Initializes a new instance of the class. + /// Parent dynamic table. + public DynamicSelectQueryBuilder(DynamicTable table) + : base(table) + { + Columns = new List(); + Group = new List(); + Order = new List(); + } + + /// Add select columns. + /// Columns to add to object. + /// Builder instance. + public DynamicSelectQueryBuilder Select(params DynamicColumn[] columns) + { + foreach (var col in columns) + Columns.Add(col); + + return this; + } + + /// Add select columns. + /// Columns to add to object. + /// Column format consist of Column Name, Alias and + /// Aggregate function in this order separated by ':'. + /// Builder instance. + public DynamicSelectQueryBuilder Select(params string[] columns) + { + return Select(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); + } + + /// Add select columns. + /// Columns to group by. + /// Builder instance. + public DynamicSelectQueryBuilder GroupBy(params DynamicColumn[] columns) + { + foreach (var col in columns) + Group.Add(col); + + return this; + } + + /// Add select columns. + /// Columns to group by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public DynamicSelectQueryBuilder GroupBy(params string[] columns) + { + return GroupBy(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); + } + + /// Add select columns. + /// Columns to order by. + /// Builder instance. + public DynamicSelectQueryBuilder OrderBy(params DynamicColumn[] columns) + { + foreach (var col in columns) + Order.Add(col); + + return this; + } + + /// Add select columns. + /// Columns to order by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public DynamicSelectQueryBuilder OrderBy(params string[] columns) + { + return OrderBy(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); + } + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public DynamicSelectQueryBuilder Top(int? top) + { + if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) + throw new NotSupportedException("Database doesn't support TOP clause."); + + _top = top; + return this; + } + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public DynamicSelectQueryBuilder Limit(int? limit) + { + if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) + throw new NotSupportedException("Database doesn't support LIMIT clause."); + + _limit = limit; + return this; + } + + /// Set top if database support it. + /// How many objects skip selecting. + /// Builder instance. + public DynamicSelectQueryBuilder Offset(int? offset) + { + if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) + throw new NotSupportedException("Database doesn't support OFFSET clause."); + + _offset = offset; + return this; + } + + /// Set distinct mode. + /// Distinct mode. + /// Builder instance. + public DynamicSelectQueryBuilder Distinct(bool distinct = true) + { + _distinct = distinct; + return this; + } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public override IDbCommand FillCommand(IDbCommand command) + { + StringBuilder sb = new StringBuilder(); + + var db = DynamicTable.Database; + + sb.AppendFormat("SELECT{0}{1} ", + _distinct ? " DISTINCT" : string.Empty, + _top.HasValue ? string.Format(" TOP {0}", _top.Value) : string.Empty); + + BuildColumns(sb, db); + + sb.Append(" FROM "); + db.DecorateName(sb, TableName); + + FillWhere(command, sb); + + BuildGroup(sb, db); + BuildOrder(sb, db); + + if (_limit.HasValue) + sb.AppendFormat(" LIMIT {0}", _limit.Value); + + if (_offset.HasValue) + sb.AppendFormat(" OFFSET {0}", _offset.Value); + + return command.SetCommand(sb.ToString()); + } + + private void BuildColumns(StringBuilder sb, DynamicDatabase db) + { + if (Columns.Count > 0) + { + bool first = true; + + // Not pretty but blazing fast + Columns.ForEach(c => + { + if (!first) + sb.Append(", "); + + c.ToSQLSelectColumn(db, sb); + first = false; + }); + } + else + sb.Append("*"); + } + + private void BuildGroup(StringBuilder sb, DynamicDatabase db) + { + if (Group.Count > 0) + { + sb.Append(" GROUP BY "); + bool first = true; + + // Not pretty but blazing fast + Group.ForEach(c => + { + if (!first) + sb.Append(", "); + + c.ToSQLGroupByColumn(db, sb); + first = false; + }); + } + } + + private void BuildOrder(StringBuilder sb, DynamicDatabase db) + { + if (Order.Count > 0) + { + sb.Append(" ORDER BY "); + bool first = true; + + // Not pretty but blazing fast + Order.ForEach(c => + { + if (!first) + sb.Append(", "); + + c.ToSQLOrderByColumn(db, sb); + first = false; + }); + } + } + + /// Execute this builder. + /// Enumerator of objects expanded from query. + public override dynamic Execute() + { + if (Columns.Count <= 1 && ((_top.HasValue && _top.Value == 1) || (_limit.HasValue && _limit.Value == 1))) + return DynamicTable.Scalar(this); + else + return DynamicTable.Query(this); + } + } +} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicUpdateQueryBuilder.cs b/DynamORM/Builders/DynamicUpdateQueryBuilder.cs new file mode 100644 index 0000000..372b0de --- /dev/null +++ b/DynamORM/Builders/DynamicUpdateQueryBuilder.cs @@ -0,0 +1,238 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using DynamORM.Mapper; + +namespace DynamORM.Builders +{ + /// Update query builder. + public class DynamicUpdateQueryBuilder : DynamicQueryBuilder + { + /// Gets list of columns that will be seected. + public IDictionary ValueColumns { get; private set; } + + /// Initializes a new instance of the class. + /// Parent dynamic table. + public DynamicUpdateQueryBuilder(DynamicTable table) + : base(table) + { + ValueColumns = new Dictionary(); + } + + /// Add update value or where condition using schema. + /// Update or where column name and value. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Update(DynamicColumn column) + { + var col = Schema.TryGetNullable(column.ColumnName.ToLower()); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); + + if (col.HasValue && col.Value.IsKey) + Where(column); + else + Values(column.ColumnName, column.Value); + + return this; + } + + /// Add update value or where condition using schema. + /// Update or where column name. + /// Column value. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Update(string column, object value) + { + var col = Schema.TryGetNullable(column.ToLower()); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); + + if (col.HasValue && col.Value.IsKey) + Where(column, value); + else + Values(column, value); + + return this; + } + + /// Add update values and where condition columns using schema. + /// Set values or conditions as properties and values of an object. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Update(object conditions) + { + if (conditions is DynamicColumn) + return Update((DynamicColumn)conditions); + + var dict = conditions.ToDictionary(); + var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + + foreach (var con in dict) + { + if (mapper.Ignored.Contains(con.Key)) + continue; + + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; + var col = Schema.TryGetNullable(colName.ToLower()); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", colName)); + + if (col.HasValue) + { + colName = col.Value.Name; + + if (col.Value.IsKey) + { + Where(colName, con.Value); + + continue; + } + } + + Values(colName, con.Value); + } + + return this; + } + + /// Add update fields. + /// Update column and value. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Values(DynamicColumn column) + { + if (ValueColumns.ContainsKey(column.ColumnName.ToLower())) + ValueColumns[column.ColumnName.ToLower()] = column; + else + ValueColumns.Add(column.ColumnName.ToLower(), column); + + return this; + } + + /// Add update fields. + /// Update column. + /// Update value. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Values(string column, object value) + { + if (value is DynamicColumn) + { + var v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return Values(v); + } + + if (ValueColumns.ContainsKey(column.ToLower())) + ValueColumns[column.ToLower()].Value = value; + else + ValueColumns.Add(column.ToLower(), new DynamicColumn + { + ColumnName = column, + Value = value + }); + + return this; + } + + /// Add update fields. + /// Set update value as properties and values of an object. + /// Builder instance. + public virtual DynamicUpdateQueryBuilder Values(object values) + { + if (values is DynamicColumn) + return Values((DynamicColumn)values); + + var dict = values.ToDictionary(); + var mapper = DynamicMapperCache.GetMapper(values.GetType()); + + if (mapper != null) + { + foreach (var con in dict) + if (!mapper.Ignored.Contains(con.Key)) + Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); + } + else + foreach (var con in dict) + Values(con.Key, con.Value); + + return this; + } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public override IDbCommand FillCommand(IDbCommand command) + { + if (ValueColumns.Count == 0) + throw new InvalidOperationException("Update query should contain columns to change."); + + StringBuilder sb = new StringBuilder(); + var db = DynamicTable.Database; + + sb.Append("UPDATE "); + db.DecorateName(sb, TableName); + sb.Append(" SET "); + + bool first = true; + + foreach (var v in ValueColumns) + { + int pos = command.Parameters.Count; + + if (!first) + sb.Append(", "); + + db.DecorateName(sb, v.Value.ColumnName); + sb.Append(" = "); + db.GetParameterName(sb, pos); + + command.AddParameter(this, v.Value); + + first = false; + } + + FillWhere(command, sb); + + return command.SetCommand(sb.ToString()); + } + + /// Execute this builder. + /// Number of affected rows. + public override dynamic Execute() + { + return DynamicTable.Execute(this); + } + } +} \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicQueryBuilder.cs b/DynamORM/Builders/IDynamicQueryBuilder.cs new file mode 100644 index 0000000..f05af75 --- /dev/null +++ b/DynamORM/Builders/IDynamicQueryBuilder.cs @@ -0,0 +1,55 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using System.Data; + +namespace DynamORM.Builders +{ + /// Base query builder interface. + public interface IDynamicQueryBuilder + { + /// Gets instance. + DynamicTable DynamicTable { get; } + + /// Gets table schema. + Dictionary Schema { get; } + + /// Gets a value indicating whether database supports standard schema. + bool SupportSchema { get; } + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + IDbCommand FillCommand(IDbCommand command); + + /// Execute this builder. + /// Result of an execution.. + dynamic Execute(); + } +} \ No newline at end of file diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj new file mode 100644 index 0000000..db0747e --- /dev/null +++ b/DynamORM/DynamORM.csproj @@ -0,0 +1,79 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {63963ED7-9C78-4672-A4D4-339B6E825503} + Library + Properties + DynamORM + DynamORM + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DynamORM/DynamicColumn.cs b/DynamORM/DynamicColumn.cs new file mode 100644 index 0000000..19a2702 --- /dev/null +++ b/DynamORM/DynamicColumn.cs @@ -0,0 +1,423 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace DynamORM +{ + /// Small utility class to manage single columns. + public class DynamicColumn + { + #region Enums + + /// Order By Order. + public enum SortOrder + { + /// Ascending order. + Asc, + + /// Descending order. + Desc + } + + /// Dynamic query operators. + public enum CompareOperator + { + /// Equals operator (default). + Eq, + + /// Not equal operator. + Not, + + /// Like operator. + Like, + + /// Not like operator. + NotLike, + + /// In operator. + In, + + /// Less than operator. + Lt, + + /// Less or equal operator. + Lte, + + /// Greather than operator. + Gt, + + /// Greather or equal operator. + Gte, + + /// Between two values. + Between, + } + + #endregion Enums + + #region Constructors + + /// Initializes a new instance of the class. + public DynamicColumn() { } + + /// Initializes a new instance of the class. + /// Constructor provided for easier object creation in qeries. + /// Name of column to set. + public DynamicColumn(string columnName) + { + ColumnName = columnName; + } + + /// Initializes a new instance of the class. + /// Constructor provided for easier object creation in qeries. + /// Name of column to set. + /// Compare column to value(s) operator. + /// Parameter value(s). + public DynamicColumn(string columnName, CompareOperator oper, object value) + : this(columnName) + { + Operator = oper; + Value = value; + } + + #endregion Constructors + + #region Properties + + /// Gets or sets column name. + public string ColumnName { get; set; } + + /// Gets or sets column alias. + /// Select specific. + public string Alias { get; set; } + + /// Gets or sets aggregate function used on column. + /// Select specific. + public string Aggregate { get; set; } + + /// Gets or sets order direction. + public SortOrder Order { get; set; } + + /// Gets or sets value for parameters. + public object Value { get; set; } + + /// Gets or sets condition operator. + public CompareOperator Operator { get; set; } + + #endregion Properties + + #region Query creation helpers + + #region Operators + + private DynamicColumn SetOperatorAndValue(CompareOperator c, object v) + { + Operator = c; + Value = v; + + return this; + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn Eq(object value) + { + return SetOperatorAndValue(CompareOperator.Eq, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn Not(object value) + { + return SetOperatorAndValue(CompareOperator.Not, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn Like(object value) + { + return SetOperatorAndValue(CompareOperator.Like, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn NotLike(object value) + { + return SetOperatorAndValue(CompareOperator.NotLike, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn Greater(object value) + { + return SetOperatorAndValue(CompareOperator.Gt, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn Less(object value) + { + return SetOperatorAndValue(CompareOperator.Lt, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn GreaterOrEqual(object value) + { + return SetOperatorAndValue(CompareOperator.Gte, value); + } + + /// Helper method setting + /// to and + /// to provided value. + /// Value of parameter to set. + /// Returns self. + public DynamicColumn LessOrEqual(object value) + { + return SetOperatorAndValue(CompareOperator.Lte, value); + } + + /// Helper method setting + /// to and + /// to provided values. + /// Value of from parameter to set. + /// Value of to parameter to set. + /// Returns self. + public DynamicColumn Between(object from, object to) + { + return SetOperatorAndValue(CompareOperator.Between, new[] { from, to }); + } + + /// Helper method setting + /// to and + /// to provided values. + /// Values of parameters to set. + /// Returns self. + public DynamicColumn In(IEnumerable values) + { + return SetOperatorAndValue(CompareOperator.In, values); + } + + /// Helper method setting + /// to and + /// to provided values. + /// Values of parameters to set. + /// Returns self. + public DynamicColumn In(params object[] values) + { + if (values.Length == 1 && (values[0].GetType().IsCollection() || values[0] is IEnumerable)) + return SetOperatorAndValue(CompareOperator.In, values[0]); + + return SetOperatorAndValue(CompareOperator.In, values); + } + + #endregion Operators + + #region Order + + /// Helper method setting + /// to .. + /// Returns self. + public DynamicColumn Asc() + { + Order = SortOrder.Asc; + return this; + } + + /// Helper method setting + /// to .. + /// Returns self. + public DynamicColumn Desc() + { + Order = SortOrder.Desc; + return this; + } + + #endregion Order + + /// Helper method setting + /// + /// to provided name. + /// Name to set. + /// Returns self. + public DynamicColumn SetName(string name) + { + ColumnName = name; + return this; + } + + /// Helper method setting + /// + /// to provided alias. + /// Alias to set. + /// Returns self. + public DynamicColumn SetAlias(string alias) + { + Alias = alias; + return this; + } + + /// Helper method setting + /// + /// to provided aggregate. + /// Aggregate to set. + /// Returns self. + public DynamicColumn SetAggregate(string aggregate) + { + Aggregate = aggregate; + return this; + } + + #endregion Query creation helpers + + #region Parsing + + /// Parse column for select query. + /// Column format consist of Column Name, Alias and + /// Aggregate function in this order separated by ':'. + /// Column string. + /// Instance of . + public static DynamicColumn ParseSelectColumn(string column) + { + // Split column description + var parts = column.Split(':'); + + if (parts.Length > 0) + { + DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] }; + + if (parts.Length > 1) + ret.Alias = parts[1]; + + if (parts.Length > 2) + ret.Aggregate = parts[2]; + + return ret; + } + + return null; + } + + /// Parse column for order by in query. + /// Column format consist of Column Name and + /// Direction in this order separated by ':'. + /// Column string. + /// Instance of . + public static DynamicColumn ParseOrderByColumn(string column) + { + // Split column description + var parts = column.Split(':'); + + if (parts.Length > 0) + { + DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] }; + + if (parts.Length > 1) + ret.Order = parts[1].ToLower() == "d" || parts[1].ToLower() == "desc" ? SortOrder.Desc : SortOrder.Asc; + + if (parts.Length > 2) + ret.Alias = parts[2]; + + return ret; + } + + return null; + } + + #endregion Parsing + + #region ToSQL + + internal void ToSQLSelectColumn(DynamicDatabase db, StringBuilder sb) + { + string column = ColumnName == "*" ? "*" : ColumnName; + + if (column != "*" && + (column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.LeftDecorator) == -1) && + (column.IndexOf('(') == -1 || column.IndexOf(')') == -1)) + column = db.DecorateName(column); + + string alias = Alias; + + if (!string.IsNullOrEmpty(Aggregate)) + { + sb.AppendFormat("{0}({1})", Aggregate, column); + + alias = string.IsNullOrEmpty(alias) ? + ColumnName == "*" ? Guid.NewGuid().ToString() : ColumnName : + alias; + } + else + sb.Append(column); + + if (!string.IsNullOrEmpty(alias)) + sb.AppendFormat(" AS {0}", alias); + } + + internal void ToSQLGroupByColumn(DynamicDatabase db, StringBuilder sb) + { + sb.Append(db.DecorateName(ColumnName)); + } + + internal void ToSQLOrderByColumn(DynamicDatabase db, StringBuilder sb) + { + if (!string.IsNullOrEmpty(Alias)) + sb.Append(Alias); + else + sb.Append(db.DecorateName(ColumnName)); + + sb.AppendFormat(" {0}", Order.ToString().ToUpper()); + } + + #endregion ToSQL + } +} \ No newline at end of file diff --git a/DynamORM/DynamicCommand.cs b/DynamORM/DynamicCommand.cs new file mode 100644 index 0000000..80b6609 --- /dev/null +++ b/DynamORM/DynamicCommand.cs @@ -0,0 +1,205 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Data; + +namespace DynamORM +{ + /// Helper class to easy manage command. + public class DynamicCommand : IDbCommand + { + private IDbCommand _command; + private int? _commandTimeout = null; + private DynamicConnection _con; + private DynamicDatabase _db; + private long _poolStamp = 0; + + /// Initializes a new instance of the class. + /// The connection. + /// The databas manager. + internal DynamicCommand(DynamicConnection con, DynamicDatabase db) + { + _con = con; + _db = db; + + lock (_db.SyncLock) + { + if (!_db.CommandsPool.ContainsKey(_con.Connection)) + throw new InvalidOperationException("Can't create transaction using disposed connection."); + else + { + _command = _con.Connection.CreateCommand(); + _db.CommandsPool[_con.Connection].Add(_command); + } + } + } + + /// Prepare command for execution. + /// Returns edited instance. + private IDbCommand PrepareForExecution() + { + if (_poolStamp < _db.PoolStamp) + { + _command.CommandTimeout = _commandTimeout ?? _db.CommandTimeout ?? _command.CommandTimeout; + + if (_db.TransactionPool[_command.Connection].Count > 0) + _command.Transaction = _db.TransactionPool[_command.Connection].Peek(); + + _poolStamp = _db.PoolStamp; + } + + return _db.DumpCommands ? _command.Dump(Console.Out) : _command; + } + + #region IDbCommand Members + + /// + /// Attempts to cancels the execution of an . + /// + public void Cancel() + { + _command.Cancel(); + } + + /// + /// Gets or sets the text command to run against the data source. + /// + /// The text command to execute. The default value is an empty string (""). + public string CommandText { get { return _command.CommandText; } set { _command.CommandText = value; } } + + /// + /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error. + /// + /// The time (in seconds) to wait for the command to execute. The default value is 30 seconds. + /// The property value assigned is less than 0. + public int CommandTimeout { get { return _commandTimeout ?? _command.CommandTimeout; } set { _commandTimeout = value; } } + + /// Gets or sets how the property is interpreted. + public CommandType CommandType { get { return _command.CommandType; } set { _command.CommandType = value; } } + + /// Gets or sets the + /// used by this instance of the . + /// The connection to the data source. + public IDbConnection Connection { get { return _con; } set { _con = (DynamicConnection)value; } } + + /// Creates a new instance of an + /// object. + /// An object. + public IDbDataParameter CreateParameter() + { + return _command.CreateParameter(); + } + + /// Executes an SQL statement against the Connection object of a + /// data provider, and returns the number of rows affected. + /// The number of rows affected. + public int ExecuteNonQuery() + { + return PrepareForExecution().ExecuteNonQuery(); + } + + /// Executes the + /// against the , + /// and builds an using one + /// of the values. + /// One of the + /// values. + /// An object. + public IDataReader ExecuteReader(CommandBehavior behavior) + { + return PrepareForExecution().ExecuteReader(behavior); + } + + /// Executes the + /// against the and + /// builds an . + /// An object. + public IDataReader ExecuteReader() + { + return PrepareForExecution().ExecuteReader(); + } + + /// Executes the query, and returns the first column of the + /// first row in the resultset returned by the query. Extra columns or + /// rows are ignored. + /// The first column of the first row in the resultset. + public object ExecuteScalar() + { + return PrepareForExecution().ExecuteScalar(); + } + + /// Gets the . + public IDataParameterCollection Parameters + { + get { return _command.Parameters; } + } + + /// Creates a prepared (or compiled) version of the command on the data source. + public void Prepare() + { + _command.Prepare(); + } + + /// Gets or sets the transaction within which the Command + /// object of a data provider executes. + /// It's does nothing, transaction is peeked from transaction + /// pool of a connection. + public IDbTransaction Transaction { get { return null; } set { } } + + /// Gets or sets how command results are applied to the + /// when used by the + /// method of a . + /// One of the values. The default is + /// Both unless the command is automatically generated. Then the default is None. + /// The value entered was not one of the + /// values. + public UpdateRowSource UpdatedRowSource { get { return _command.UpdatedRowSource; } set { _command.UpdatedRowSource = value; } } + + #endregion IDbCommand Members + + #region IDisposable Members + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + lock (_db.SyncLock) + { + var pool = _db.CommandsPool.TryGetValue(_con.Connection); + + if (pool != null) + pool.Remove(_command); + + _command.Dispose(); + } + } + + #endregion IDisposable Members + } +} \ No newline at end of file diff --git a/DynamORM/DynamicConnection.cs b/DynamORM/DynamicConnection.cs new file mode 100644 index 0000000..00ae93c --- /dev/null +++ b/DynamORM/DynamicConnection.cs @@ -0,0 +1,153 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Data; + +namespace DynamORM +{ + /// Connection wrapper. + /// This class is only connection holder conection is managed by + /// instance. + public class DynamicConnection : IDbConnection, IDisposable + { + private DynamicDatabase _db; + private bool _singleTransaction; + + /// Gets underlaying connection. + internal IDbConnection Connection { get; private set; } + + /// Initializes a new instance of the class. + /// Database connection manager. + /// Active connection. + /// Are we using single transaction mode? I so... act correctly. + internal DynamicConnection(DynamicDatabase db, IDbConnection con, bool singleTransaction) + { + _db = db; + Connection = con; + _singleTransaction = singleTransaction; + } + + /// Begins a database transaction. + /// One of the values. + /// This action is invoked when transaction is disposed. + /// Returns representation. + internal DynamicTransaction BeginTransaction(IsolationLevel? il, Action disposed) + { + return new DynamicTransaction(_db, this, _singleTransaction, il, disposed); + } + + #region IDbConnection Members + + /// Creates and returns a Command object associated with the connection. + /// A Command object associated with the connection. + public IDbCommand CreateCommand() + { + return new DynamicCommand(this, _db); + } + + /// Begins a database transaction. + /// Returns representation. + public IDbTransaction BeginTransaction() + { + return BeginTransaction(null, null); + } + + /// Begins a database transaction with the specified + /// value. + /// One of the values. + /// Returns representation. + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return BeginTransaction(il, null); + } + + /// Changes the current database for an open Connection object. + /// The name of the database to use in place of the current database. + /// This operation is not supported in DynamORM. and will throw . + /// Thrown always. + public void ChangeDatabase(string databaseName) + { + throw new NotSupportedException("This operation is not supported in DynamORM."); + } + + /// Opens a database connection with the settings specified by + /// the ConnectionString property of the provider-specific + /// Connection object. + /// Does nothing. handles + /// opening connections. + public void Open() { } + + /// Closes the connection to the database. + /// Does nothing. handles + /// closing connections. Only way to close it is to dispose connection. + /// It will close if this is multi connection configuration, otherwise + /// it will stay open untill is not + /// disposed. + public void Close() { } + + /// Gets or sets the string used to open a database. + /// Changing connection string operation is not supported in DynamORM. + /// and will throw . + /// Thrown always when set is attempted. + public string ConnectionString + { + get { return Connection.ConnectionString; } + set { throw new NotSupportedException("This operation is not supported in DynamORM."); } + } + + /// Gets the time to wait while trying to establish a connection + /// before terminating the attempt and generating an error. + public int ConnectionTimeout + { + get { return Connection.ConnectionTimeout; } + } + + /// Gets the name of the current database or the database + /// to be used after a connection is opened. + public string Database + { + get { throw new NotImplementedException(); } + } + + /// Gets the current state of the connection. + public ConnectionState State + { + get { return Connection.State; } + } + + #endregion IDbConnection Members + + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + public void Dispose() + { + _db.Close(Connection); + } + } +} \ No newline at end of file diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs new file mode 100644 index 0000000..f513baa --- /dev/null +++ b/DynamORM/DynamicDatabase.cs @@ -0,0 +1,566 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using DynamORM.Mapper; + +namespace DynamORM +{ + /// Dynamic database is a class responsible for managing database. + public class DynamicDatabase : IDisposable + { + #region Internal fields and properties + + private DbProviderFactory _provider; + private string _connectionString; + private bool _singleConnection; + private bool _singleTransaction; + private string _leftDecorator = "\""; + private string _rightDecorator = "\""; + private string _parameterFormat = "@{0}"; + private int? _commandTimeout = null; + private long _poolStamp = 0; + + private DynamicConnection _tempConn = null; + + /// Provides lock object for this database instance. + internal readonly object SyncLock = new object(); + + /// Gets or sets timestamp of last transaction pool or configuration change. + /// This property is used to allow commands to determine if + /// they need to update transaction object or not. + internal long PoolStamp { get { return _poolStamp; } set { lock (SyncLock) _poolStamp = value; } } + + /// Gets pool of connections and transactions. + internal Dictionary> TransactionPool { get; private set; } + + /// Gets pool of connections and commands. + internal Dictionary> CommandsPool { get; private set; } + + /// Gets schema columns cache. + internal Dictionary> Schema { get; private set; } + + /// Gets tables cache for this database instance. + internal Dictionary TablesCache { get; private set; } + + #endregion Internal fields and properties + + #region Properties and Constructors + + /// Gets database options. + public DynamicDatabaseOptions Options { get; private set; } + + /// Gets or sets command timeout. + public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } } + + /// Gets or sets a value indicating whether + /// dump commands to console or not. + public bool DumpCommands { get; set; } + + /// Initializes a new instance of the class. + /// Database proider by name. + /// Connection string to provided database. + /// Connection options. + public DynamicDatabase(string provider, string connectionString, DynamicDatabaseOptions options) + : this(DbProviderFactories.GetFactory(provider), connectionString, options) + { + } + + /// Initializes a new instance of the class. + /// Database proider. + /// Connection string to provided database. + /// Connection options. + public DynamicDatabase(DbProviderFactory provider, string connectionString, DynamicDatabaseOptions options) + { + _provider = provider; + + InitCommon(connectionString, options); + } + + /// Initializes a new instance of the class. + /// Active database connection. + /// Connection options. required. + public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options) + { + InitCommon(connection.ConnectionString, options); + TransactionPool.Add(connection, new Stack()); + + if (!_singleConnection) + throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option."); + } + + private void InitCommon(string connectionString, DynamicDatabaseOptions options) + { + _connectionString = connectionString; + Options = options; + + _singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection; + _singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction; + + TransactionPool = new Dictionary>(); + CommandsPool = new Dictionary>(); + Schema = new Dictionary>(); + TablesCache = new Dictionary(); + } + + #endregion Properties and Constructors + + #region Table + + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// Table name. + /// Override keys in schema. + /// Instance of . + public dynamic Table(string table = "", string[] keys = null) + { + string key = string.Concat( + table == null ? string.Empty : table, + keys == null ? string.Empty : string.Join("_|_", keys)); + + DynamicTable dt = null; + lock (SyncLock) + dt = TablesCache.TryGetValue(key) ?? + TablesCache.AddAndPassValue(key, + new DynamicTable(this, table, keys)); + + return dt; + } + + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// Type used to determine table name. + /// Override keys in schema. + /// Instance of . + public dynamic Table(string[] keys = null) + { + Type table = typeof(T); + string key = string.Concat( + table.FullName, + keys == null ? string.Empty : string.Join("_|_", keys)); + + DynamicTable dt = null; + lock (SyncLock) + dt = TablesCache.TryGetValue(key) ?? + TablesCache.AddAndPassValue(key, + new DynamicTable(this, table, keys)); + + return dt; + } + + /// Removes cached table. + /// Disposed dynamic table. + internal void RemoveFromCache(DynamicTable dynamicTable) + { + foreach (var item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList()) + TablesCache.Remove(item.Key); + } + + #endregion Table + + #region Schema + + /// Builds table cache if nessesary and returns it. + /// Name of table for which build schema. + /// Table chema. + public Dictionary GetSchema(string table) + { + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(table.GetType().FullName) ?? + BuildAndCacheSchema(table, null); + + return schema; + } + + /// Builds table cache if nessesary and returns it. + /// Type of table for which build schema. + /// Table schema or null if type was anonymous. + public Dictionary GetSchema() + { + if (typeof(T).IsAnonymous()) + return null; + + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(typeof(T).GetType().FullName) ?? + BuildAndCacheSchema(null, DynamicMapperCache.GetMapper()); + + return schema; + } + + /// Builds table cache if nessesary and returns it. + /// Type of table for which build schema. + /// Table schema or null if type was anonymous. + public Dictionary GetSchema(Type table) + { + if (table == null || table.IsAnonymous() || table.IsValueType) + return null; + + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(table.FullName) ?? + BuildAndCacheSchema(null, DynamicMapperCache.GetMapper(table)); + + return schema; + } + + /// Get schema describing objects from reader. + /// Table from which extract column info. + /// List of objects . + /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. + protected virtual IEnumerable ReadSchema(string table) + { + using (var con = Open()) + using (var cmd = con.CreateCommand()) + { + using (var rdr = cmd + .SetCommand(string.Format("SELECT * FROM {0} WHERE 1 = 0", DecorateName(table))) + .ExecuteReader(CommandBehavior.SchemaOnly)) + foreach (DataRow col in rdr.GetSchemaTable().Rows) + { + var c = col.RowToDynamicUpper(); + + yield return new DynamicSchemaColumn + { + Name = c.COLUMNNAME, + Type = DynamicExtensions.TypeMap.TryGetNullable((Type)c.DATATYPE) ?? DbType.String, + IsKey = c.ISKEY ?? false, + IsUnique = c.ISUNIQUE ?? false, + Size = (int)(c.COLUMNSIZE ?? 0), + Precision = (byte)(c.NUMERICPRECISION ?? 0), + Scale = (byte)(c.NUMERICSCALE ?? 0) + }; + } + } + } + + private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper) + { + Dictionary schema = null; + + if (mapper != null) + tableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + bool databaseSchemaSupport = !string.IsNullOrEmpty(tableName) && + (Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + bool mapperSchema = mapper != null && mapper.Table != null && (mapper.Table.Override || !databaseSchemaSupport); + + #region Database schema + + if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower())) + { + schema = ReadSchema(tableName) + .ToDictionary(k => k.Name.ToLower(), k => k); + + Schema[tableName.ToLower()] = schema; + } + + #endregion Database schema + + #region Type schema + + if (mapperSchema && !Schema.ContainsKey(mapper.Type.FullName)) + { + // TODO: Ged rid of this monster below... + if (databaseSchemaSupport) + { + #region Merge with db schema + + schema = mapper.ColumnsMap.ToDictionary(k => k.Key, (v) => + { + DynamicSchemaColumn? col = Schema[tableName.ToLower()].TryGetNullable(v.Key); + + return new DynamicSchemaColumn + { + Name = DynamicExtensions.Coalesce( + v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, + col.HasValue && !string.IsNullOrEmpty(col.Value.Name) ? col.Value.Name : null, + v.Value.Name), + IsKey = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.IsKey : false, + col.HasValue ? col.Value.IsKey : false).Value, + Type = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Type : null, + col.HasValue ? col.Value.Type : DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.IsUnique : null, + col.HasValue ? col.Value.IsUnique : false).Value, + Size = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Size : null, + col.HasValue ? col.Value.Size : 0).Value, + Precision = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Precision : null, + col.HasValue ? col.Value.Precision : (byte)0).Value, + Scale = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Scale : null, + col.HasValue ? col.Value.Scale : (byte)0).Value, + }; + }); + + #endregion Merge with db schema + } + else + { + #region MapEnumerable based only on type + + schema = mapper.ColumnsMap.ToDictionary(k => k.Key, + v => new DynamicSchemaColumn + { + Name = DynamicExtensions.Coalesce(v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, v.Value.Name), + IsKey = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsKey : false, false).Value, + Type = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Type : null, DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value, + Size = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Size : null, 0).Value, + Precision = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Precision : null, 0).Value, + Scale = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Scale : null, 0).Value, + }); + + #endregion MapEnumerable based only on type + } + } + + if (mapper != null && schema != null) + Schema[mapper.Type.FullName] = schema; + + #endregion Type schema + + return schema; + } + + #endregion Schema + + #region Decorators + + /// Gets or sets left side decorator for database objects. + public string LeftDecorator { get { return _leftDecorator; } set { _leftDecorator = value; } } + + /// Gets or sets right side decorator for database objects. + public string RightDecorator { get { return _rightDecorator; } set { _rightDecorator = value; } } + + /// Gets or sets parameter name format. + public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } } + + /// Decorate string representing name of database object. + /// Name of database object. + /// Decorated name of database object. + public string DecorateName(string name) + { + return String.Concat(_leftDecorator, name, _rightDecorator); + } + + /// Decorate string representing name of database object. + /// String builder to which add decorated name. + /// Name of database object. + public void DecorateName(StringBuilder sb, string name) + { + sb.Append(_leftDecorator); + sb.Append(name); + sb.Append(_rightDecorator); + } + + /// Get database parameter name. + /// Friendly parameter name or number. + /// Formatted parameter name. + public string GetParameterName(object parameter) + { + return String.Format(_parameterFormat, parameter).Replace(" ", "_"); + } + + /// Get database parameter name. + /// String builder to which add parameter name. + /// Friendly parameter name or number. + public void GetParameterName(StringBuilder sb, object parameter) + { + sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_")); + } + + #endregion Decorators + + #region Connection + + /// Open managed connection. + /// Opened connection. + public IDbConnection Open() + { + IDbConnection conn = null; + DynamicConnection ret = null; + + lock (SyncLock) + { + if (_tempConn == null) + { + if (TransactionPool.Count == 0 || !_singleConnection) + { + conn = _provider.CreateConnection(); + conn.ConnectionString = _connectionString; + conn.Open(); + + TransactionPool.Add(conn, new Stack()); + CommandsPool.Add(conn, new List()); + } + else + { + conn = TransactionPool.Keys.First(); + + if (conn.State != ConnectionState.Open) + conn.Open(); + } + + ret = new DynamicConnection(this, conn, _singleTransaction); + } + else + ret = _tempConn; + } + + return ret; + } + + /// Close connection if we are allowed to. + /// Connection to manage. + internal void Close(IDbConnection connection) + { + if (connection == null) + return; + + lock (SyncLock) + { + if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection)) + { + // Close all commands + if (CommandsPool.ContainsKey(connection)) + { + CommandsPool[connection].ForEach(cmd => cmd.Dispose()); + CommandsPool[connection].Clear(); + } + + // Rollback remaining transactions + while (TransactionPool[connection].Count > 0) + { + IDbTransaction trans = TransactionPool[connection].Pop(); + trans.Rollback(); + trans.Dispose(); + } + + // Close connection + if (connection.State == ConnectionState.Open) + connection.Close(); + + // remove from pools + TransactionPool.Remove(connection); + CommandsPool.Remove(connection); + + // Set stamp + _poolStamp = DateTime.Now.Ticks; + + // Dispose the corpse + connection.Dispose(); + } + } + } + + #endregion Connection + + #region Transaction + + /// Begins a global database transaction. + /// Using this method connection is set to single open + /// connection untill all transactions are finished. + /// Returns representation. + public IDbTransaction BeginTransaction() + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(null, () => + { + var t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + + #endregion Transaction + + #region IDisposable Members + + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + public void Dispose() + { + lock (SyncLock) + { + var tables = TablesCache.Values.ToList(); + TablesCache.Clear(); + + tables.ForEach(t => t.Dispose()); + + foreach (var con in TransactionPool) + { + // Close all commands + if (CommandsPool.ContainsKey(con.Key)) + { + CommandsPool[con.Key].ForEach(cmd => cmd.Dispose()); + CommandsPool[con.Key].Clear(); + } + + // Rollback remaining transactions + while (con.Value.Count > 0) + { + IDbTransaction trans = con.Value.Pop(); + trans.Rollback(); + trans.Dispose(); + } + + // Close connection + if (con.Key.State == ConnectionState.Open) + con.Key.Close(); + + // Dispose it + con.Key.Dispose(); + } + + // Clear pools + TransactionPool.Clear(); + CommandsPool.Clear(); + } + } + + #endregion IDisposable Members + } +} \ No newline at end of file diff --git a/DynamORM/DynamicDatabaseOptions.cs b/DynamORM/DynamicDatabaseOptions.cs new file mode 100644 index 0000000..5369e5d --- /dev/null +++ b/DynamORM/DynamicDatabaseOptions.cs @@ -0,0 +1,58 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM +{ + /// Represents database connection options. + [Flags] + public enum DynamicDatabaseOptions + { + /// No specific options. + None, + + /// Only single presistent database connection. + SingleConnection, + + /// Only one transaction. + SingleTransaction, + + /// Database supports top syntax. + SupportTop, + + /// Database supports limit offset syntax. + SupportLimitOffset, + + /// Database support standard schema. + SupportSchema, + + /// Database support stored procedures. + SupportStoredProcedures + } +} \ No newline at end of file diff --git a/DynamORM/DynamicExtensions.cs b/DynamORM/DynamicExtensions.cs new file mode 100644 index 0000000..694e778 --- /dev/null +++ b/DynamORM/DynamicExtensions.cs @@ -0,0 +1,1052 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Data; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using DynamORM.Builders; +using DynamORM.Mapper; + +namespace DynamORM +{ + /// Extension to ORM objects. + public static class DynamicExtensions + { + #region Type column map + + /// MapEnumerable of .NET types to DbType's. + public static readonly Dictionary TypeMap = new Dictionary() + { + { typeof(byte), DbType.Byte }, + { typeof(sbyte), DbType.SByte }, + { typeof(short), DbType.Int16 }, + { typeof(ushort), DbType.UInt16 }, + { typeof(int), DbType.Int32 }, + { typeof(uint), DbType.UInt32 }, + { typeof(long), DbType.Int64 }, + { typeof(ulong), DbType.UInt64 }, + { typeof(float), DbType.Single }, + { typeof(double), DbType.Double }, + { typeof(decimal), DbType.Decimal }, + { typeof(bool), DbType.Boolean }, + { typeof(string), DbType.String }, + { typeof(char), DbType.StringFixedLength }, + { typeof(Guid), DbType.Guid }, + { typeof(DateTime), DbType.DateTime }, + { typeof(DateTimeOffset), DbType.DateTimeOffset }, + { typeof(byte[]), DbType.Binary }, + { typeof(byte?), DbType.Byte }, + { typeof(sbyte?), DbType.SByte }, + { typeof(short?), DbType.Int16 }, + { typeof(ushort?), DbType.UInt16 }, + { typeof(int?), DbType.Int32 }, + { typeof(uint?), DbType.UInt32 }, + { typeof(long?), DbType.Int64 }, + { typeof(ulong?), DbType.UInt64 }, + { typeof(float?), DbType.Single }, + { typeof(double?), DbType.Double }, + { typeof(decimal?), DbType.Decimal }, + { typeof(bool?), DbType.Boolean }, + { typeof(char?), DbType.StringFixedLength }, + { typeof(Guid?), DbType.Guid }, + { typeof(DateTime?), DbType.DateTime }, + { typeof(DateTimeOffset?), DbType.DateTimeOffset } + }; + + #endregion Type column map + + #region Command extensions + + /// Set connection on the fly. + /// in which changes will be made. + /// which will be set to instance. + /// Returns edited instance. + public static IDbCommand SetConnection(this IDbCommand command, IDbConnection connection) + { + command.Connection = connection; + + return command; + } + + /// Set connection on the fly. + /// in which changes will be made. + /// which will be set to instance. + /// Returns edited instance. + public static IDbCommand SetTransaction(this IDbCommand command, IDbTransaction transaction) + { + command.Transaction = transaction; + + return command; + } + + #region SetCommand + + /// Set properties on the fly. + /// in which changes will be made. + /// Indicates or specifies how the System.Data.IDbCommand.CommandText property is interpreted. + /// The wait time before terminating the attempt to execute a command and generating an error. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, int commandTimeout, string commandText, params object[] args) + { + command.CommandType = commandType; + command.CommandTimeout = commandTimeout; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + + /// Set properties on the fly. + /// in which changes will be made. + /// The wait time before terminating the attempt to execute a command and generating an error. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, int commandTimeout, string commandText, params object[] args) + { + command.CommandTimeout = commandTimeout; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + + /// Set properties on the fly. + /// in which changes will be made. + /// Indicates or specifies how the System.Data.IDbCommand.CommandText property is interpreted. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, string commandText, params object[] args) + { + command.CommandType = commandType; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + + /// Set properties on the fly. + /// in which changes will be made. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, string commandText, params object[] args) + { + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + + /// Set properties on the fly. + /// in which changes will be made. + /// Command builder. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, IDynamicQueryBuilder builder) + { + builder.FillCommand(command); + + return command; + } + + #endregion SetCommand + + #region AddParameter + + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, params object[] args) + { + if (args != null && args.Count() > 0) + foreach (var item in args) + cmd.AddParameter(database, item); + + return cmd; + } + + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, object item) + { + var p = cmd.CreateParameter(); + p.ParameterName = database.GetParameterName(cmd.Parameters.Count); + + if (item == null) + p.Value = DBNull.Value; + else + { + Type type = item.GetType(); + + p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String; + + if (type == typeof(ExpandoObject)) + p.Value = ((IDictionary)item).Values.FirstOrDefault(); + else + p.Value = item; + + if (p.DbType == DbType.String) + p.Size = item.ToString().Length > 4000 ? -1 : 4000; + } + + cmd.Parameters.Add(p); + + return cmd; + } + + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Query builder containing schema. + /// Column item to add. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item) + { + var p = cmd.CreateParameter(); + p.ParameterName = builder.DynamicTable.Database.GetParameterName(cmd.Parameters.Count); + + if (item.Value == null) + p.Value = DBNull.Value; + else + { + var col = builder.Schema.TryGetNullable(item.ColumnName.ToLower()); + + if (col.HasValue) + { + p.DbType = col.Value.Type; + + if (builder.SupportSchema) + { + p.Size = col.Value.Size; + p.Precision = col.Value.Precision; + p.Scale = col.Value.Scale; + } + } + else + { + p.DbType = TypeMap.TryGetNullable(item.Value.GetType()) ?? DbType.String; + + if (p.DbType == DbType.String) + p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000; + } + + p.Value = item.Value; + } + + cmd.Parameters.Add(p); + + return cmd; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Precision = precision; + param.Scale = scale; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Precision = precision; + param.Scale = scale; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The size of the parameter. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Size = size; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Value = value; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The size of the parameter. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Size = size; + command.Parameters.Add(param); + + return command; + } + + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + command.Parameters.Add(param); + + return command; + } + + #endregion AddParameter + + #region SetParameter + + /// Set value for on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value to set on this parameter. + /// Returns edited instance. + public static IDbCommand SetParameter(this IDbCommand command, string parameterName, object value) + { + ((IDbDataParameter)command.Parameters[parameterName]).Value = value; + + return command; + } + + /// Set value for on the fly. + /// to which parameter will be added. + /// Index of the . + /// Value to set on this parameter. + /// Returns edited instance. + public static IDbCommand SetParameter(this IDbCommand command, int index, object value) + { + ((IDbDataParameter)command.Parameters[index]).Value = value; + + return command; + } + + #endregion SetParameter + + #region Generic Execution + + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command) + { + return ExecuteScalarAs(command, default(T), null); + } + + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Handler of a try parse method. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, DynamicExtensions.TryParseHandler handler) + { + return ExecuteScalarAs(command, default(T), handler); + } + + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, T defaultValue) + { + return ExecuteScalarAs(command, defaultValue, null); + } + + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Handler of a try parse method. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) + { + T ret = defaultValue; + + object o = command.ExecuteScalar(); + + if (o is T) + return (T)o; + else if (o != DBNull.Value && o != null) + { + var method = typeof(T).GetMethod( + "TryParse", + new[] + { + typeof(string), + Type.GetType(string.Format("{0}&", typeof(T).FullName)) + }); + + if (handler != null) + ret = o.ToString().TryParseDefault(defaultValue, handler); + else if (method != null) + ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) + { + r = defaultValue; + return (bool)method.Invoke(null, new object[] { v, r }); + }); + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + else + throw new InvalidOperationException("Provided type can't be parsed using generic approach."); + } + + return ret; + } + + /// Execute enumerator of specified type. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Handler of a try parse method. + /// Returns enumerator of specified type from query. + public static IEnumerable ExecuteEnumeratorOf(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) where T : struct + { + using (IDataReader reader = command.ExecuteReader()) + { + var method = typeof(T).GetMethod( + "TryParse", + new[] + { + typeof(string), + Type.GetType(string.Format("{0}&", typeof(T).FullName)) + }); + + while (reader.Read()) + { + T ret = defaultValue; + + if (!reader.IsDBNull(0)) + { + object o = reader.GetValue(0); + + if (o is T) + ret = (T)o; + else if (o != DBNull.Value) + { + if (handler != null) + ret = o.ToString().TryParseDefault(defaultValue, handler); + else if (method != null) + ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) + { + r = defaultValue; + return (bool)method.Invoke(null, new object[] { v, r }); + }); + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + else + throw new InvalidOperationException("Provided type can't be parsed using generic approach."); + } + } + + yield return ret; + } + } + } + + #endregion Generic Execution + + /// Dump command into text writer. + /// Command to dump. + /// Writer to which write output. + /// Returns dumped instance. + public static IDbCommand Dump(this IDbCommand command, TextWriter writer) + { + writer.WriteLine("Type: {0}; Timeout: {1}; Query: {2}", command.CommandType, command.CommandTimeout, command.CommandText); + + if (command.Parameters.Count > 0) + { + writer.Write("Parameters:"); + + foreach (IDbDataParameter param in command.Parameters) + { + writer.Write(" '{0}'({1}) = '{2}'({3});", + param.ParameterName, param.DbType, + param.Value ?? "NULL", + param.Value != null ? param.Value.GetType().Name : "DBNull"); + } + + writer.WriteLine(); + } + + return command; + } + + #endregion Command extensions + + #region Dynamic extensions + + /// Turns an to a Dynamic list of things. + /// Reader from which read data.. + /// List of things. + public static List ToDynamicList(this IDataReader r) + { + var result = new List(); + + while (r.Read()) + result.Add(r.RowToDynamic()); + + return result; + } + + /// Turns the object into an ExpandoObject. + /// Object to convert. + /// Converted object. + public static dynamic ToDynamic(this object o) + { + var result = new ExpandoObject(); + var dict = result as IDictionary; + + if (o.GetType() == typeof(ExpandoObject)) + return o; + if (o.GetType() == typeof(NameValueCollection) || o.GetType().IsSubclassOf(typeof(NameValueCollection))) + { + var nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + var mapper = DynamicMapperCache.GetMapper(o.GetType()); + + if (mapper != null) + { + foreach (var item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + var props = o.GetType().GetProperties(); + + foreach (var item in props) + if (item.CanRead) + dict.Add(item.Name, item.GetValue(o, null)); + } + } + + return result; + } + + /// Convert data row row into dynamic object. + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToDynamic(this DataRow r) + { + dynamic e = new ExpandoObject(); + var d = e as IDictionary; + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + d.Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + + return e; + } + + /// Convert data row row into dynamic object (upper case key). + /// DataRow from which read. + /// Generated dynamic object. + internal static dynamic RowToDynamicUpper(this DataRow r) + { + dynamic e = new ExpandoObject(); + var d = e as IDictionary; + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + d.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + + /// Convert reader row into dynamic object. + /// Reader from which read. + /// Generated dynamic object. + public static dynamic RowToDynamic(this IDataReader r) + { + dynamic e = new ExpandoObject(); + var d = e as IDictionary; + + int c = r.FieldCount; + for (int i = 0; i < c; i++) + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + + return e; + } + + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this ExpandoObject o) + { + return (IDictionary)o; + } + + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this object o) + { + return (IDictionary)o.ToDynamic(); + } + + #endregion Dynamic extensions + + #region Type extensions + + /// Check if type is anonymous. + /// Type to test. + /// Returns true if type is anonymous. + public static bool IsAnonymous(this Type type) + { + // HACK: The only way to detect anonymous types right now. + return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType + && (type.Name.Contains("AnonymousType") || type.Name.Contains("AnonType")) + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } + + /// Check if type implements IEnumerable<> interface. + /// Type to check. + /// Returns true if it does. + public static bool IsGenericEnumerable(this Type type) + { + return type.IsGenericType && + typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition()); + } + + /// Check if type is collection of any kind. + /// Type to check. + /// Returns true if it is. + public static bool IsCollection(this Type type) + { + if (!type.IsArray) + return type.IsGenericEnumerable(); + + return true; + } + + /// Check if type is collection of value types like int. + /// Type to check. + /// Returns true if it is. + public static bool IsCollectionOfValueTypes(this Type type) + { + if (type.IsArray) + return type.GetElementType().IsValueType; + else + { + if (type.IsGenericType) + { + Type[] gt = type.GetGenericArguments(); + + return (gt.Length == 1) && gt[0].IsValueType; + } + } + + return false; + } + + #endregion Type extensions + + #region IDictionary extensions + + /// Gets the value associated with the specified key. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to probe. + /// The key whose value to get. + /// Nullable type containing value or null if key was not found. + public static TValue? TryGetNullable(this IDictionary dict, TKey key) where TValue : struct + { + TValue val; + + if (dict.TryGetValue(key, out val)) + return val; + + return null; + } + + /// Gets the value associated with the specified key. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to probe. + /// The key whose value to get. + /// Instance of object or null if not found. + public static TValue TryGetValue(this IDictionary dict, TKey key) where TValue : class + { + TValue val; + + if (dict.TryGetValue(key, out val)) + return val; + + return default(TValue); + } + + /// Adds element to dictionary and returns added value. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to which add value. + /// The key under which value value will be added. + /// Value to add. + /// Instance of object or null if not found. + public static TValue AddAndPassValue(this IDictionary dict, TKey key, TValue value) + { + dict.Add(key, value); + return value; + } + + #endregion IDictionary extensions + + #region Mapper extensions + + /// MapEnumerable object enumerator into specified type. + /// Type to which columnMap results. + /// Source enumerator. + /// Enumerator of specified type. + public static IEnumerable MapEnumerable(this IEnumerable enumerable) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (var item in enumerable) + yield return (T)mapper.Create(item); + } + + /// MapEnumerable object item into specified type. + /// Type to which columnMap results. + /// Source object. + /// Item of specified type. + public static T Map(this object item) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return (T)mapper.Create(item); + } + + /// Fill object of speciied type with data from source object. + /// Type to which columnMap results. + /// Item to which columnMap data. + /// Item from which extract data. + /// Filled item. + public static T Fill(this T item, object source) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + mapper.Map(item, source); + + return item; + } + + /// MapEnumerable object enumerator into specified type. + /// Source enumerator. + /// Type to which columnMap results. + /// Enumerator of specified type. + public static IEnumerable MapEnumerable(this IEnumerable enumerable, Type type) + { + var mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (var item in enumerable) + yield return mapper.Create(item); + } + + /// MapEnumerable object item into specified type. + /// Source object. + /// Type to which columnMap results. + /// Item of specified type. + public static object Map(this object item, Type type) + { + var mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return mapper.Create(item); + } + + #endregion Mapper extensions + + #region TryParse extensions + + /// Generic try parse. + /// Type to parse to. + /// Value to parse. + /// Handler of a try parse method. + /// Returns true if conversion was sucessfull. + public static T? TryParse(this string value, TryParseHandler handler) where T : struct + { + if (String.IsNullOrEmpty(value)) + return null; + + T result; + + if (handler(value, out result)) + return result; + + return null; + } + + /// Generic try parse with default value. + /// Type to parse to. + /// Value to parse. + /// Default value of a result. + /// Handler of a try parse method. + /// Returns true if conversion was sucessfull. + public static T TryParseDefault(this string value, T defaultValue, TryParseHandler handler) + { + if (String.IsNullOrEmpty(value)) + return defaultValue; + + T result; + + if (handler(value, out result)) + return result; + + return defaultValue; + } + + /// Delegate fro try parse function of a type. + /// Type which implements this function. + /// Value to parse. + /// Resulting value. + /// Returns true if conversion was sucessfull. + public delegate bool TryParseHandler(string value, out T result); + + #endregion TryParse extensions + + #region Coalesce - besicaly not an extensions + + /// Select first not null value. + /// Type to return. + /// Values to check. + /// First not null or default value. + public static T Coalesce(params T[] vals) where T : class + { + return vals.FirstOrDefault(v => v != null); + } + + /// Select first not null value. + /// Type to return. + /// Values to check. + /// First not null or default value. + public static T? CoalesceNullable(params T?[] vals) where T : struct + { + return vals.FirstOrDefault(v => v != null); + } + + #endregion Coalesce - besicaly not an extensions + } +} \ No newline at end of file diff --git a/DynamORM/DynamicSchemaColumn.cs b/DynamORM/DynamicSchemaColumn.cs new file mode 100644 index 0000000..9f29d47 --- /dev/null +++ b/DynamORM/DynamicSchemaColumn.cs @@ -0,0 +1,57 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Data; + +namespace DynamORM +{ + /// Stores information about column from database schema. + public struct DynamicSchemaColumn + { + /// Gets or sets column name. + public string Name { get; set; } + + /// Gets or sets column type. + public DbType Type { get; set; } + + /// Gets or sets a value indicating whether column is a key. + public bool IsKey { get; set; } + + /// Gets or sets a value indicating whether column should have unique value. + public bool IsUnique { get; set; } + + /// Gets or sets column size. + public int Size { get; set; } + + /// Gets or sets column precision. + public byte Precision { get; set; } + + /// Gets or sets column scale. + public byte Scale { get; set; } + } +} \ No newline at end of file diff --git a/DynamORM/DynamicTable.cs b/DynamORM/DynamicTable.cs new file mode 100644 index 0000000..cf7a010 --- /dev/null +++ b/DynamORM/DynamicTable.cs @@ -0,0 +1,786 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using DynamORM.Builders; +using DynamORM.Helpers; +using DynamORM.Mapper; + +namespace DynamORM +{ + /// Dynamic table is a simple ORM using dynamic objects. + /// + /// Assume that we have a table representing Users class. + /// + /// Let's take a look at Query posibilities. Assume we want + /// to get enumerator for all records in database, mapped to our class + /// instead of dynamic type we can use following syntax. + /// Approach first. Use dynamic Query method and just set type + /// then just cast it to user class. Remember that you must cast result + /// of Queryto IEnumerable<object>. because from + /// point of view of runtime you are operating on object type. + /// (db.Table<User>().Query(type: typeof(User)) as IEnumerable<object>).Cast<User>(); + /// Second approach is similar. We ask database using dynamic + /// Query method. The difference is that we use extension method of + /// IEnumerable<object> (to which we must cast to) to map + /// object. + /// (db.Table<User>().Query(columns: "*") as IEnumerable<object>).MapEnumerable<User>(); + /// You can also use generic approach. But be careful this method is currently avaliable thanks to framework hack. + /// (db.Table<User>().Query<User>() as IEnumerable<object>).Cast<User>() + /// Another approach uses existing methods, but still requires a + /// cast, because Query also returns dynamic object enumerator. + /// (db.Table<User>().Query().Execute() as IEnumerable<object>).MapEnumerable<User>(); + /// + /// + public class DynamicTable : DynamicObject, IDisposable, ICloneable + { + private static HashSet _allowedCommands = new HashSet + { + "Insert", "Update", "Delete", + "Query", "Single", "Where", + "First", "Last", "Get", + "Count", "Sum", "Avg", + "Min", "Max", "Scalar" + }; + + /// Gets dynamic database. + internal DynamicDatabase Database { get; private set; } + + /// Gets type of table (for coning and schema building). + internal Type TableType { get; private set; } + + /// Gets name of table. + public virtual string TableName { get; private set; } + + /// Gets table schema. + /// If database doesn't support schema, only key columns are listed here. + public virtual Dictionary Schema { get; private set; } + + private DynamicTable() { } + + /// Initializes a new instance of the class. + /// Database and connection management. + /// Table name. + /// Override keys in schema. + public DynamicTable(DynamicDatabase database, string table = "", string[] keys = null) + { + Database = database; + TableName = table; + TableType = null; + + BuildAndCacheSchema(keys); + } + + /// Initializes a new instance of the class. + /// Database and connection management. + /// Type describing table. + /// Override keys in schema. + public DynamicTable(DynamicDatabase database, Type type, string[] keys = null) + { + if (type == null) + throw new ArgumentNullException("type", "Type can't be null."); + + Database = database; + + TableType = type; + + var mapper = DynamicMapperCache.GetMapper(type); + + if (mapper != null) + TableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + type.Name : mapper.Table.Name; + + BuildAndCacheSchema(keys); + } + + #region Schema + + private void BuildAndCacheSchema(string[] keys) + { + Dictionary schema = null; + + schema = Database.GetSchema(TableType) ?? + Database.GetSchema(TableName); + + #region Fill currrent table schema + + if (keys == null && TableType != null) + { + var mapper = DynamicMapperCache.GetMapper(TableType); + + if (mapper != null) + { + var k = mapper.ColumnsMap.Where(p => p.Value.Column != null && p.Value.Column.IsKey).Select(p => p.Key); + if (k.Count() > 0) + keys = k.ToArray(); + } + } + + if (schema != null) + { + if (keys == null) + Schema = new Dictionary(schema); + else + { + // TODO: Make this.... nicer + List ks = keys.Select(k => k.ToLower()).ToList(); + + Schema = schema.ToDictionary(k => k.Key, (v) => + { + DynamicSchemaColumn dsc = v.Value; + dsc.IsKey = ks.Contains(v.Key); + return dsc; + }); + } + } + + #endregion Fill currrent table schema + + #region Build ad-hock schema + + if (keys != null && Schema == null) + { + Schema = keys.Select(k => k.ToLower()).ToList() + .ToDictionary(k => k, k => new DynamicSchemaColumn { Name = k, IsKey = true }); + } + + #endregion Build ad-hock schema + } + + #endregion Schema + + #region Basic Queries + + /// Enumerate the reader and yield the result. + /// Sql query containing numered parameters in format provided by + /// methods. Also names should be formated with + /// method. + /// Arguments (parameters). + /// Enumerator of objects expanded from query. + public virtual IEnumerable Query(string sql, params object[] args) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + using (var rdr = cmd + .SetCommand(sql) + .AddParameters(Database, args) + .ExecuteReader()) + while (rdr.Read()) + yield return rdr.RowToDynamic(); + } + } + + /// Enumerate the reader and yield the result. + /// Command builder. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Query(IDynamicQueryBuilder builder) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + using (var rdr = cmd + .SetCommand(builder) + .ExecuteReader()) + while (rdr.Read()) + yield return rdr.RowToDynamic(); + } + } + + /// Create new . + /// New instance. + public virtual DynamicSelectQueryBuilder Query() + { + return new DynamicSelectQueryBuilder(this); + } + + /// Returns a single result. + /// Sql query containing numered parameters in format provided by + /// methods. Also names should be formated with + /// method. + /// Arguments (parameters). + /// Result of a query. + public virtual object Scalar(string sql, params object[] args) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(Database, args) + .ExecuteScalar(); + } + } + + /// Returns a single result. + /// Command builder. + /// Result of a query. + public virtual object Scalar(IDynamicQueryBuilder builder) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteScalar(); + } + } + + /// Execute non query. + /// Sql query containing numered parameters in format provided by + /// methods. Also names should be formated with + /// method. + /// Arguments (parameters). + /// Number of affected rows. + public virtual int Execute(string sql, params object[] args) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(Database, args) + .ExecuteNonQuery(); + } + } + + /// Execute non query. + /// Command builder. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder builder) + { + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteNonQuery(); + } + } + + /// Execute non query. + /// Command builders. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder[] builers) + { + int ret = 0; + + using (var con = Database.Open()) + { + using (var trans = con.BeginTransaction()) + { + foreach (var builder in builers) + { + using (var cmd = con.CreateCommand()) + { + ret += cmd + .SetCommand(builder) + .ExecuteNonQuery(); + } + } + + trans.Commit(); + } + } + + return ret; + } + + #endregion Basic Queries + + #region Insert + + /// Create new . + /// New instance. + public DynamicInsertQueryBuilder Insert() + { + return new DynamicInsertQueryBuilder(this); + } + + /// Adds a record to the database. You can pass in an Anonymous object, an ExpandoObject, + /// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString. + /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection + /// from a Request.Form or Request.QueryString, containing fields to update. + /// Number of updated rows. + public virtual int Insert(object o) + { + return Insert() + .Insert(o) + .Execute(); + } + + #endregion Insert + + #region Update + + /// Create new . + /// New instance. + public DynamicUpdateQueryBuilder Update() + { + return new DynamicUpdateQueryBuilder(this); + } + + /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject, + /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. + /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection + /// from a Request.Form or Request.QueryString, containing fields to update. + /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection + /// from a Request.Form or Request.QueryString, containing fields with conditions. + /// Number of updated rows. + public virtual int Update(object o, object key) + { + return Update() + .Values(o) + .Where(key) + .Execute(); + } + + /// Updates a record in the database using schema. You can pass in an Anonymous object, an ExpandoObject, + /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. + /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection + /// from a Request.Form or Request.QueryString, containing fields to update and conditions. + /// Number of updated rows. + public virtual int Update(object o) + { + return Update() + .Update(o) + .Execute(); + } + + #endregion Update + + #region Delete + + /// Create new . + /// New instance. + public DynamicDeleteQueryBuilder Delete() + { + return new DynamicDeleteQueryBuilder(this); + } + + /// Removes a record from the database. You can pass in an Anonymous object, an ExpandoObject, + /// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString. + /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection + /// from a Request.Form or Request.QueryString, containing fields with where conditions. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Number of updated rows. + public virtual int Delete(object o, bool schema = true) + { + return Delete() + .Where(o, schema) + .Execute(); + } + + #endregion Delete + + #region Universal Dynamic Invoker + + /// This is where the magic begins. + /// Binder to invoke. + /// Binder arguments. + /// Binder invoke result. + /// Returns true if invoke was performed. + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + // parse the method + var info = binder.CallInfo; + + // Get generic types + var types = binder.GetGenericTypeArguments(); + + // accepting named args only... SKEET! + if (info.ArgumentNames.Count != args.Length) + throw new InvalidOperationException("Please use named arguments for this type of query - the column name, orderby, columns, etc"); + + var op = binder.Name; + + // Avoid strange things + if (!_allowedCommands.Contains(op)) + throw new InvalidOperationException(string.Format("Dynamic method '{0}' is not supported.", op)); + + switch (op) + { + case "Insert": + result = DynamicInsert(args, info, types); + break; + case "Update": + result = DynamicUpdate(args, info, types); + break; + case "Delete": + result = DynamicDelete(args, info, types); + break; + default: + result = DynamicQuery(args, info, op, types); + break; + } + + return true; + } + + private object DynamicInsert(object[] args, CallInfo info, IList types) + { + var builder = new DynamicInsertQueryBuilder(this); + + if (types != null && types.Count == 1) + HandleTypeArgument(null, info, ref types, builder, 0); + + // loop the named args - see if we have order, columns and constraints + if (info.ArgumentNames.Count > 0) + { + for (int i = 0; i < args.Length; i++) + { + var name = info.ArgumentNames[i].ToLower(); + + switch (name) + { + case "table": + if (args[i] is string) + builder.Table(args[i].ToString()); + else goto default; + break; + case "values": + builder.Insert(args[i]); + break; + case "type": + if (types == null || types.Count == 0) + HandleTypeArgument(args, info, ref types, builder, i); + else goto default; + break; + default: + builder.Insert(name, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicUpdate(object[] args, CallInfo info, IList types) + { + var builder = new DynamicUpdateQueryBuilder(this); + + if (types != null && types.Count == 1) + HandleTypeArgument(null, info, ref types, builder, 0); + + // loop the named args - see if we have order, columns and constraints + if (info.ArgumentNames.Count > 0) + { + for (int i = 0; i < args.Length; i++) + { + var name = info.ArgumentNames[i].ToLower(); + + switch (name) + { + case "table": + if (args[i] is string) + builder.Table(args[i].ToString()); + else goto default; + break; + case "update": + builder.Update(args[i]); + break; + case "values": + builder.Values(args[i]); + break; + case "where": + builder.Where(args[i]); + break; + case "type": + if (types == null || types.Count == 0) + HandleTypeArgument(args, info, ref types, builder, i); + else goto default; + break; + default: + builder.Update(name, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicDelete(object[] args, CallInfo info, IList types) + { + var builder = new DynamicDeleteQueryBuilder(this); + + if (types != null && types.Count == 1) + HandleTypeArgument(null, info, ref types, builder, 0); + + // loop the named args - see if we have order, columns and constraints + if (info.ArgumentNames.Count > 0) + { + for (int i = 0; i < args.Length; i++) + { + var name = info.ArgumentNames[i].ToLower(); + + switch (name) + { + case "table": + if (args[i] is string) + builder.Table(args[i].ToString()); + else goto default; + break; + case "where": + builder.Where(args[i], false); + break; + case "delete": + builder.Where(args[i], true); + break; + case "type": + if (types == null || types.Count == 0) + HandleTypeArgument(args, info, ref types, builder, i); + else goto default; + break; + default: + builder.Where(name, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicQuery(object[] args, CallInfo info, string op, IList types) + { + object result; + var builder = new DynamicSelectQueryBuilder(this); + + if (types != null && types.Count == 1) + HandleTypeArgument(null, info, ref types, builder, 0); + + // loop the named args - see if we have order, columns and constraints + if (info.ArgumentNames.Count > 0) + { + for (int i = 0; i < args.Length; i++) + { + var name = info.ArgumentNames[i].ToLower(); + + // TODO: Make this nicer + switch (name) + { + case "order": + if (args[i] is string) + builder.OrderBy(((string)args[i]).Split(',')); + else if (args[i] is string[]) + builder.OrderBy(args[i] as string); + else if (args[i] is DynamicColumn[]) + builder.OrderBy((DynamicColumn[])args[i]); + else if (args[i] is DynamicColumn) + builder.OrderBy((DynamicColumn)args[i]); + else goto default; + break; + case "group": + if (args[i] is string) + builder.GroupBy(((string)args[i]).Split(',')); + else if (args[i] is string[]) + builder.GroupBy(args[i] as string); + else if (args[i] is DynamicColumn[]) + builder.GroupBy((DynamicColumn[])args[i]); + else if (args[i] is DynamicColumn) + builder.GroupBy((DynamicColumn)args[i]); + else goto default; + break; + case "columns": + if (args[i] is string) + builder.Select(((string)args[i]).Split(',')); + else if (args[i] is string[]) + builder.Select(args[i] as string); + else if (args[i] is DynamicColumn[]) + builder.Select((DynamicColumn[])args[i]); + else if (args[i] is DynamicColumn) + builder.Select((DynamicColumn)args[i]); + else goto default; + break; + case "where": + builder.Where(args[i]); + break; + case "table": + if (args[i] is string) + builder.Table(args[i].ToString()); + else goto default; + break; + case "type": + if (types == null || types.Count == 0) + HandleTypeArgument(args, info, ref types, builder, i); + else goto default; + break; + default: + builder.Where(name, args[i]); + break; + } + } + } + + if (op == "Count" && builder.Columns.Count == 0) + { + result = Scalar(builder.Select(new DynamicColumn + { + ColumnName = "*", + Aggregate = op.ToUpper(), + Alias = "Count" + })); + + if (result is long) + result = (int)(long)result; + } + else if (op == "Sum" || op == "Max" || + op == "Min" || op == "Avg" || op == "Count") + { + if (builder.Columns.Count == 0) + throw new InvalidOperationException("You must select at least one column to agregate."); + + foreach (var o in builder.Columns) + o.Aggregate = op.ToUpper(); + + if (builder.Columns.Count == 1) + { + result = Scalar(builder); + + if (op == "Count" && result is long) + result = (int)(long)result; + } + else + { + result = Query(builder).FirstOrDefault(); // return lots + } + } + else + { + // build the SQL + var justOne = op == "First" || op == "Last" || op == "Get" || op == "Single"; + + // Be sure to sort by DESC on selected columns + if (op == "Last") + { + if (builder.Order.Count > 0) + foreach (var o in builder.Order) + o.Order = o.Order == DynamicColumn.SortOrder.Desc ? + DynamicColumn.SortOrder.Asc : DynamicColumn.SortOrder.Desc; + } + + if (justOne && !(op == "Last" && builder.Order.Count == 0)) + { + if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) + builder.Limit(1); + else if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) + builder.Top(1); + } + + if (op == "Scalar") + { + if (builder.Columns.Count != 1) + throw new InvalidOperationException("You must select one column in scalar steatement."); + + result = Scalar(builder); + } + else + { + if (justOne) + { + if (op == "Last" && builder.Order.Count == 0) + result = Query(builder).LastOrDefault(); // Last record fallback + else + result = Query(builder).FirstOrDefault(); // return a single record + } + else + result = Query(builder); // return lots + + // MapEnumerable to specified result (still needs to be casted after that) + if (types != null) + { + if (types.Count == 1) + result = justOne ? + result.Map(types[0]) : + ((IEnumerable)result).MapEnumerable(types[0]); + + // TODO: Dictionaries + } + } + } + + return result; + } + + private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, DynamicQueryBuilder builder, int i) where T : class + { + if (args != null) + { + if (args[i] is Type[]) + types = new List((Type[])args[i]); + else if (args[i] is Type) + types = new List(new Type[] { (Type)args[i] }); + } + + if (types != null && types.Count == 1 && !info.ArgumentNames.Any(a => a.ToLower() == "table")) + builder.Table(types[0]); + } + + #endregion Universal Dynamic Invoker + + #region IDisposable Members + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + Database.RemoveFromCache(this); + + // Lose reference but don't kill it. + if (Database != null) + Database = null; + } + + #endregion IDisposable Members + + #region ICloneable Members + + /// Creates a new object that is a copy of the current + /// instance. + /// A new object that is a copy of this instance. + public object Clone() + { + return new DynamicTable() + { + Database = this.Database, + Schema = this.Schema, + TableName = this.TableName, + TableType = this.TableType + }; + } + + #endregion ICloneable Members + } +} \ No newline at end of file diff --git a/DynamORM/DynamicTransaction.cs b/DynamORM/DynamicTransaction.cs new file mode 100644 index 0000000..09af02e --- /dev/null +++ b/DynamORM/DynamicTransaction.cs @@ -0,0 +1,138 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Data; + +namespace DynamORM +{ + /// Helper class to easy manage transaction. + public class DynamicTransaction : IDbTransaction, IDisposable + { + private DynamicDatabase _db; + private DynamicConnection _con; + private bool _singleTransaction; + private Action _disposed; + private bool _operational = false; + + /// Initializes a new instance of the class. + /// Database connection manager. + /// Active connection. + /// Are we using single transaction mode? I so... act correctly. + /// One of the values. + /// This action is invoked when transaction is disposed. + internal DynamicTransaction(DynamicDatabase db, DynamicConnection con, bool singleTransaction, IsolationLevel? il, Action disposed) + { + _db = db; + _con = con; + _singleTransaction = singleTransaction; + _disposed = disposed; + + lock (_db.SyncLock) + { + if (!_db.TransactionPool.ContainsKey(_con.Connection)) + throw new InvalidOperationException("Can't create transaction using disposed connection."); + else if (_singleTransaction && _db.TransactionPool[_con.Connection].Count > 0) + _operational = false; + else + { + _db.TransactionPool[_con.Connection].Push(_con.Connection.BeginTransaction()); + _db.PoolStamp = DateTime.Now.Ticks; + _operational = true; + } + } + } + + /// Commits the database transaction. + public void Commit() + { + lock (_db.SyncLock) + { + if (_operational) + { + var t = _db.TransactionPool.TryGetValue(_con.Connection); + + if (t != null && t.Count > 0) + { + IDbTransaction trans = t.Pop(); + + _db.PoolStamp = DateTime.Now.Ticks; + + trans.Commit(); + trans.Dispose(); + } + + _operational = false; + } + } + } + + /// Rolls back a transaction from a pending state. + public void Rollback() + { + lock (_db.SyncLock) + { + if (_operational) + { + var t = _db.TransactionPool.TryGetValue(_con.Connection); + + if (t != null && t.Count > 0) + { + IDbTransaction trans = t.Pop(); + + _db.PoolStamp = DateTime.Now.Ticks; + + trans.Rollback(); + trans.Dispose(); + } + + _operational = false; + } + } + } + + /// Gets connection object to associate with the transaction. + public IDbConnection Connection + { + get { return _con; } + } + + /// Gets for this transaction. + public IsolationLevel IsolationLevel { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + Rollback(); + + if (_disposed != null) + _disposed(); + } + } +} \ No newline at end of file diff --git a/DynamORM/Helpers/CollectionComparer.cs b/DynamORM/Helpers/CollectionComparer.cs new file mode 100644 index 0000000..bfa49ba --- /dev/null +++ b/DynamORM/Helpers/CollectionComparer.cs @@ -0,0 +1,126 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using System.Linq; + +namespace DynamORM.Helpers +{ + /// Defines methods to support the comparison of collections for equality. + /// The type of collection to compare. + public class CollectionComparer : IEqualityComparer> + { + /// Determines whether the specified objects are equal. + /// The first object of type T to compare. + /// The second object of type T to compare. + /// Returns true if the specified objects are equal; otherwise, false. + bool IEqualityComparer>.Equals(IEnumerable first, IEnumerable second) + { + return Equals(first, second); + } + + /// Returns a hash code for the specified object. + /// The enumerable for which a hash code is to be returned. + /// A hash code for the specified object. + int IEqualityComparer>.GetHashCode(IEnumerable enumerable) + { + return GetHashCode(enumerable); + } + + /// Returns a hash code for the specified object. + /// The enumerable for which a hash code is to be returned. + /// A hash code for the specified object. + public static int GetHashCode(IEnumerable enumerable) + { + int hash = 17; + + foreach (T val in enumerable.OrderBy(x => x)) + hash = (hash * 23) + val.GetHashCode(); + + return hash; + } + + /// Determines whether the specified objects are equal. + /// The first object of type T to compare. + /// The second object of type T to compare. + /// Returns true if the specified objects are equal; otherwise, false. + public static bool Equals(IEnumerable first, IEnumerable second) + { + if ((first == null) != (second == null)) + return false; + + if (!object.ReferenceEquals(first, second) && (first != null)) + { + if (first.Count() != second.Count()) + return false; + + if ((first.Count() != 0) && HaveMismatchedElement(first, second)) + return false; + } + + return true; + } + + private static bool HaveMismatchedElement(IEnumerable first, IEnumerable second) + { + int firstCount; + int secondCount; + + var firstElementCounts = GetElementCounts(first, out firstCount); + var secondElementCounts = GetElementCounts(second, out secondCount); + + if (firstCount != secondCount) + return true; + + foreach (var kvp in firstElementCounts) + if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) + return true; + + return false; + } + + private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) + { + var dictionary = new Dictionary(); + nullCount = 0; + + foreach (T element in enumerable) + { + if (element == null) + nullCount++; + else + { + int count = dictionary.TryGetNullable(element) ?? 0; + dictionary[element] = ++count; + } + } + + return dictionary; + } + } +} \ No newline at end of file diff --git a/DynamORM/Helpers/FrameworkTools.cs b/DynamORM/Helpers/FrameworkTools.cs new file mode 100644 index 0000000..a16de37 --- /dev/null +++ b/DynamORM/Helpers/FrameworkTools.cs @@ -0,0 +1,156 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq.Expressions; +using System.Reflection; + +namespace DynamORM.Helpers +{ + /// Framework detection and specific implementations. + public static class FrameworkTools + { + #region Mono or .NET Framework detection + + /// This is preaty simple trick. + private static bool _isMono = Type.GetType("Mono.Runtime") != null; + + /// Gets a value indicating whether application is running under mono runtime. + public static bool IsMono { get { return _isMono; } } + + #endregion Mono or .NET Framework detection + + static FrameworkTools() + { + _frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter(); + } + + #region GetGenericTypeArguments + + private static Func> _frameworkTypeArgumentsGetter = null; + + private static Func> CreateTypeArgumentsGetter() + { + // HACK: Creating binders assuming types are correct... this may fail. + if (IsMono) + { + var binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); + + if (binderType != null) + { + ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Field( + Expression.TypeAs(param, binderType), "typeArguments"), + typeof(IList)), param).Compile(); + } + } + else + { + var inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + var prop = inter.GetProperty("TypeArguments"); + + if (!prop.CanRead) + return null; + + var objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Property( + Expression.TypeAs(objParm, inter), prop.Name), + typeof(IList)), objParm).Compile(); + } + } + + return null; + } + + /// Extension method allowing to easyly extract generic type + /// arguments from assuming that it + /// inherits from + /// Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder + /// in .NET Framework or + /// Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder + /// under Mono. + /// Binder from which get type arguments. + /// This is generally a bad solution, but there is no other + /// currently so we have to go with it. + /// List of types passed as generic parameters. + public static IList GetGenericTypeArguments(this InvokeMemberBinder binder) + { + // First try to use delegate if exist + if (_frameworkTypeArgumentsGetter != null) + return _frameworkTypeArgumentsGetter(binder); + + if (_isMono) + { + // HACK: Using Reflection + // In mono this is trivial. + + // First we get field info. + var field = binder.GetType().GetField("typeArguments", BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + // If this was a success get and return it's value + if (field != null) + return field.GetValue(binder) as IList; + } + else + { + // HACK: Using Reflection + // In this case, we need more aerobic :D + + // First, get the interface + var inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + // Now get property. + var prop = inter.GetProperty("TypeArguments"); + + // If we have a property, return it's value + if (prop != null) + return prop.GetValue(binder, null) as IList; + } + } + + // Sadly return null if failed. + return null; + } + + #endregion GetGenericTypeArguments + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/ColumnAttribute.cs b/DynamORM/Mapper/ColumnAttribute.cs new file mode 100644 index 0000000..24b499f --- /dev/null +++ b/DynamORM/Mapper/ColumnAttribute.cs @@ -0,0 +1,148 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Data; + +namespace DynamORM.Mapper +{ + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Property)] + public class ColumnAttribute : Attribute + { + /// Gets or sets name. + public string Name { get; set; } + + /// Gets or sets column type. + /// Used when overriding schema. + public DbType? Type { get; set; } + + /// Gets or sets a value indicating whether column is a key. + public bool IsKey { get; set; } + + /// Gets or sets a value indicating whether column should have unique value. + /// Used when overriding schema. + public bool? IsUnique { get; set; } + + /// Gets or sets column size. + /// Used when overriding schema. + public int? Size { get; set; } + + /// Gets or sets column precision. + /// Used when overriding schema. + public byte? Precision { get; set; } + + /// Gets or sets column scale. + /// Used when overriding schema. + public byte? Scale { get; set; } + + #region Constructors + + /// Initializes a new instance of the class. + public ColumnAttribute() { } + + /// Initializes a new instance of the class. + /// Name of column. + public ColumnAttribute(string name) + { + Name = name; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + public ColumnAttribute(string name, bool isKey) + : this(name) + { + IsKey = isKey; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + public ColumnAttribute(string name, bool isKey, DbType type) + : this(name, isKey) + { + Type = type; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + public ColumnAttribute(string name, bool isKey, DbType type, int size) + : this(name, isKey, type) + { + Size = size; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale) + : this(name, isKey, type) + { + Precision = precision; + Scale = scale; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, precision, scale) + { + Size = size; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column has unique value. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, size, precision, scale) + { + IsUnique = isUnique; + } + + #endregion Constructors + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/DynamicMapperCache.cs b/DynamORM/Mapper/DynamicMapperCache.cs new file mode 100644 index 0000000..a947b9f --- /dev/null +++ b/DynamORM/Mapper/DynamicMapperCache.cs @@ -0,0 +1,74 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; + +namespace DynamORM.Mapper +{ + /// Class with maper cache. + public static class DynamicMapperCache + { + private static readonly object SyncLock = new object(); + private static Dictionary _cache = new Dictionary(); + + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper() + { + return GetMapper(typeof(T)); + } + + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper(Type type) + { + if (type == null) + return null; + /*if (type.IsAnonymous()) + return null;*/ + + DynamicTypeMap mapper = null; + + lock (SyncLock) + { + if (!_cache.TryGetValue(type, out mapper)) + { + mapper = new DynamicTypeMap(type); + + if (mapper != null) + _cache.Add(type, mapper); + } + } + + return mapper; + } + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/DynamicPropertyInvoker.cs b/DynamORM/Mapper/DynamicPropertyInvoker.cs new file mode 100644 index 0000000..7e44ccc --- /dev/null +++ b/DynamORM/Mapper/DynamicPropertyInvoker.cs @@ -0,0 +1,102 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace DynamORM.Mapper +{ + /// Dynamic property invoker. + public class DynamicPropertyInvoker + { + /// Gets value getter. + public Func Get { get; private set; } + + /// Gets value setter. + public Action Set { get; private set; } + + /// Gets name of property. + public string Name { get; private set; } + + /// Gets type column description. + public ColumnAttribute Column { get; private set; } + + /// Gets a value indicating whether this is ignored in some cases. + public bool Ignore { get; private set; } + + /// Initializes a new instance of the class. + /// Property info to be invoked in the future. + /// Column attribute if exist. + public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr) + { + Name = property.Name; + + var ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); + + Ignore = ignore != null && ignore.Length > 0; + + Column = attr; + + Get = CreateGetter(property); + Set = CreateSetter(property); + } + + private Func CreateGetter(PropertyInfo property) + { + if (!property.CanRead) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + + return Expression.Lambda>( + Expression.Convert( + Expression.Property( + Expression.TypeAs(objParm, property.DeclaringType), + property.Name), + typeof(object)), objParm).Compile(); + } + + private Action CreateSetter(PropertyInfo property) + { + if (!property.CanWrite) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + var valueParm = Expression.Parameter(typeof(object), "value"); + + return Expression.Lambda>( + Expression.Assign( + Expression.Property( + Expression.Convert(objParm, property.DeclaringType), + property.Name), + Expression.Convert(valueParm, property.PropertyType)), + objParm, valueParm).Compile(); + } + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/DynamicTypeMap.cs b/DynamORM/Mapper/DynamicTypeMap.cs new file mode 100644 index 0000000..b3c4958 --- /dev/null +++ b/DynamORM/Mapper/DynamicTypeMap.cs @@ -0,0 +1,134 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace DynamORM.Mapper +{ + /// Represents type columnMap. + public class DynamicTypeMap + { + /// Gets mapper destination type creator. + public Type Type { get; private set; } + + /// Gets type table description. + public TableAttribute Table { get; private set; } + + /// Gets object creator. + public Func Creator { get; private set; } + + /// Gets map of columns to properties. + public Dictionary ColumnsMap { get; private set; } + + /// Gets map of properties to column. + public Dictionary PropertyMap { get; private set; } + + /// Gets list of ignored properties. + public List Ignored { get; private set; } + + /// Initializes a new instance of the class. + /// Type to which columnMap objects. + public DynamicTypeMap(Type type) + { + Type = type; + + var attr = type.GetCustomAttributes(typeof(TableAttribute), false); + + if (attr != null && attr.Length > 0) + Table = (TableAttribute)attr[0]; + + Creator = CreateCreator(); + CreateColumnAndPropertyMap(); + } + + private void CreateColumnAndPropertyMap() + { + var columnMap = new Dictionary(); + var propertyMap = new Dictionary(); + + foreach (var pi in Type.GetProperties()) + { + ColumnAttribute attr = null; + + var attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); + + if (attrs != null && attrs.Length > 0) + attr = (ColumnAttribute)attrs[0]; + + string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name; + + var val = new DynamicPropertyInvoker(pi, attr); + columnMap.Add(col.ToLower(), val); + + propertyMap.Add(pi.Name, col); + } + + ColumnsMap = columnMap; + PropertyMap = propertyMap; + + Ignored = columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList(); + } + + private Func CreateCreator() + { + if (Type.GetConstructor(Type.EmptyTypes) != null) + return Expression.Lambda>(Expression.New(Type)).Compile(); + + return null; + } + + /// Create object of type and fill values from source. + /// Object containing values that will be mapped to newy created object. + /// New object of type with matching values from source. + public object Create(object source) + { + return Map(source, Creator()); + } + + /// Fill values from source to object in destination. + /// Object containing values that will be mapped to newy created object. + /// Object of type to which copy values from source. + /// Object of type with matching values from source. + public object Map(object source, object destination) + { + DynamicPropertyInvoker dpi = null; + + foreach (var item in source.ToDictionary()) + { + if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) + if (dpi.Set != null) + dpi.Set(destination, item.Value); + } + + return destination; + } + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/IgnoreAttribute.cs b/DynamORM/Mapper/IgnoreAttribute.cs new file mode 100644 index 0000000..e173737 --- /dev/null +++ b/DynamORM/Mapper/IgnoreAttribute.cs @@ -0,0 +1,39 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Mapper +{ + /// Allows to add ignore action to property. + /// Property still get's mapped from output. + [AttributeUsage(AttributeTargets.Property)] + public class IgnoreAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/TableAttribute.cs b/DynamORM/Mapper/TableAttribute.cs new file mode 100644 index 0000000..02aa020 --- /dev/null +++ b/DynamORM/Mapper/TableAttribute.cs @@ -0,0 +1,46 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Mapper +{ + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + /// Gets or sets name. + public string Name { get; set; } + + /// Gets or sets a value indicating whether overide database + /// schema values. + /// If database doeesn't support schema, you still have to + /// set this to true to get schema from type. + public bool Override { get; set; } + } +} \ No newline at end of file diff --git a/DynamORM/Properties/AssemblyInfo.cs b/DynamORM/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..84529c1 --- /dev/null +++ b/DynamORM/Properties/AssemblyInfo.cs @@ -0,0 +1,65 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * See: http://opensource.org/licenses/bsd-license.php +*/ + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DynamORM")] +[assembly: AssemblyDescription("Dynamic Object-Relational Mapping library.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RUSSEK Software")] +[assembly: AssemblyProduct("DynamORM")] +[assembly: AssemblyCopyright("Copyright © RUSSEK Software 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0a42480b-bba7-4b01-807f-9962a76e4e47")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.1")] +[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file