diff --git a/.gitignore b/.gitignore index 2a1e41e..ecc8fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,11 +29,13 @@ build/ bld/ [Bb]in/ [Oo]bj/ -[Oo]bj/* [Oo]ut/ msbuild.log msbuild.err msbuild.wrn +*/[Bb]in/ +*/[Oo]bj/ +*/[Oo]ut/ # Visual Studio 2015 .vs/ diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index f8b1432..9da8b88 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -33,33 +33,32 @@ * * DYNAMORM_OMMIT_TRYPARSE - Remove TryParse helpers (also applies DYNAMORM_OMMIT_GENERICEXECUTION) */ -using System; -using System.Collections; +using DynamORM.Builders.Extensions; +using DynamORM.Builders.Implementation; +using DynamORM.Builders; +using DynamORM.Helpers.Dynamics; +using DynamORM.Helpers; +using DynamORM.Mapper; +using DynamORM.Validation; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; -using System.Data; +using System.Collections; using System.Data.Common; +using System.Data; using System.Dynamic; using System.IO; -using System.Linq; using System.Linq.Expressions; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Text; using System.Text.RegularExpressions; -using DynamORM.Builders; -using DynamORM.Builders.Extensions; -using DynamORM.Builders.Implementation; -using DynamORM.Helpers; -using DynamORM.Helpers.Dynamics; -using DynamORM.Mapper; -using DynamORM.Validation; +using System.Text; +using System; [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is a generated file which generates all the necessary support classes.")] [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1403:FileMayOnlyContainASingleNamespace", Justification = "This is a generated file which generates all the necessary support classes.")] - namespace DynamORM { /// Cache data reader in memory. @@ -3566,6 +3565,9 @@ namespace DynamORM /// Database support stored procedures (EXEC procedure ...). SupportStoredProcedures = 0x00000100, + /// Database support with no lock syntax. + SupportNoLock = 0x00001000, + /// Debug option allowing to enable command dumps by default. DumpCommands = 0x01000000, } @@ -4531,7 +4533,7 @@ namespace DynamORM param.Scale, param.Precision, param.Scale, - param.Value is byte[] ? ConvertByteArrayToHexString((byte[])param.Value) : param.Value ?? "NULL", + param.Value is byte[]? ConvertByteArrayToHexString((byte[])param.Value) : param.Value ?? "NULL", param.Value != null ? param.Value.GetType().Name : "DBNull"); } @@ -7417,6 +7419,9 @@ namespace DynamORM /// Gets table alias. string Alias { get; } + /// Gets table no lock status. + bool NoLock { get; } + /// Gets table schema. Dictionary Schema { get; } } @@ -8318,12 +8323,14 @@ namespace DynamORM /// The name of table. /// The table alias. /// The table owner. - public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null) + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, bool nolock = false) : this() { Name = name; Alias = alias; Owner = owner; + NoLock = nolock; if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) Schema = db.GetSchema(name, owner: owner); @@ -8336,7 +8343,8 @@ namespace DynamORM /// The type which can be mapped to database. /// The table alias. /// The table owner. - public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null) + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, bool nolock = false) : this() { DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); @@ -8346,6 +8354,7 @@ namespace DynamORM Owner = (mapper.Table != null) ? mapper.Table.Owner : owner; Alias = alias; + NoLock = nolock; Schema = db.GetSchema(type); } @@ -8359,6 +8368,9 @@ namespace DynamORM /// Gets or sets table alias. public string Alias { get; internal set; } + /// Gets or sets table alias. + public bool NoLock { get; internal set; } + /// Gets or sets table schema. public Dictionary Schema { get; internal set; } @@ -8467,6 +8479,7 @@ namespace DynamORM Database.AddToCache(this); SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == DynamicDatabaseOptions.SupportNoLock; } /// Initializes a new instance of the class. @@ -8515,6 +8528,9 @@ namespace DynamORM /// Gets a value indicating whether database supports standard schema. public bool SupportSchema { get; private set; } + /// Gets a value indicating whether database supports with no lock syntax. + public bool SupportNoLock { get; private set; } + /// /// Generates the text this command will execute against the underlying database. /// @@ -8957,6 +8973,15 @@ namespace DynamORM item = item.Validated("Alias"); // Intercepting null and empty aliases return string.Format("{0} AS {1}", parent, item); + case "NOLOCK": + if (!SupportNoLock) + return parent; + + if (node.Arguments != null && node.Arguments.Length > 1) + throw new ArgumentException("NOLOCK method expects no arguments."); + + return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); + case "COUNT": if (node.Arguments != null && node.Arguments.Length > 1) throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch()); @@ -9512,6 +9537,7 @@ namespace DynamORM string owner = null; string main = null; string alias = null; + bool nolock = false; Type type = null; while (true) @@ -9536,6 +9562,20 @@ namespace DynamORM continue; } + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") { main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); @@ -9605,7 +9645,7 @@ namespace DynamORM } if (!string.IsNullOrEmpty(main)) - tableInfo = type == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, type, alias, owner); + tableInfo = type == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, type, alias, owner, nolock); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); } @@ -9627,6 +9667,9 @@ namespace DynamORM if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); } @@ -9696,6 +9739,7 @@ namespace DynamORM string owner = null; string alias = null; string condition = null; + bool nolock = false; Type tableType = null; // If the expression resolves to a string... @@ -9773,6 +9817,20 @@ namespace DynamORM continue; } + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + // Support for table specifications... if (node is DynamicParser.Node.GetMember) { @@ -9884,7 +9942,7 @@ namespace DynamORM if (justAddTables) { if (!string.IsNullOrEmpty(main)) - tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, tableType, alias, owner); + tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, tableType, alias, owner, nolock); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); @@ -9912,6 +9970,9 @@ namespace DynamORM if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + if (!string.IsNullOrEmpty(condition)) sb.AppendFormat(" ON {0}", condition); @@ -14448,7 +14509,7 @@ namespace DynamORM public enum DynamicEntityState { /// Default state. This state will only permit to refresh data from database. - /// In this state repository will be unable to tell if object with this state should be added + /// In this state repository will be unable to tell if object with this state should be added /// or updated in database, but you can still manually perform update or insert on such object. Unknown, @@ -14510,7 +14571,8 @@ namespace DynamORM /// Objects enumerator. public virtual IEnumerable GetAll() { - return EnumerateQuery(_database.From()); + using (var q = _database.From()) + return EnumerateQuery(q); } /// Get rows from database by custom query. diff --git a/AmalgamationTool/Program.cs b/AmalgamationTool/Program.cs index 0c75232..99bd1ad 100644 --- a/AmalgamationTool/Program.cs +++ b/AmalgamationTool/Program.cs @@ -25,7 +25,7 @@ namespace AmalgamationTool // Deal with usings foreach (var u in content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None) - .Where(l => l.Trim().StartsWith("using ")) + .Where(l => l.Trim().StartsWith("using ") && !l.Trim().StartsWith("using (")) .Select(l => l.Trim())) if (!usings.Contains(u)) usings.Add(u); @@ -96,6 +96,41 @@ namespace AmalgamationTool FillClassesAndNamespacesIddented(classes, sb); + string amalgamation = sb.ToString(); + + sb = new StringBuilder(); + + string prevTrimmed = null; + + string[] array = amalgamation.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + + for (int i = 0; i < array.Length; i++) + { + string l = array[i]; + var currentTrimmed = l.Trim(); + var nextTrimmed = (i + 1 == array.Length) ? null : array[i + 1].Trim(); + + if (prevTrimmed != null) + { + switch (prevTrimmed) + { + case "": + if (currentTrimmed == string.Empty) + continue; + break; + + case "{": + case "}": + if (currentTrimmed == string.Empty && (nextTrimmed == prevTrimmed || nextTrimmed == string.Empty)) + continue; + break; + } + } + + sb.AppendLine(l); + prevTrimmed = currentTrimmed; + } + File.WriteAllText(Path.GetFullPath(args[1].Trim('"', '\'')), sb.ToString()); } diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs index b99ef99..f7654de 100644 --- a/DynamORM.Tests/Select/ParserTests.cs +++ b/DynamORM.Tests/Select/ParserTests.cs @@ -47,7 +47,8 @@ namespace DynamORM.Tests.Select CreateDynamicDatabase( DynamicDatabaseOptions.SingleConnection | DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset); + DynamicDatabaseOptions.SupportLimitOffset | + DynamicDatabaseOptions.SupportNoLock); } /// Tear down test objects. @@ -98,6 +99,18 @@ namespace DynamORM.Tests.Select Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); } + /// + /// Tests from method with as expression in text. + /// + [TestMethod] + public void TestFromGetAsNoLock1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As("c").NoLock()); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WITH(NOLOCK)", cmd.CommandText()); + } + /// /// Tests from method with as expression using lambda. /// @@ -211,6 +224,19 @@ namespace DynamORM.Tests.Select Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); } + /// + /// Tests from method using invoke with sub query. + /// + [TestMethod] + public void TestFromSubQuery4() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users.NoLock())).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\" WITH(NOLOCK)) AS u", cmd.CommandText()); + } + /// /// Tests where method with alias. /// @@ -416,6 +442,35 @@ namespace DynamORM.Tests.Select Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); } + /// + /// Tests from method using invoke with sub query an no lock. + /// + [TestMethod] + public void TestInnerJoin5() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) + .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients.NoLock()).Where(x => x.Deleted == 0)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) + .Select(usr => usr.All(), uc => uc.Users); + + Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WITH(NOLOCK) WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests inner join method with no lock. + /// + [TestMethod] + public void TestInnerJoin6() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) + .Join(u => u.Inner().dbo.UserClients.AS(u.uc).NoLock().On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN \"dbo\".\"UserClients\" AS uc WITH(NOLOCK) ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + /// /// Tests left outer join method. /// diff --git a/DynamORM/Builders/ITableInfo.cs b/DynamORM/Builders/ITableInfo.cs index 4059796..b32513b 100644 --- a/DynamORM/Builders/ITableInfo.cs +++ b/DynamORM/Builders/ITableInfo.cs @@ -43,6 +43,9 @@ namespace DynamORM.Builders /// Gets table alias. string Alias { get; } + /// Gets table no lock status. + bool NoLock { get; } + /// Gets table schema. Dictionary Schema { get; } } diff --git a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs index a6c8958..f38c1ed 100644 --- a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs @@ -86,12 +86,14 @@ namespace DynamORM.Builders.Implementation /// The name of table. /// The table alias. /// The table owner. - public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null) + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, bool nolock = false) : this() { Name = name; Alias = alias; Owner = owner; + NoLock = nolock; if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) Schema = db.GetSchema(name, owner: owner); @@ -104,7 +106,8 @@ namespace DynamORM.Builders.Implementation /// The type which can be mapped to database. /// The table alias. /// The table owner. - public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null) + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, bool nolock = false) : this() { DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); @@ -114,6 +117,7 @@ namespace DynamORM.Builders.Implementation Owner = (mapper.Table != null) ? mapper.Table.Owner : owner; Alias = alias; + NoLock = nolock; Schema = db.GetSchema(type); } @@ -127,6 +131,9 @@ namespace DynamORM.Builders.Implementation /// Gets or sets table alias. public string Alias { get; internal set; } + /// Gets or sets table alias. + public bool NoLock { get; internal set; } + /// Gets or sets table schema. public Dictionary Schema { get; internal set; } @@ -235,6 +242,7 @@ namespace DynamORM.Builders.Implementation Database.AddToCache(this); SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == DynamicDatabaseOptions.SupportNoLock; } /// Initializes a new instance of the class. @@ -283,6 +291,9 @@ namespace DynamORM.Builders.Implementation /// Gets a value indicating whether database supports standard schema. public bool SupportSchema { get; private set; } + /// Gets a value indicating whether database supports with no lock syntax. + public bool SupportNoLock { get; private set; } + /// /// Generates the text this command will execute against the underlying database. /// @@ -725,6 +736,15 @@ namespace DynamORM.Builders.Implementation item = item.Validated("Alias"); // Intercepting null and empty aliases return string.Format("{0} AS {1}", parent, item); + case "NOLOCK": + if (!SupportNoLock) + return parent; + + if (node.Arguments != null && node.Arguments.Length > 1) + throw new ArgumentException("NOLOCK method expects no arguments."); + + return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); + case "COUNT": if (node.Arguments != null && node.Arguments.Length > 1) throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch()); diff --git a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs index 4b4897c..da5add2 100644 --- a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs @@ -346,6 +346,7 @@ namespace DynamORM.Builders.Implementation string owner = null; string main = null; string alias = null; + bool nolock = false; Type type = null; while (true) @@ -369,6 +370,20 @@ namespace DynamORM.Builders.Implementation node = node.Host; continue; } + + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") { @@ -439,7 +454,7 @@ namespace DynamORM.Builders.Implementation } if (!string.IsNullOrEmpty(main)) - tableInfo = type == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, type, alias, owner); + tableInfo = type == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, type, alias, owner, nolock); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); } @@ -461,6 +476,9 @@ namespace DynamORM.Builders.Implementation if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); } @@ -530,6 +548,7 @@ namespace DynamORM.Builders.Implementation string owner = null; string alias = null; string condition = null; + bool nolock = false; Type tableType = null; // If the expression resolves to a string... @@ -607,6 +626,20 @@ namespace DynamORM.Builders.Implementation continue; } + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + // Support for table specifications... if (node is DynamicParser.Node.GetMember) { @@ -718,7 +751,7 @@ namespace DynamORM.Builders.Implementation if (justAddTables) { if (!string.IsNullOrEmpty(main)) - tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, tableType, alias, owner); + tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, tableType, alias, owner, nolock); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); @@ -746,6 +779,9 @@ namespace DynamORM.Builders.Implementation if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + if (!string.IsNullOrEmpty(condition)) sb.AppendFormat(" ON {0}", condition); diff --git a/DynamORM/DynamicDatabaseOptions.cs b/DynamORM/DynamicDatabaseOptions.cs index d1bc190..3d4ba42 100644 --- a/DynamORM/DynamicDatabaseOptions.cs +++ b/DynamORM/DynamicDatabaseOptions.cs @@ -59,6 +59,9 @@ namespace DynamORM /// Database support stored procedures (EXEC procedure ...). SupportStoredProcedures = 0x00000100, + /// Database support with no lock syntax. + SupportNoLock = 0x00001000, + /// Debug option allowing to enable command dumps by default. DumpCommands = 0x01000000, }