diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..dbd1cbd1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,226 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = Space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_return = true:warning +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = 0 + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false +dotnet_style_allow_statement_immediately_after_block_experimental = false + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = true + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = when_on_single_line +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = true +csharp_style_expression_bodied_methods = when_on_single_line +csharp_style_expression_bodied_operators = true +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +csharp_style_prefer_readonly_struct = true + +# Code-block preferences +csharp_prefer_braces = when_multiline +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = file_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = false +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:suggestion + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/PetaPoco.Tests.Integration/Documentation/Delete.cs b/PetaPoco.Tests.Integration/Documentation/Delete.cs index 0f21788d..18d4ac29 100644 --- a/PetaPoco.Tests.Integration/Documentation/Delete.cs +++ b/PetaPoco.Tests.Integration/Documentation/Delete.cs @@ -20,7 +20,7 @@ public Deletes() [Fact] public void DeleteByPoco() { - // Create the person + // Create a person var person = new Person { Id = Guid.NewGuid(), Name = "PetaPoco", Dob = new DateTime(2011, 1, 1), Age = (DateTime.Now.Year - 2011), Height = 242 }; // Tell PetaPoco to insert it @@ -41,10 +41,7 @@ public void DeleteByPoco() [Fact] public void DeleteByPrimaryKey() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - - // Add a note + // Insert a new note var note = new Note { Text = "This is my note", CreatedOn = DateTime.UtcNow }; DB.Insert(note); @@ -66,10 +63,7 @@ public void DeleteByPrimaryKey() [Fact] public void DeleteCustomWhere() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - - // Add a note + // Insert a new note var note = new Note { Text = "This is my note", CreatedOn = DateTime.UtcNow }; DB.Insert(note); @@ -91,10 +85,7 @@ public void DeleteCustomWhere() [Fact] public void DeleteCustomSqlWhere() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - - // Add a note + // Insert a new note var note = new Note { Text = "This is my note", CreatedOn = DateTime.UtcNow }; DB.Insert(note); @@ -118,17 +109,17 @@ public void DeleteCustomSqlWhere() [Fact] public void DeleteAdvanced() { - // Create the person + // Create a person var person = new Person { Id = Guid.NewGuid(), Name = "PetaPoco", Dob = new DateTime(2011, 1, 1), Age = (DateTime.Now.Year - 2011), Height = 242 }; - // Tell PetaPoco to insert it, but to the table SpecificPeople and not People + // Tell PetaPoco to insert it in the `SpecificPeople` table (not the usual `People` table) DB.Insert("SpecificPeople", person); - // Obviously, we find only 1 matching person in the db + // Obviously, we find only 1 matching person in our db, in our `SpecificPeople` table var count = DB.ExecuteScalar("SELECT COUNT([Id]) FROM [SpecificPeople] WHERE [Id] = @Id", new { person.Id }); count.ShouldBe(1); - // Tell PetaPoco to delete it, but in the table SpecificPeople and not People + // Tell PetaPoco to delete it from the `SpecificPeople` table (specifying table as `People` this is a special case, not the default mapped table) DB.Delete("SpecificPeople", "Id", person); // Obviously, we should now have none in the db diff --git a/PetaPoco.Tests.Integration/Documentation/Inserts.cs b/PetaPoco.Tests.Integration/Documentation/Inserts.cs index d4d006e7..cb729f7b 100644 --- a/PetaPoco.Tests.Integration/Documentation/Inserts.cs +++ b/PetaPoco.Tests.Integration/Documentation/Inserts.cs @@ -36,7 +36,7 @@ public void Insert() // See, they are the same clone.ShouldBe(person); - // But, they're not not reference equals as PetaPoco doesn't cache because it's a MircoORM. + // But they are not ReferenceEquals, as PetaPoco doesn't cache because it's a Micro-ORM. person.Equals(clone).ShouldBeFalse(); } @@ -68,7 +68,7 @@ public void InsertAutoIncrement() // See, they are the same clone.ShouldBe(order); - // But, they're not not reference equals as PetaPoco doesn't cache because it's a MircoORM. + // But they are not ReferenceEquals, as PetaPoco doesn't cache because it's a Micro-ORM. order.Equals(clone).ShouldBeFalse(); } @@ -98,16 +98,13 @@ public void InsertToDifferentTable() // See, they are the same clone.ShouldBe(person); - // But, they're not not reference equals as PetaPoco doesn't cache because it's a MircoORM. + // But they are not ReferenceEquals, as PetaPoco doesn't cache because it's a Micro-ORM. person.Equals(clone).ShouldBeFalse(); } [Fact] public void InsertConventionalPoco() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - // Insert some notes using all APIs // Each of the API usuages here are effectively the same, as PetaPoco is providing the correct unknown values. @@ -132,7 +129,7 @@ public void InsertUnconventionalPoco() { // Create the UnconventionalPocos table DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos') - DROP TABLE dbo.[TBL_UnconventionalPocos] + DROP TABLE dbo.[TBL_UnconventionalPocos] CREATE TABLE dbo.[TBL_UnconventionalPocos] ( [PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY, @@ -152,7 +149,7 @@ [Text] NTEXT NOT NULL // See, they are the same clone.ShouldBe(poco); - // But, they're not not reference equals as PetaPoco doesn't cache because it's a MircoORM. + // But they are not ReferenceEquals, as PetaPoco doesn't cache because it's a Micro-ORM. poco.Equals(clone).ShouldBeFalse(); } @@ -161,7 +158,7 @@ public void InsertConventionalUnconventionalPoco() { // Create the UnconventionalPocos table DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos') - DROP TABLE dbo.[TBL_UnconventionalPocos] + DROP TABLE dbo.[TBL_UnconventionalPocos] CREATE TABLE dbo.[TBL_UnconventionalPocos] ( [PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY, @@ -195,7 +192,7 @@ [Text] NTEXT NOT NULL // See, they are the same clone.ShouldBe(poco); - // But, they're not not reference equals as PetaPoco doesn't cache because it's a MircoORM. + // But they are not ReferenceEquals, as PetaPoco doesn't cache because it's a Micro-ORM. poco.Equals(clone).ShouldBeFalse(); } @@ -204,7 +201,7 @@ public void InsertAnonymousPocoWithConventionalNaming() { // Create the table for our unknown but conventional POCO DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles') - DROP TABLE dbo.[XFiles] + DROP TABLE dbo.[XFiles] CREATE TABLE dbo.[XFiles] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, @@ -231,7 +228,7 @@ public void InsertDynamicUnknownPocoWithConventionalNaming() { // Create the table for our unknown but conventional POCO DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles') - DROP TABLE dbo.[XFiles] + DROP TABLE dbo.[XFiles] CREATE TABLE dbo.[XFiles] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, diff --git a/PetaPoco.Tests.Integration/Documentation/Save.cs b/PetaPoco.Tests.Integration/Documentation/Save.cs index 4e1e0dbd..8eb221b1 100644 --- a/PetaPoco.Tests.Integration/Documentation/Save.cs +++ b/PetaPoco.Tests.Integration/Documentation/Save.cs @@ -20,9 +20,6 @@ public Save() [Fact] public void Save_Insert() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - // Add a note var note = new Note { Text = "This is my note", CreatedOn = DateTime.UtcNow }; DB.Save(note); @@ -46,9 +43,6 @@ public void Save_Insert() [Fact] public void Save_Update() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - // Add a note var note = new Note { Text = "This is my note", CreatedOn = DateTime.UtcNow }; DB.Save(note); diff --git a/PetaPoco.Tests.Integration/Documentation/Updates.cs b/PetaPoco.Tests.Integration/Documentation/Updates.cs index 2e3b8ecf..f00af65e 100644 --- a/PetaPoco.Tests.Integration/Documentation/Updates.cs +++ b/PetaPoco.Tests.Integration/Documentation/Updates.cs @@ -103,9 +103,6 @@ public void UpdateToDifferentTable() [Fact] public void UpdateConventionalPoco() { - // Clear out any notes and reset the ID sequence counter - DB.Execute("TRUNCATE TABLE [Note]"); - // Create some notes var note1 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) }; var note2 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) }; @@ -153,7 +150,7 @@ public void UpdateUnconventionalPoco() { // Create the UnconventionalPocos table DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos') - DROP TABLE dbo.[TBL_UnconventionalPocos] + DROP TABLE dbo.[TBL_UnconventionalPocos] CREATE TABLE dbo.[TBL_UnconventionalPocos] ( [PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY, @@ -186,7 +183,7 @@ public void UpdateConventionalUnconventionalPoco() { // Create the UnconventionalPocos table DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos') - DROP TABLE dbo.[TBL_UnconventionalPocos] + DROP TABLE dbo.[TBL_UnconventionalPocos] CREATE TABLE dbo.[TBL_UnconventionalPocos] ( [PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY, @@ -238,7 +235,7 @@ public void UpdateAnonymousPocoWithConventionalNaming() { // Create the table for our unknown but conventional POCO DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles') - DROP TABLE dbo.[XFiles] + DROP TABLE dbo.[XFiles] CREATE TABLE dbo.[XFiles] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, @@ -262,8 +259,8 @@ [FileName] VARCHAR(255) NOT NULL var clone = DB.Query("SELECT * FROM [XFiles] WHERE [Id] = @Id", new { Id = id }).Single(); // See, they are the same - id.ShouldBe((int)clone.Id); - xfile.FileName.ShouldBe((string)clone.FileName); + id.ShouldBe((int) clone.Id); + xfile.FileName.ShouldBe((string) clone.FileName); } [Fact] @@ -271,7 +268,7 @@ public void UpdateDynamicUnknownPocoWithConventionalNaming() { // Create the table for our unknown but conventional POCO DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles') - DROP TABLE dbo.[XFiles] + DROP TABLE dbo.[XFiles] CREATE TABLE dbo.[XFiles] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, @@ -296,8 +293,8 @@ [FileName] VARCHAR(255) NOT NULL var clone = DB.Query("SELECT * FROM [XFiles] WHERE [Id] = @Id", new { Id = id }).Single(); // See, they are the same - id.ShouldBe((int)clone.Id); - ((string)xfile.FileName).ShouldBe((string)clone.FileName); + id.ShouldBe((int) clone.Id); + ((string) xfile.FileName).ShouldBe((string) clone.FileName); } } } diff --git a/PetaPoco.Tests.Unit/Core/EnumMapperTest.cs b/PetaPoco.Tests.Unit/Core/EnumMapperTest.cs index 4a782185..ad2295cc 100644 --- a/PetaPoco.Tests.Unit/Core/EnumMapperTest.cs +++ b/PetaPoco.Tests.Unit/Core/EnumMapperTest.cs @@ -38,7 +38,7 @@ public void Mapping_NonExistingEnum_ThrowsMeaningfulException() return; } - Assert.False(true, "Expedted InvalidOperationException"); + Assert.False(true, "Expected InvalidOperationException"); } private enum FakeEnum @@ -46,4 +46,4 @@ private enum FakeEnum ExistingValue } } -} \ No newline at end of file +} diff --git a/PetaPoco.Tests.Unit/Core/PocoDataTests.cs b/PetaPoco.Tests.Unit/Core/PocoDataTests.cs index 5d258781..189e8634 100644 --- a/PetaPoco.Tests.Unit/Core/PocoDataTests.cs +++ b/PetaPoco.Tests.Unit/Core/PocoDataTests.cs @@ -9,7 +9,7 @@ namespace PetaPoco.Tests.Unit.Core public class PocoDataTests { [Fact] - public void GetFactory_GivenTypeWithNonPublicConstructor_ShouldThrow() + public void GetFactory_GivenTypeWithNoPublicConstructor_ShouldThrow() { var pd = PocoData.ForObject(TestEntity.Instance, "Id", new ConventionMapper()); Should.Throw(() => pd.GetFactory("", "", 1, 1, null, null)); diff --git a/PetaPoco.Tests.Unit/DatabaseConfigurationTests.cs b/PetaPoco.Tests.Unit/DatabaseConfigurationTests.cs index d319fbec..b269befb 100644 --- a/PetaPoco.Tests.Unit/DatabaseConfigurationTests.cs +++ b/PetaPoco.Tests.Unit/DatabaseConfigurationTests.cs @@ -36,7 +36,7 @@ public void SetSetting_GivenKeyAndNull_ShouldRemoveSetting() ((IBuildConfigurationSettings) config).SetSetting("key", null); var getCalled = false; - ((IBuildConfigurationSettings) config).TryGetSetting("key", v => { getCalled = true; }); + ((IBuildConfigurationSettings) config).TryGetSetting("key", v => getCalled = true); getCalled.ShouldBeFalse(); } @@ -55,7 +55,7 @@ public void TryGetSetting_GivenKeyAndValue_ShouldGetSetting() public void TryGetSetting_GivenKeyThatDoesNotMatchValue_ShouldNotCallback() { var getCalled = false; - ((IBuildConfigurationSettings) config).TryGetSetting("key", v => { getCalled = true; }); + ((IBuildConfigurationSettings) config).TryGetSetting("key", v => getCalled = true); getCalled.ShouldBeFalse(); } @@ -72,7 +72,7 @@ public void TryGetSetting_GivenNullKey_Throws() } [Fact] - public void TryGetSetting_GivenNullCallback_Throws() + public void TryGetSetting_GivenNullAction_Throws() { ((IBuildConfigurationSettings) config).SetSetting("key", "value"); Should.Throw(() => ((IBuildConfigurationSettings) config).TryGetSetting("key", null)); @@ -287,7 +287,7 @@ public void UsingConnectionOpening_AfterCreate_InstanceShouldHaveDelegate() (db as Database).OnConnectionOpening(null); eventFired.ShouldBeTrue(); } - + [Fact] public void UsingConnectionOpened_AfterCreate_InstanceShouldHaveDelegate() { @@ -383,4 +383,4 @@ public void UsingConnectionWithProviderName_AfterCreate_InstanceShouldBeValid() } } } -} \ No newline at end of file +} diff --git a/PetaPoco.Tests.Unit/DatabaseTests.cs b/PetaPoco.Tests.Unit/DatabaseTests.cs index b16ffa78..4185a75c 100644 --- a/PetaPoco.Tests.Unit/DatabaseTests.cs +++ b/PetaPoco.Tests.Unit/DatabaseTests.cs @@ -157,7 +157,12 @@ public void IsNew_GivenInvalidArguments_ShouldThrow() { Should.Throw(() => DB.IsNew(null)); Should.Throw(() => DB.IsNew(null, null)); + Should.Throw(() => DB.IsNew(null, default)); + Should.Throw(() => DB.IsNew(null, null)); + Should.Throw(() => DB.IsNew("PrimaryKey", null)); + Should.Throw(() => DB.IsNew(string.Empty, null)); + Should.Throw(() => DB.IsNew(string.Empty, default)); Should.Throw(() => DB.IsNew("MissingId", new { })); Should.Throw(() => DB.IsNew(new TransactionLog())); diff --git a/PetaPoco/Attributes/ColumnAttribute.cs b/PetaPoco/Attributes/ColumnAttribute.cs index b3cd197e..0345c2b3 100644 --- a/PetaPoco/Attributes/ColumnAttribute.cs +++ b/PetaPoco/Attributes/ColumnAttribute.cs @@ -3,61 +3,88 @@ namespace PetaPoco { /// - /// Represents an attribute which can decorate a POCO property to mark the property as a column. It may also optionally - /// supply the DB column name. + /// The ColumnAttribute class defines an attribute for POCO properties that map to a database column. /// + /// + /// This attribute can be used to override the default mapped column name, provide hints to PetaPoco for how to treat DateTime and + /// String columns, and further customize INSERT and UPDATE operations with the use of string templates. + /// [AttributeUsage(AttributeTargets.Property)] public class ColumnAttribute : Attribute { /// - /// The SQL name of the column + /// Gets or sets the database column name this property maps to. /// + /// When not , overrides this property's inflected column name from the mapper. public string Name { get; set; } /// - /// True if time and date values returned through this column should be forced to UTC DateTimeKind. (no conversion is - /// applied - the Kind of the DateTime property - /// is simply set to DateTimeKind.Utc instead of DateTimeKind.Unknown. + /// Gets or sets whether the column is of type (SQL DB data type VARCHAR). /// - public bool ForceToUtc { get; set; } + /// + /// For use with properties. This property is implicitly for properties of type . + /// + public bool ForceToAnsiString { get; set; } /// - /// The insert template. If not null, this template is used for generating the insert section instead of the default - /// string.Format("{0}{1}", paramPrefix, index"). Setting this allows DB related interactions, such as "CAST({0}{1} AS - /// json)" + /// Gets or sets whether the column is of type . /// - public string InsertTemplate { get; set; } + /// + /// For use with properties. This property is implicitly for properties of type . + /// + public bool ForceToDateTime2 { get; set; } /// - /// The update template. If not null, this template is used for generating the update section instead of the default - /// string.Format("{0} = {1}{2}", colName, paramPrefix, index"). Setting this allows DB related interactions, such as - /// "{0} = CAST({1}{2} AS - /// json)" + /// Gets or sets whether the DbType.DateTime or DbType.DateTime2 values in this DB column are always UTC. /// - public string UpdateTemplate { get; set; } + /// + /// No conversion is applied - the of the POCO property's underlying value is + /// simply set to correctly reflect the UTC timezone as an invariant. + /// + /// If , the underlying 's property is set to Utc; otherwise, the default is used (Unspecified). + public bool ForceToUtc { get; set; } + /// - /// True if AnsiString / VARCHAR column + /// Gets or sets the INSERT string template. /// - public bool ForceToAnsiString { get; set; } + /// + /// When set, this template is used for generating the INSERT portion of the SQL statement instead of the default + ///
String.Format("{0}{1}", paramPrefix, index). + /// Setting this allows database-related interactions, such as: + ///
String.Format("CAST({0}{1} AS JSON)", paramPrefix, index).
+ ///
+ public string InsertTemplate { get; set; } + /// - /// True if DateTime2 column + /// Gets or sets the UPDATE string template. /// - public bool ForceToDateTime2 { get; set; } + /// + /// When set, this template is used for generating the UPDATE portion of the SQL statement instead of the default + ///
String.Format("{0} = {1}{2}", colName, paramPrefix, index). + /// Setting this allows database-related interactions, such as: + ///
String.Format("{0} = CAST({1}{2} AS JSON)", colName, paramPrefix, index)
+ ///
+ public string UpdateTemplate { get; set; } /// - /// Constructs a new instance of the . + /// Initializes a new instance of the class with default values. /// public ColumnAttribute() { } /// - /// Constructs a new instance of the . + /// Initializes a new instance of the class with the specified column name. /// - /// The name of the column. - public ColumnAttribute(string name) + /// The name of the database column associated with this property. + public ColumnAttribute(string column) { - Name = name; + Name = column; } } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/ExplicitColumnsAttribute.cs b/PetaPoco/Attributes/ExplicitColumnsAttribute.cs index 70e1400f..053b54b6 100644 --- a/PetaPoco/Attributes/ExplicitColumnsAttribute.cs +++ b/PetaPoco/Attributes/ExplicitColumnsAttribute.cs @@ -3,11 +3,19 @@ namespace PetaPoco { /// - /// Represents the attribute which decorates a POCO class to state all columns must be explicitly mapped using either a - /// or . + /// The ExplicitColumnsAttribute class defines an attribute for POCO classes specifying that only explicitly-marked properties should be + /// mapped to columns in the database table. /// + /// + /// When using this attribute, any properties not decorated with one of the following attributes are ignored by the mapper: + /// + /// + /// + /// + /// + /// [AttributeUsage(AttributeTargets.Class)] public class ExplicitColumnsAttribute : Attribute { } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/IgnoreAttribute.cs b/PetaPoco/Attributes/IgnoreAttribute.cs index 24cbb499..975a27ee 100644 --- a/PetaPoco/Attributes/IgnoreAttribute.cs +++ b/PetaPoco/Attributes/IgnoreAttribute.cs @@ -3,11 +3,17 @@ namespace PetaPoco { /// - /// Represents an attribute which can decorate a POCO property to ensure PetaPoco does not map column, and therefore - /// ignores the column. + /// The IgnoreAttribute class defines an attribute for POCO properties that should be explicitly ignored by the mapper. /// + /// + /// Properties decorated with this attribute are completely ignored by PetaPoco, and do not participate in any database-related + /// operations. + /// If you find yourself using this attribute excessively, consider instead decorating your POCO class with the , and then marking the properties you want mapped with their appropriate column attribute. + /// + /// [AttributeUsage(AttributeTargets.Property)] public class IgnoreAttribute : Attribute { } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/PrimaryKeyAttribute.cs b/PetaPoco/Attributes/PrimaryKeyAttribute.cs index c3da4c15..0c5328f9 100644 --- a/PetaPoco/Attributes/PrimaryKeyAttribute.cs +++ b/PetaPoco/Attributes/PrimaryKeyAttribute.cs @@ -1,43 +1,38 @@ -using System; +using System; namespace PetaPoco { /// - /// Is an attribute, which when applied to a POCO class, specifies primary key column. Additionally, specifies whether - /// the column is auto incrementing and the optional sequence name for Oracle sequence columns. + /// The PrimaryKeyAttribute class defines an attribute for POCO properties that map to primary key columns in the database. /// + /// + /// The PrimaryKeyAttribute, when used in a POCO class, designates the decorated property as the primary key column in the database. It + /// can also be used to override the default mapped column name for the primary key, mark the column as auto-incrementing, and + /// optionally assign a sequence name for Oracle sequence columns. + /// [AttributeUsage(AttributeTargets.Class)] public class PrimaryKeyAttribute : Attribute { /// - /// The column name. + /// Gets the column name in the database. /// - /// - /// The column name. - /// public string Value { get; } /// - /// The sequence name. + /// Gets or sets the optional sequence name, for Oracle databases. /// - /// - /// The sequence name. - /// public string SequenceName { get; set; } /// - /// A flag which specifies if the primary key is auto incrementing. + /// Gets or sets whether the primary key column represented by this property in the database is auto-incrementing. + /// Default is . /// - /// - /// True if the primary key is auto incrementing; else, False. - /// public bool AutoIncrement { get; set; } = true; /// - /// Constructs a new instance of the . + /// Initializes a new instance of the class with the specified column name. /// - /// The name of the primary key column. - public PrimaryKeyAttribute(string primaryKey) - => Value = primaryKey; + /// The database table's primary key column name that this property maps to. + public PrimaryKeyAttribute(string primaryKeyName) => Value = primaryKeyName; } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/ResultColumnAttribute.cs b/PetaPoco/Attributes/ResultColumnAttribute.cs index c6a7aea5..88a0cf40 100644 --- a/PetaPoco/Attributes/ResultColumnAttribute.cs +++ b/PetaPoco/Attributes/ResultColumnAttribute.cs @@ -2,39 +2,79 @@ namespace PetaPoco { + /// + /// Specifies whether a property should be included in SQL auto-select queries. + /// public enum IncludeInAutoSelect { + /// + /// The property should not be included in auto-select queries. + /// No, + + /// + /// The property should be included in auto-select queries. + /// Yes } /// - /// Represents an attribute which can decorate a POCO property as a result only column. A result only column is a - /// column that is only populated in queries and is not used for updates or inserts operations. + /// The ResultColumnAttribute class defines an attribute for POCO properties that should be used in SQL queries only, but ignored + /// in UPDATE and INSERT operations. /// + /// + /// This attribute marks properties that are mapped to a column, but should not be involved in any mutating operations on the database. + /// The property value is updated by queries from the database, but the database column it maps to will not be changed to reflect + /// changes in this property. + /// Use the property to dictate whether the decorated property is included in operations using + /// PetaPoco's AutoSelect feature (default is ). Auto-select queries are operations made using the + /// "short form" syntax, without specifying the SELECT [cols] FROM [table] portion of the SQL statement. The inferred portion of + /// the SQL statement is then automatically generated by PetaPoco based on generics or the called method's parameters. + /// [AttributeUsage(AttributeTargets.Property)] public class ResultColumnAttribute : ColumnAttribute { + /// + /// Gets or sets a value indicating whether the property should be included in auto-select queries. + /// Default is . + /// public IncludeInAutoSelect IncludeInAutoSelect { get; set; } + /// + /// Initializes a new instance of the class with default values. + /// public ResultColumnAttribute() { } - public ResultColumnAttribute(string name) - : this(name, IncludeInAutoSelect.No) + /// + /// Initializes a new instance of the class with the specified column name. + /// + /// The name of the database column associated with this property. + public ResultColumnAttribute(string column) + : this(column, IncludeInAutoSelect.No) { } + /// + /// Initializes a new instance of the class with the specified auto-select inclusion setting. + /// + /// Specifies whether the property should be included in auto-select queries. public ResultColumnAttribute(IncludeInAutoSelect includeInAutoSelect) : this(null, includeInAutoSelect) { } - public ResultColumnAttribute(string name, IncludeInAutoSelect includeInAutoSelect) - : base(name) + /// + /// Initializes a new instance of the class with the specified column name and auto-select + /// inclusion setting. + /// + /// The name of the database column associated with this property. + /// Specifies whether the property should be included in auto-select queries. + public ResultColumnAttribute(string column, IncludeInAutoSelect includeInAutoSelect) + : base(column) { IncludeInAutoSelect = includeInAutoSelect; } } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/TableNameAttribute.cs b/PetaPoco/Attributes/TableNameAttribute.cs index b3e3eccb..c954ff72 100644 --- a/PetaPoco/Attributes/TableNameAttribute.cs +++ b/PetaPoco/Attributes/TableNameAttribute.cs @@ -3,24 +3,24 @@ namespace PetaPoco { /// - /// Represents an attribute, which when applied to a POCO class, specifies the the DB table name which it maps to + /// The TableNameAttribute class defines an attribute for POCO classes to specify a custom database table name that class should map to. /// + /// + /// When decorating a class, the provided table name overrides the default inflected name of the active mapper. + /// [AttributeUsage(AttributeTargets.Class)] public class TableNameAttribute : Attribute { /// - /// The table nane of the database that this entity maps to. + /// Gets the database table name this POCO maps to. /// - /// - /// The table nane of the database that this entity maps to. - /// + /// Overrides this class's inflected table name from the mapper. public string Value { get; } /// - /// Constructs a new instance of the . + /// Initializes a new instance of the class with the specified table name. /// - /// The table nane of the database that this entity maps to. - public TableNameAttribute(string tableName) - => Value = tableName; + /// The database table name this class maps to. + public TableNameAttribute(string tableName) => Value = tableName; } -} \ No newline at end of file +} diff --git a/PetaPoco/Attributes/ValueConverterAttribute.cs b/PetaPoco/Attributes/ValueConverterAttribute.cs index bf017eca..f61a81f1 100644 --- a/PetaPoco/Attributes/ValueConverterAttribute.cs +++ b/PetaPoco/Attributes/ValueConverterAttribute.cs @@ -3,25 +3,29 @@ namespace PetaPoco { /// - /// Represents an attribute which can decorate a POCO property to provide - /// functions to convert a value from database type to property type and - /// vice versa. + /// The ValueConverterAttribute class provides a base class all ValueConverters must derive from and implement. /// + /// + /// ValueConverters are used to implement custom two-way conversions between your POCO property data type, and the mapped database + /// column's data type. They are ideal for implementing a custom conversion without requiring any changes to the mapper. + /// To provide a custom ValueConverter for a property, inherit from this class, and supply definitions for both conversion methods + /// for your data type. Decorate the appropriate properties that require your ValueConverter with your derived class. + /// [AttributeUsage(AttributeTargets.Property)] public abstract class ValueConverterAttribute : Attribute { /// - /// Function to convert property value to database type value. + /// Converts the given from the database type to your POCO's property type. /// - /// Property value - /// Converted database value - public abstract object ConvertToDb(object value); + /// The database value to be converted. + /// The converted property value. + public abstract object ConvertFromDb(object value); /// - /// Function to convert database value to property type value. + /// Converts the given from your POCO's property type to the database type. /// - /// Database value - /// Converted property type value - public abstract object ConvertFromDb(object value); + /// The property value to be converted. + /// The converted database value. + public abstract object ConvertToDb(object value); } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/AnsiString.cs b/PetaPoco/Core/AnsiString.cs index 3078cda3..3e2d97dd 100644 --- a/PetaPoco/Core/AnsiString.cs +++ b/PetaPoco/Core/AnsiString.cs @@ -1,22 +1,30 @@ -namespace PetaPoco -{ - /// - /// Wrap strings in an instance of this class to force use of DBType.AnsiString - /// - public class AnsiString - { - /// - /// The string value - /// - public string Value { get; } - - /// - /// Constructs an AnsiString - /// - /// The C# string to be converted to ANSI before being passed to the DB - public AnsiString(string str) - => Value = str; - - public static explicit operator AnsiString(string s) => new AnsiString(s); - } -} \ No newline at end of file +namespace PetaPoco +{ + /// + /// Wraps a Unicode string that will be stored in a VARCHAR DB column as an . + /// + /// + /// Using this type for a column-mapped POCO property is equivalent to decorating a property with . + /// + public class AnsiString + { + /// + /// Gets the Unicode string value wrapped by this instance. + /// + public string Value { get; } + + /// + /// Initializes a new instance of the class with the specified string. + /// + /// The string to be stored in the database as an . + public AnsiString(string value) => Value = value; + + /// + /// Explicitly converts a Unicode string to an instance. + /// + /// The string to convert. + /// An AnsiString instance containing the wrapped value. + public static explicit operator AnsiString(string value) => new AnsiString(value); + } +} diff --git a/PetaPoco/Core/AnsiStringExtensions.cs b/PetaPoco/Core/AnsiStringExtensions.cs index b1593f3e..765d14b6 100644 --- a/PetaPoco/Core/AnsiStringExtensions.cs +++ b/PetaPoco/Core/AnsiStringExtensions.cs @@ -1,18 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PetaPoco +namespace PetaPoco { + /// + /// Provides extension methods for the class. + /// public static class AnsiStringExtensions { /// - /// Converts an object to its representation + /// Converts an object to its representation. /// - /// The object to be converted - /// - public static AnsiString ToAnsiString(this object o) => new AnsiString(o.ToString()); + /// The object to be converted. + /// An AnsiString object that wraps the given . + public static AnsiString ToAnsiString(this object value) => new AnsiString(value.ToString()); } } diff --git a/PetaPoco/Core/ColumnInfo.cs b/PetaPoco/Core/ColumnInfo.cs index e156c4e0..35722e3c 100644 --- a/PetaPoco/Core/ColumnInfo.cs +++ b/PetaPoco/Core/ColumnInfo.cs @@ -1,106 +1,153 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace PetaPoco -{ - /// - /// Holds information about a column in the database. - /// - /// - /// Typically ColumnInfo is automatically populated from the attributes on a POCO object and its properties. It can - /// however also be returned from the IMapper interface to provide your own bindings between the DB and your POCOs. - /// - public class ColumnInfo - { - /// - /// The SQL name of the column - /// - public string ColumnName { get; set; } - - /// - /// True if this column returns a calculated value from the database and shouldn't be used in Insert and Update - /// operations. - /// - public bool ResultColumn { get; set; } - - /// - /// True if this is a result column but should be included in auto select queries. - /// - public bool AutoSelectedResultColumn { get; set; } - - /// - /// True if time and date values returned through this column should be forced to UTC DateTimeKind. (no conversion is - /// applied - the Kind of the DateTime property - /// is simply set to DateTimeKind.Utc instead of DateTimeKind.Unknown. - /// - public bool ForceToUtc { get; set; } - /// - /// True if Database Column is DbType.AnsiString (like VARCHAR) - /// - public bool ForceToAnsiString { get; set; } - /// - /// True if Database Colume is DbType.DateTime2 - /// - public bool ForceToDateTime2 { get; set; } - - /// - /// The insert template. If not null, this template is used for generating the insert section instead of the deafult - /// string.Format("{0}{1}", paramPrefix, index"). Setting this allows DB related interactions, such as "CAST({0}{1} AS - /// json)" - /// - public string InsertTemplate { get; set; } - - /// - /// The update template. If not null, this template is used for generating the update section instead of the deafult - /// string.Format("{0} = {1}{2}", colName, paramPrefix, index"). Setting this allows DB related interactions, such as - /// "{0} = CAST({1}{2} AS - /// json)" - /// - public string UpdateTemplate { get; set; } - - internal static void PopulateFromProperty(PropertyInfo pi, ref ColumnInfo ci, out ColumnAttribute columnAttr) - { - // Check if declaring poco has [Explicit] attribute - var isExplicit = pi.DeclaringType.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Any(); - - // Check for [Column]/[Ignore] Attributes - columnAttr = Attribute.GetCustomAttributes(pi, typeof(ColumnAttribute)).FirstOrDefault() as ColumnAttribute; - var isIgnore = Attribute.GetCustomAttributes(pi, typeof(IgnoreAttribute)).Any(); - - if (isIgnore || (isExplicit && columnAttr == null)) - { - ci = null; - } - else - { - ci = ci ?? new ColumnInfo(); - - ci.ColumnName = columnAttr?.Name ?? pi.Name; - ci.ForceToUtc = columnAttr?.ForceToUtc == true; - ci.ForceToAnsiString = columnAttr?.ForceToAnsiString == true; - ci.ForceToDateTime2 = columnAttr?.ForceToDateTime2 == true; - ci.InsertTemplate = columnAttr?.InsertTemplate; - ci.UpdateTemplate = columnAttr?.UpdateTemplate; - - if (columnAttr is ResultColumnAttribute resAttr) - { - ci.ResultColumn = true; - ci.AutoSelectedResultColumn = resAttr.IncludeInAutoSelect == IncludeInAutoSelect.Yes; - } - } - } - - /// - /// Creates and populates a ColumnInfo from the attributes of a POCO property. - /// - /// The property whose column info is required - /// A ColumnInfo instance - public static ColumnInfo FromProperty(PropertyInfo propertyInfo) - { - var ci = new ColumnInfo(); - PopulateFromProperty(propertyInfo, ref ci, out _); - return ci; - } - } -} \ No newline at end of file +using System; +using System.Linq; +using System.Reflection; + +namespace PetaPoco +{ + /// + /// The ColumnInfo class stores information about a column in the database. + /// + /// + /// Typically ColumnInfo is automatically populated from the attributes on a POCO object and its properties. It can, however, also be + /// returned from the interface allowing you to provide your own custom bindings between the DB and your POCOs. + /// + /// + /// + public class ColumnInfo + { + /// + /// Gets or sets the database column name this property maps to. + /// + /// When not , overrides this property's inflected column name from the mapper. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets whether this column represents a property that should be updated in queries that include a user-supplied + /// SELECT statement, but ignored in queries generated by auto-select. Result columns are always ignored in UPDATE and + /// INSERT operations. + /// + /// If , this property will be updated in SQL query operations containing a user-supplied SELECT + /// statement, and ignored for all other database operations. + /// + public bool ResultColumn { get; set; } + + /// + /// Gets or sets whether this serves as a ResultColumn that is included with auto-select queries as well as queries containing + /// user-supplied SELECT statements. + /// + /// If , this property will be updated in all SQL queries, but ignored for all other database + /// operations such as INSERT and UPDATE. + /// + public bool AutoSelectedResultColumn { get; set; } + + /// + /// Gets or sets a value indicating whether the column's data type should be treated as . + /// + /// + /// For use with properties. This property is implicitly for properties of type . + /// + /// If , the column's data type is assumed to be DbType.AnsiString (equivalent to the DB data type VARCHAR). + /// + /// + public bool ForceToAnsiString { get; set; } + + /// + /// Gets or sets a value indicating whether the column containing a DateTime data type should be treated as DbType.DateTime. + /// + /// + /// For use with properties. This property is implicitly for properties of type . + /// + /// If , the column's data type is assumed to be DbType.DateTime. + /// + /// + public bool ForceToDateTime2 { get; set; } + + /// + /// Gets or sets a value indicating whether the DateTime value of this column should be treated as UTC. + /// + /// + /// For use with or properties. No conversion is applied - the of the POCO property's underlying value is simply set to correctly reflect the UTC + /// timezone as an invariant. + /// + /// If , the DateTime value is assumed to be in UTC. If , the property will be assigned . + /// + /// + public bool ForceToUtc { get; set; } + + /// + /// Gets or sets the template used for INSERT operations. + /// + /// + /// When set, this template is used for generating the INSERT portion of the SQL statement instead of the default + ///
String.Format("{0}{1}", paramPrefix, index). + /// Setting this allows database-related interactions, such as: + ///
String.Format("CAST({0}{1} AS JSON)", paramPrefix, index).
+ ///
+ /// + public string InsertTemplate { get; set; } + + /// + /// Gets or sets the template used for UPDATE operations. + /// + /// + /// When set, this template is used for generating the UPDATE portion of the SQL statement instead of the default + ///
String.Format("{0} = {1}{2}", colName, paramPrefix, index). + /// Setting this allows database-related interactions, such as: + ///
String.Format("{0} = CAST({1}{2} AS JSON)", colName, paramPrefix, index)
+ ///
+ /// + public string UpdateTemplate { get; set; } + + /// + /// Creates and populates a ColumnInfo from the attributes of a POCO property. + /// + /// The POCO property to use for initializing the ColumnInfo. + /// A ColumnInfo instance. + public static ColumnInfo FromProperty(PropertyInfo propertyInfo) + { + var ci = new ColumnInfo(); + PopulateFromProperty(propertyInfo, ref ci, out _); + return ci; + } + + internal static void PopulateFromProperty(PropertyInfo pi, ref ColumnInfo ci, out ColumnAttribute columnAttr) + { + // Check if declaring poco has [Explicit] attribute + var isExplicit = pi.DeclaringType.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Any(); + + // Check for [Column]/[Ignore] Attributes + columnAttr = Attribute.GetCustomAttributes(pi, typeof(ColumnAttribute)).FirstOrDefault() as ColumnAttribute; + var isIgnore = Attribute.GetCustomAttributes(pi, typeof(IgnoreAttribute)).Any(); + + if (isIgnore || (isExplicit && columnAttr == null)) + { + ci = null; + } + else + { + ci = ci ?? new ColumnInfo(); + + ci.ColumnName = columnAttr?.Name ?? pi.Name; + ci.ForceToAnsiString = columnAttr?.ForceToAnsiString == true; + ci.ForceToDateTime2 = columnAttr?.ForceToDateTime2 == true; + ci.ForceToUtc = columnAttr?.ForceToUtc == true; + ci.InsertTemplate = columnAttr?.InsertTemplate; + ci.UpdateTemplate = columnAttr?.UpdateTemplate; + + if (columnAttr is ResultColumnAttribute resAttr) + { + ci.ResultColumn = true; + ci.AutoSelectedResultColumn = resAttr.IncludeInAutoSelect == IncludeInAutoSelect.Yes; + } + } + } + } +} diff --git a/PetaPoco/Core/ConventionMapper.cs b/PetaPoco/Core/ConventionMapper.cs index aec12119..71201e1f 100644 --- a/PetaPoco/Core/ConventionMapper.cs +++ b/PetaPoco/Core/ConventionMapper.cs @@ -6,61 +6,60 @@ namespace PetaPoco { /// - /// Represents a configurable convention mapper. + /// The ConventionMapper class represents a configurable convention mapper. /// /// - /// By default this mapper replaces without change, which means backwards compatibility - /// is kept. + /// By default this mapper replaces the original without change, ensuring backwards compatibility. /// public class ConventionMapper : IMapper { /// - /// Gets or sets the get sequence name logic. + /// Gets or sets the sequence name logic (for Oracle). /// public Func GetSequenceName { get; set; } /// - /// Gets or sets the inflect column name logic. + /// Gets or sets the inflected column name logic. /// public Func InflectColumnName { get; set; } /// - /// Gets or sets the inflect table name logic. + /// Gets or sets the inflected table name logic. /// public Func InflectTableName { get; set; } /// - /// Gets or sets the is primary key auto increment logic. + /// Gets or sets the primary key auto-increment logic. /// public Func IsPrimaryKeyAutoIncrement { get; set; } /// - /// Gets or sets the map column logic. + /// Gets or sets the map column logic. /// public Func MapColumn { get; set; } /// - /// Gets or set the map primary key logic. + /// Gets or sets the map primary key logic. /// public Func MapPrimaryKey { get; set; } /// - /// Gets or sets the map table logic. + /// Gets or sets the map table logic. /// public Func MapTable { get; set; } /// - /// Gets or sets the from db convert logic. + /// Gets or sets the from db convert logic. /// public Func> FromDbConverter { get; set; } /// - /// Gets or sets the to db converter logic. + /// Gets or sets the to db converter logic. /// public Func> ToDbConverter { get; set; } /// - /// Constructs a new instance of convention mapper. + /// Constructs a new instance of convention mapper. /// public ConventionMapper() { @@ -113,12 +112,12 @@ public ConventionMapper() MapColumn = (ci, t, pi) => { ColumnInfo.PopulateFromProperty(pi, ref ci, out var columnAttr); - + if (ci == null) return false; else { - // If there's no colAttr.Name, then we got the name + // If there's no colAttr.Name, then we got the name // from pi, so inflect it if (columnAttr?.Name == null) ci.ColumnName = InflectColumnName(Inflector.Instance, pi.Name); @@ -150,59 +149,30 @@ public ConventionMapper() }; } - /// - /// Get information about the table associated with a POCO class - /// - /// The poco type. - /// A TableInfo instance - /// - /// This method must return a valid TableInfo. - /// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco - /// + /// public virtual TableInfo GetTableInfo(Type pocoType) { var ti = new TableInfo(); return MapTable(ti, pocoType) ? ti : null; } - /// - /// Get information about the column associated with a property of a POCO - /// - /// The PropertyInfo of the property being queried - /// A reference to a ColumnInfo instance, or null to ignore this property - /// - /// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty - /// + /// public virtual ColumnInfo GetColumnInfo(PropertyInfo pocoProperty) { var ci = new ColumnInfo(); return MapColumn(ci, pocoProperty.DeclaringType, pocoProperty) ? ci : null; } - /// - /// Supply a function to convert a database value to the correct property value - /// - /// The target property - /// The type of data returned by the DB - /// A Func that can do the conversion, or null for no conversion + /// public virtual Func GetFromDbConverter(PropertyInfo targetProperty, Type sourceType) { return FromDbConverter?.Invoke(targetProperty, sourceType); } - /// - /// Supply a function to convert a property value into a database value - /// - /// The property to be converted - /// A Func that can do the conversion - /// - /// This conversion is only used for converting values from POCOs that are - /// being Inserted or Updated. - /// Conversion is not available for parameter values passed directly to queries. - /// + /// public virtual Func GetToDbConverter(PropertyInfo sourceProperty) { return ToDbConverter?.Invoke(sourceProperty); } } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/DatabaseProvider.cs b/PetaPoco/Core/DatabaseProvider.cs index e083c080..65911993 100644 --- a/PetaPoco/Core/DatabaseProvider.cs +++ b/PetaPoco/Core/DatabaseProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Data; using System.Data.Common; @@ -14,48 +14,41 @@ namespace PetaPoco.Core { /// - /// Base class for DatabaseType handlers - provides default/common handling for different database engines + /// The DatabaseProvider class defines a base implementation for database Provider classes, and provides common defaults used by + /// different database management systems. /// public abstract class DatabaseProvider : IProvider { private static readonly ConcurrentDictionary customProviders = new ConcurrentDictionary(); - /// + /// public abstract DbProviderFactory GetFactory(); - /// + /// public virtual bool HasNativeGuidSupport => false; - /// + /// public virtual IPagingHelper PagingUtility => PagingHelper.Instance; - /// + /// public virtual string EscapeTableName(string tableName) => tableName.IndexOf('.') >= 0 ? tableName : EscapeSqlIdentifier(tableName); - /// - public virtual string EscapeSqlIdentifier(string sqlIdentifier) - => $"[{sqlIdentifier}]"; + /// + public virtual string EscapeSqlIdentifier(string sqlIdentifier) => $"[{sqlIdentifier}]"; - /// - public virtual string GetParameterPrefix(string connectionString) - => "@"; + /// + public virtual string GetParameterPrefix(string connectionString) => "@"; - /// - public virtual object MapParameterValue(object value) - { - if (value is bool b) - return b ? 1 : 0; - - return value; - } + /// + public virtual object MapParameterValue(object value) => value is bool b ? b ? 1 : 0 : value; - /// + /// public virtual void PreExecute(IDbCommand cmd) { } - /// + /// public virtual string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) { var sql = $"{parts.Sql}\nLIMIT @{args.Length} OFFSET @{args.Length + 1}"; @@ -63,39 +56,37 @@ public virtual string BuildPageQuery(long skip, long take, SQLParts parts, ref o return sql; } - /// - public virtual string GetExistsSql() - => "SELECT COUNT(*) FROM {0} WHERE {1}"; + /// + public virtual string GetExistsSql() => "SELECT COUNT(*) FROM {0} WHERE {1}"; - /// - public virtual string GetAutoIncrementExpression(TableInfo tableInfo) - => null; + /// + public virtual string GetAutoIncrementExpression(TableInfo tableInfo) => null; - /// - public virtual string GetInsertOutputClause(string primaryKeyName) - => string.Empty; + /// + public virtual string GetInsertOutputClause(string primaryKeyName) => string.Empty; - /// - public virtual object ExecuteInsert(Database database, IDbCommand cmd, string primaryKeyName) + /// + public virtual object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - return ExecuteScalarHelper(database, cmd); + return ExecuteScalarHelper(db, cmd); } #if ASYNC - public virtual Task ExecuteInsertAsync(CancellationToken cancellationToken, Database database, IDbCommand cmd, string primaryKeyName) + /// + public virtual Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - return ExecuteScalarHelperAsync(cancellationToken, database, cmd); + return ExecuteScalarHelperAsync(cancellationToken, db, cmd); } #endif /// - /// Returns the DbProviderFactory. + /// Returns the provider factory from one or more specified assembly qualified names. /// - /// The assembly qualified name of the provider factory. - /// The db provider factory. - /// Thrown when does not match a type. + /// One or more assembly qualified names of the provider factory. + /// The provider factory. + /// None of the match a type. protected DbProviderFactory GetFactory(params string[] assemblyQualifiedNames) { Type ft = null; @@ -106,6 +97,7 @@ protected DbProviderFactory GetFactory(params string[] assemblyQualifiedNames) break; } + // TODO: Possible incorrect type name used when throwing ArgumentException? if (ft == null) throw new ArgumentException($"Could not load the {GetType().Name} DbProviderFactory."); @@ -113,11 +105,13 @@ protected DbProviderFactory GetFactory(params string[] assemblyQualifiedNames) } /// - /// Registers a custom IProvider with a string that will match the beginning of the name - /// of the provider, DbConnection, or DbProviderFactory. + /// Registers a custom IProvider with a string that will match the beginning of the name of the provider, DbConnection, or + /// DbProviderFactory. /// - /// Type of IProvider to be registered. - /// String to be matched against the beginning of the provider name. + /// The type of IProvider to be registered. + /// The string to be matched against the beginning of the provider name. + /// is null, empty, or consists of only white + /// space. public static void RegisterCustomProvider(string initialString) where T : IProvider, new() { if (String.IsNullOrWhiteSpace(initialString)) @@ -136,32 +130,30 @@ private static IProvider GetCustomProvider(string name) return null; } - internal static void ClearCustomProviders() - => customProviders.Clear(); + internal static void ClearCustomProviders() => customProviders.Clear(); /// - /// Look at the type and provider name being used and instantiate a suitable IProvider instance. + /// Instantiates a suitable IProvider instance based on the specified provider's type. /// - /// The type name. - /// - /// A flag that when set allows the default to be - /// returned if not match is found. - /// + /// The type of provider to be registered. + /// Specifies whether to allow the default to be returned if no + /// matching provider is found. /// The connection string. - /// The database provider. - internal static IProvider Resolve(Type type, bool allowDefault, string connectionString) + /// The resolved database provider. + /// The name cannot be matched to a provider. + internal static IProvider Resolve(Type providerType, bool allowDefault, string connectionString) { - var typeName = type.Name; + var typeName = providerType.Name; // Try using type name first (more reliable) var custom = GetCustomProvider(typeName); if (custom != null) return custom; - if (type.Namespace != null) + if (providerType.Namespace != null) { - if (typeName.Equals("SqlConnection") && type.Namespace.StartsWith("Microsoft.Data") || - type.Namespace.StartsWith("Microsoft.Data") && typeName.Equals("SqlClientFactory")) + if (typeName.Equals("SqlConnection") && providerType.Namespace.StartsWith("Microsoft.Data") || + providerType.Namespace.StartsWith("Microsoft.Data") && typeName.Equals("SqlClientFactory")) return Singleton.Instance; } @@ -200,22 +192,21 @@ internal static IProvider Resolve(Type type, bool allowDefault, string connectio } if (!allowDefault) - throw new ArgumentException($"Could not match `{type.FullName}` to a provider.", nameof(type)); + throw new ArgumentException($"Could not match `{providerType.FullName}` to a provider.", nameof(providerType)); // Assume SQL Server return Singleton.Instance; } /// - /// Look at the type and provider name being used and instantiate a suitable IProvider instance. + /// Instantiates a suitable IProvider instance based on the specified provider name. /// /// The provider name. - /// - /// A flag that when set allows the default to be - /// returned if not match is found. - /// + /// Specifies whether to allow the default to be returned if no + /// matching provider is found. /// The connection string. - /// The database type. + /// The resolved database provider. + /// The name cannot be matched to a provider. internal static IProvider Resolve(string providerName, bool allowDefault, string connectionString) { // Try again with provider name @@ -243,7 +234,8 @@ internal static IProvider Resolve(string providerName, bool allowDefault, string providerName.IndexOf("SqlCeConnection", StringComparison.InvariantCultureIgnoreCase) >= 0) return Singleton.Instance; - if (providerName.IndexOf("Npgsql", StringComparison.InvariantCultureIgnoreCase) >= 0 || providerName.IndexOf("pgsql", StringComparison.InvariantCultureIgnoreCase) >= 0) + if (providerName.IndexOf("Npgsql", StringComparison.InvariantCultureIgnoreCase) >= 0 || + providerName.IndexOf("pgsql", StringComparison.InvariantCultureIgnoreCase) >= 0) return Singleton.Instance; if (providerName.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) @@ -263,8 +255,6 @@ internal static IProvider Resolve(string providerName, bool allowDefault, string return Singleton.Instance; } - - if (!allowDefault) throw new ArgumentException($"Could not match `{providerName}` to a provider.", nameof(providerName)); @@ -273,10 +263,10 @@ internal static IProvider Resolve(string providerName, bool allowDefault, string } /// - /// Unwraps a wrapped . + /// Unwraps the specified wrapped provider factory/>. /// - /// The factory to unwrap. - /// The unwrapped factory or the original factory if no wrapping occurred. + /// The database provider factory to unwrap. + /// The unwrapped factory, or the original factory if no wrapping occurred. internal static DbProviderFactory Unwrap(DbProviderFactory factory) { if (!(factory is IServiceProvider sp)) @@ -292,16 +282,45 @@ internal static DbProviderFactory Unwrap(DbProviderFactory factory) } } + /// + /// Executes a non-query command. + /// + /// The database instance that will execute the SQL command. + /// The SQL command to execute. protected void ExecuteNonQueryHelper(Database db, IDbCommand cmd) => db.ExecuteNonQueryHelper(cmd); + /// + /// Executes a query command and returns the first column of the first row in the result set. + /// + /// The database instance that will execute the SQL command. + /// The SQL command to execute. + /// The first column of the first row in the result set. protected object ExecuteScalarHelper(Database db, IDbCommand cmd) => db.ExecuteScalarHelper(cmd); #if ASYNC + /// + /// Asynchronously executes a non-query command. + /// + /// A cancellation token that can be used to cancel the operation. + /// The database instance that will execute the SQL command. + /// The SQL command to execute. + /// + /// A task that represents the asynchronous operation. + /// protected Task ExecuteNonQueryHelperAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd) => db.ExecuteNonQueryHelperAsync(cancellationToken, cmd); + /// + /// Asynchronously executes a query command and returns the first column of the first row in the result set. + /// + /// A cancellation token that can be used to cancel the operation. + /// The database instance that will execute the SQL command. + /// The SQL command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains the first column of the first row in the result set. + /// protected Task ExecuteScalarHelperAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd) => db.ExecuteScalarHelperAsync(cancellationToken, cmd); #endif } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/DateTime2.cs b/PetaPoco/Core/DateTime2.cs index eb484048..05556170 100644 --- a/PetaPoco/Core/DateTime2.cs +++ b/PetaPoco/Core/DateTime2.cs @@ -3,22 +3,32 @@ namespace PetaPoco { /// - /// Wrap DateTime in an instance of this class to force use of DBType.DateTime2 + /// Wraps a that will be stored in the database as a . /// + /// + /// Using this type for a column-mapped POCO property is equivalent to decorating a DateTime property with . + /// DbType.DateTime2 is a data type used by SQL DBs with a larger date range and + /// fractional second precision than DbType.DateTime. + /// public class DateTime2 { /// - /// The DateTime value + /// Gets the value wrapped by this instance. /// public DateTime Value { get; } /// - /// Constructs an DateTime2 + /// Initializes a new instance of the DateTime2 class with the specified . /// - /// The C# DateTime to be converted to DateTime2 before being passed to the DB - public DateTime2(DateTime dt) - => Value = dt; + /// The DateTime to be stored in the database as a . + public DateTime2(DateTime value) => Value = value; - public static explicit operator DateTime2(DateTime dt) => new DateTime2(dt); + /// + /// Explicitly converts a to a instance. + /// + /// The DateTime value to convert. + /// A DateTime2 instance containing the wrapped value. + public static explicit operator DateTime2(DateTime value) => new DateTime2(value); } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/DateTime2Extensions.cs b/PetaPoco/Core/DateTime2Extensions.cs index 3b2ede2a..7ba1a902 100644 --- a/PetaPoco/Core/DateTime2Extensions.cs +++ b/PetaPoco/Core/DateTime2Extensions.cs @@ -2,19 +2,24 @@ namespace PetaPoco { + /// + /// Provides extension methods for the class. + /// public static class DateTime2Extensions { /// - /// Converts an DateTime to its representation + /// Converts a to its representation. /// - /// The DateTime to be converted - /// - public static DateTime2 ToDateTime2(this DateTime dt) => new DateTime2(dt); + /// The DateTime object to be converted. + /// A object containing the converted . + public static DateTime2 ToDateTime2(this DateTime value) => new DateTime2(value); + /// - /// Parse a string to its representation + /// Parses a string to its representation. /// - /// The string to be converted - /// - public static DateTime2 ToDateTime2(this string inputStr) => new DateTime2(DateTime.Parse(inputStr)); + /// The string representing a date and time to be converted. + /// A object containing the parsed . + /// + public static DateTime2 ToDateTime2(this string value) => new DateTime2(DateTime.Parse(value)); } } diff --git a/PetaPoco/Core/GridReader.cs b/PetaPoco/Core/GridReader.cs index 5766fe16..e78234b5 100644 --- a/PetaPoco/Core/GridReader.cs +++ b/PetaPoco/Core/GridReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -7,6 +7,9 @@ namespace PetaPoco { + /// + /// Provides methods for reading a result set from a database query into an enumerable collection of single or multi-POCO objects. + /// public class GridReader : IGridReader { private readonly Database _db; @@ -15,12 +18,12 @@ public class GridReader : IGridReader private IDataReader _reader; /// - /// The control structure for a multi-result set query + /// Initializes a new instance of the GridReader class with the control structure for a multi-poco query result set. /// - /// - /// - /// - /// + /// The database instance this GridReader is associated with. + /// The database query command to execute. + /// The underlying data reader for reading the result sets. + /// The default mapper to be used for mapping the result sets to POCOs. internal GridReader(Database database, IDbCommand command, IDataReader reader, IMapper defaultMapper) { _db = database; @@ -29,115 +32,60 @@ internal GridReader(Database database, IDbCommand command, IDataReader reader, I _defaultMapper = defaultMapper; } -#region public Read methods + #region Public Read methods - /// - /// Reads from a GridReader, returning the results as an IEnumerable collection - /// - /// The Type representing a row in the result set - /// An enumerable collection of result records + /// public IEnumerable Read() - { - return SinglePocoFromIDataReader(_gridIndex); - } + => SinglePocoFromIDataReader(_gridIndex); - /// - /// Perform a multi-poco read from a GridReader - /// - /// The first POCO type - /// The second POCO type - /// A collection of POCOs as an IEnumerable + /// public IEnumerable Read() - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2) }, null); - } + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2) }, null); - /// - /// Perform a multi-poco read from a GridReader - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// A collection of POCOs as an IEnumerable + /// public IEnumerable Read() - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null); - } + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null); - /// - /// Perform a multi-poco read from a GridReader - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The forth POCO type - /// A collection of POCOs as an IEnumerable + /// public IEnumerable Read() - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null); - } + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - public IEnumerable Read(Func cb) - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2) }, cb); - } + /// + public IEnumerable Read(Func projector) + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2) }, projector); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - public IEnumerable Read(Func cb) - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb); - } + /// + public IEnumerable Read(Func projector) + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, projector); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The forth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - public IEnumerable Read(Func cb) - { - return MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb); - } + /// + public IEnumerable Read(Func projector) + => MultiPocoFromIDataReader(_gridIndex, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, projector); -#endregion + #endregion -#region PocoFromIDataReader + #region PocoFromIDataReader /// - /// Read data to a single poco + /// Reads data to a single POCO. /// - /// The type representing a row in the result set - /// Reader row to be read from the underlying IDataReader - /// + /// The POCO type representing a single result record. + /// The zero-based row index to be read from the underlying . + /// A POCO of type . + /// Called after the data reader has been disposed. + /// Result records are consumed in the incorrect order, or more than once. private IEnumerable SinglePocoFromIDataReader(int index) { + // TODO: Incorrect object name used when throwing ObjectDisposedException; should be `nameof(_reader)` if (_reader == null) throw new ObjectDisposedException(GetType().FullName, "The data reader has been disposed"); if (_consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); + _consumed = true; var pd = PocoData.ForType(typeof(T), _defaultMapper); + try { while (index == _gridIndex) @@ -164,7 +112,7 @@ private IEnumerable SinglePocoFromIDataReader(int index) } } } - finally // finally so that First etc progresses things even when multiple rows + finally // Ensure that calls to .First() etc progresses the data reader when there are multiple rows { if (index == _gridIndex) { @@ -174,39 +122,47 @@ private IEnumerable SinglePocoFromIDataReader(int index) } /// - /// Read data to multiple POCOs + /// Reads data to multiple POCOs. /// - /// The type of objects in the returned IEnumerable - /// Reader row to be read from the underlying IDataReader - /// An array of Types representing the POCO types of the returned result set. - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - private IEnumerable MultiPocoFromIDataReader(int index, Type[] types, object cb) + /// The POCO type representing a single result record. + /// The zero-based row index to be read from the underlying . + /// An array of types representing the POCO types in the returned result set. + /// A function used to connect the POCO instances as a single POCO of type , or + /// to let PetaPoco automatically deduce the relationships. + /// A composite POCO of type . + /// Called after the data reader has been disposed. + /// Result records are consumed in the incorrect order, or more than once. + private IEnumerable MultiPocoFromIDataReader(int index, Type[] types, object transformer) { + // TODO: Incorrect object name used when throwing ObjectDisposedException; should be `nameof(_reader)` if (_reader == null) throw new ObjectDisposedException(GetType().FullName, "The data reader has been disposed"); if (_consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); + _consumed = true; try { var cmd = _command; - var r = _reader; + var rdr = _reader; + + var factory = MultiPocoFactory.GetFactory(types, cmd.Connection.ConnectionString, cmd.CommandText, rdr, _defaultMapper); + + // if no projector function provided by caller, figure out the split points and connect them ourself + if (transformer == null) + transformer = MultiPocoFactory.GetAutoMapper(types.ToArray()); - var factory = MultiPocoFactory.GetFactory(types, cmd.Connection.ConnectionString, cmd.CommandText, r, _defaultMapper); - if (cb == null) - cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); bool bNeedTerminator = false; while (true) { - TRet poco; + T poco; try { - if (!r.Read()) + if (!rdr.Read()) break; - poco = factory(r, cb); + poco = factory(rdr, transformer); } catch (Exception x) { @@ -223,7 +179,7 @@ private IEnumerable MultiPocoFromIDataReader(int index, Type[] types if (bNeedTerminator) { - var poco = (TRet) (cb as Delegate).DynamicInvoke(new object[types.Length]); + var poco = (T)(transformer as Delegate).DynamicInvoke(new object[types.Length]); if (poco != null) yield return poco; else @@ -239,15 +195,15 @@ private IEnumerable MultiPocoFromIDataReader(int index, Type[] types } } -#endregion + #endregion -#region DataReader Management + #region DataReader Management private int _gridIndex; private bool _consumed; /// - /// Advance the IDataReader to the NextResult, if available + /// Advances the to the , if one exists. /// private void NextResult() { @@ -258,7 +214,8 @@ private void NextResult() } /// - /// Dispose the grid, closing and disposing the underlying reader, command, and shared connection + /// Disposes the GridReader, closing and releasing the underlying , , and shared + /// . /// public void Dispose() { @@ -279,6 +236,6 @@ public void Dispose() _db.CloseSharedConnection(); } -#endregion + #endregion } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/IGridReader.cs b/PetaPoco/Core/IGridReader.cs index f19cd545..d76af284 100644 --- a/PetaPoco/Core/IGridReader.cs +++ b/PetaPoco/Core/IGridReader.cs @@ -3,73 +3,72 @@ namespace PetaPoco { + /// + /// Specifies a set of methods for reading a result set from a database query into a sequence of single or multi-POCO objects. + /// public interface IGridReader : IDisposable { + #region Read : Single-POCO + /// - /// Reads from a GridReader, returning the results as an IEnumerable collection + /// Reads a sequence of results from a data reader. /// - /// The Type representing a row in the result set - /// An enumerable collection of result records + /// The POCO type representing a single result record. + /// An enumerable sequence of results of type . IEnumerable Read(); - /// - /// Perform a multi-poco read from a GridReader - /// - /// The first POCO type - /// The second POCO type - /// A collection of POCOs as an IEnumerable + #endregion + + #region Read with Default Mapping : Multi-POCO + + /// IEnumerable Read(); - /// - /// Perform a multi-poco read from a GridReader - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// A collection of POCOs as an IEnumerable + /// IEnumerable Read(); /// - /// Perform a multi-poco read from a GridReader + /// Reads a sequence of results from a data reader and projects them into a new form of type using a + /// default mapping function. /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The forth POCO type - /// A collection of POCOs as an IEnumerable + /// + /// PetaPoco will automatically attempt to determine the split points and auto-map any additional POCO types into . + /// + /// The first POCO type, and the projected POCO type representing a single composite result record. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// An enumerable sequence of results of type . IEnumerable Read(); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - IEnumerable Read(Func cb); + #endregion - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - IEnumerable Read(Func cb); + #region Read with Custom Mapping : Multi-POCO + + /// + IEnumerable Read(Func projector); + + /// + IEnumerable Read(Func projector); /// - /// Perform a multi-poco query + /// Reads a sequence of results from a data reader and projects them into a new form of type using + /// the provided mapping function. /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The forth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// A collection of POCOs as an IEnumerable - IEnumerable Read(Func cb); + /// + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The projected POCO type representing a single result record. + /// A function that transforms each of the given types into a . + /// An enumerable sequence of results of type . + IEnumerable Read(Func projector); + + #endregion } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/IMapper.cs b/PetaPoco/Core/IMapper.cs index 86227caa..b4d73a6d 100644 --- a/PetaPoco/Core/IMapper.cs +++ b/PetaPoco/Core/IMapper.cs @@ -1,57 +1,57 @@ -using System; -using System.Reflection; - -namespace PetaPoco -{ - /// - /// IMapper provides a way to hook into PetaPoco's Database to POCO mapping mechanism to either - /// customize or completely replace it. - /// - /// - /// To use this functionality, instantiate a class that implements IMapper and then pass it to - /// PetaPoco through the static method Mappers.Register() - /// - public interface IMapper - { - /// - /// Get information about the table associated with a POCO class - /// - /// The poco type. - /// A TableInfo instance - /// - /// This method must return a valid TableInfo. - /// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco - /// - TableInfo GetTableInfo(Type pocoType); - - /// - /// Get information about the column associated with a property of a POCO - /// - /// The PropertyInfo of the property being queried - /// A reference to a ColumnInfo instance, or null to ignore this property - /// - /// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty - /// - ColumnInfo GetColumnInfo(PropertyInfo pocoProperty); - - /// - /// Supply a function to convert a database value to the correct property value - /// - /// The target property - /// The type of data returned by the DB - /// A Func that can do the conversion, or null for no conversion - Func GetFromDbConverter(PropertyInfo targetProperty, Type sourceType); - - /// - /// Supply a function to convert a property value into a database value - /// - /// The property to be converted - /// A Func that can do the conversion - /// - /// This conversion is only used for converting values from POCOs that are - /// being Inserted or Updated. - /// Conversion is not available for parameter values passed directly to queries. - /// - Func GetToDbConverter(PropertyInfo sourceProperty); - } -} \ No newline at end of file +using System; +using System.Reflection; + +namespace PetaPoco +{ + /// + /// Provides a way to hook into PetaPoco's DB-to-POCO mapping mechanism to either customise or completely replace it. + /// + /// + /// To use this functionality, instantiate a class that implements IMapper and then register it using one of the + /// static register methods. + /// + /// + /// + public interface IMapper + { + /// + /// Returns information about the table associated with a POCO class. + /// + /// + /// This method must return a valid . To create a TableInfo from a POCO's attributes, use . + /// + /// The POCO type representing a single result record in the associated database table. + /// A TableInfo instance. + TableInfo GetTableInfo(Type pocoType); + + /// + /// Returns a object containing information about the column associated with a property of a POCO. + /// + /// + /// To create a ColumnInfo from a property's attributes, use + /// + /// The PropertyInfo for the property that maps to the associated database column. + /// A ColumnInfo instance, or if the property should be ignored. + ColumnInfo GetColumnInfo(PropertyInfo pocoProperty); + + /// + /// Supplies a function to convert a database value to the correct property value. + /// + /// The target property. + /// The data type returned by the database. + /// A function to perform the conversion, or if no conversion is needed. + Func GetFromDbConverter(PropertyInfo targetProperty, Type sourceType); + + /// + /// Supplies a function to convert a property value to the correct database value. + /// + /// + /// This conversion is only used for converting values from POCOs that are being Inserted or Updated. Conversion is not available + /// for parameter values passed directly to queries. + /// + /// The property to be converted. + /// A function to perform the conversion, or if no conversion is needed. + Func GetToDbConverter(PropertyInfo sourceProperty); + } +} diff --git a/PetaPoco/Core/IProvider.cs b/PetaPoco/Core/IProvider.cs index df34f8be..3ec7ca57 100644 --- a/PetaPoco/Core/IProvider.cs +++ b/PetaPoco/Core/IProvider.cs @@ -9,106 +9,119 @@ namespace PetaPoco.Core { /// - /// Represents a contract for a database type provider. + /// Represents a contract for a database type provider. /// public interface IProvider { /// - /// Gets the this provider supplies. + /// Gets the supplied by this provider. /// IPagingHelper PagingUtility { get; } /// - /// Gets a flag for whether the DB has native support for GUID/UUID. + /// Gets a flag indicating whether the DB has native support for GUID/UUID. /// bool HasNativeGuidSupport { get; } /// - /// Escape a table name into a suitable format for the associated database provider. + /// Escapes a table name into a suitable format for the associated database provider. /// - /// - /// The name of the table (as specified by the client program, or as attributes on the associated - /// POCO class. - /// - /// The escaped table name + /// The name of the table as specified by the client program, or as attributes on the associated POCO + /// class. + /// The escaped table name. string EscapeTableName(string tableName); /// - /// Escape an arbitary SQL identifier into a format suitable for the associated database provider + /// Escapes an arbitrary SQL identifier into a format suitable for the associated database provider. /// - /// The SQL identifier to be escaped - /// The escaped identifier + /// The SQL identifier to be escaped. + /// The escaped identifier. string EscapeSqlIdentifier(string sqlIdentifier); /// - /// Builds an SQL query suitable for performing page-based queries to the database + /// Builds an SQL query suitable for performing page-based queries to the database. /// - /// The number of rows that should be skipped by the query - /// The number of rows that should be retruend by the query - /// The original SQL query after being parsed into its component parts - /// Arguments to any embedded parameters in the SQL query - /// The final SQL query that should be executed. + /// The number of records to skip. + /// The number of records to take. + /// The parsed SQL query parts. + /// The arguments to any embedded parameters in the SQL query. + /// The final SQL query to be executed. string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args); /// - /// Converts a supplied C# object value into a value suitable for passing to the database + /// Converts the specified C# object value into a data type suitable for passing to the database. /// - /// The value to convert - /// The converted value + /// The value to convert. + /// The converted value. object MapParameterValue(object value); /// - /// Called immediately before a command is executed, allowing for modification of the IDbCommand before it's passed to - /// the database provider + /// Called immediately before an SQL command is executed, allowing for modification of the command before being passed to the + /// database provider. /// - /// + /// The SQL command to be executed. void PreExecute(IDbCommand cmd); /// - /// Returns an SQL statement that can check for the existence of a row in the database. + /// Returns an SQL statement that can check for the existence of a row in the database. /// - /// + /// The SQL statement. string GetExistsSql(); /// - /// Performs an Insert operation + /// Performs an Insert operation. /// - /// The calling Database object - /// The insert command to be executed - /// The primary key of the table being inserted into - /// The ID of the newly inserted record - object ExecuteInsert(Database database, IDbCommand cmd, string primaryKeyName); + /// The database instance that will execute the SQL command. + /// The SQL command to be executed. + /// The primary key column name for the table being inserted into. + /// The ID of the newly inserted record. + object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName); #if ASYNC /// - /// Async version of . + /// Performs an Insert operation asynchronously. /// - Task ExecuteInsertAsync(CancellationToken cancellationToken, Database database, IDbCommand cmd, string primaryKeyName); + /// A cancellation token that can be used to cancel the operation. + /// The database instance that will execute the SQL command. + /// The SQL command to be executed. + /// The primary key column name for the table being inserted into. + /// + /// A task that represents the asynchronous operation. The task result contains the primary key of the new record. + /// + Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName); #endif /// - /// Returns an SQL expression that can be used to specify the return value of auto incremented columns. + /// Returns an SQL expression that can be used to specify the return value of auto-incremented columns. /// - /// The primary key of the row being inserted. - /// An expression describing how to return the new primary key value - /// See the SQLServer database provider for an example of how this method is used. + /// + /// See the SQLServer database provider for an example of how this method is used. + /// + /// The primary key column name of the row being inserted. + /// An expression describing how to return the new primary key value. string GetInsertOutputClause(string primaryKeyName); /// - /// Returns the prefix used to delimit parameters in SQL query strings. + /// Returns the prefix used to delimit parameters in SQL query strings. /// /// The connection string. /// The provider's character for prefixing a query parameter. string GetParameterPrefix(string connectionString); /// - /// Return an SQL expression that can be used to populate the primary key column of an auto-increment column. + /// Returns an SQL expression that can be used to populate the primary key column of an auto-increment column. /// - /// Table info describing the table - /// An SQL expressions - /// See the Oracle database type for an example of how this method is used. + /// + /// See the Oracle database type for an example of how this method is used. + /// + /// The TableInfo instance describing the table. + /// An SQL expression. string GetAutoIncrementExpression(TableInfo tableInfo); + /// + /// Returns the DbProviderFactory. + /// + /// The DbProviderFactory factory. DbProviderFactory GetFactory(); } } diff --git a/PetaPoco/Core/ITransaction.cs b/PetaPoco/Core/ITransaction.cs index 318c7aaf..5cd8b9a3 100644 --- a/PetaPoco/Core/ITransaction.cs +++ b/PetaPoco/Core/ITransaction.cs @@ -3,16 +3,16 @@ namespace PetaPoco { /// - /// Represents the contract for the transaction. + /// Represents the contract for the transaction. /// /// - /// A PetaPoco helper to support transactions using the using syntax. + /// A PetaPoco helper to support transactions inside the using block syntax. /// public interface ITransaction : IDisposable, IHideObjectMethods { /// - /// Completes the transaction. Not calling complete will cause the transaction to rollback on dispose. + /// Completes the transaction. /// void Complete(); } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/Inflection/EnglishInflector.cs b/PetaPoco/Core/Inflection/EnglishInflector.cs index 578f024a..0f0668bd 100644 --- a/PetaPoco/Core/Inflection/EnglishInflector.cs +++ b/PetaPoco/Core/Inflection/EnglishInflector.cs @@ -4,15 +4,16 @@ namespace PetaPoco.Core.Inflection { /// - /// Author: Originally written (I believe) by Andrew Peters - /// Source: Scott Kirkland (https://github.com/srkirkland/Inflector) + /// The EnglishInflector class implements inflection for the English language. /// + /// + /// Author: Originally written (I believe) by Andrew Peters + ///
Source: Scott Kirkland () + ///
public class EnglishInflector : IInflector { private static readonly List Plurals = new List(); - private static readonly List Singulars = new List(); - private static readonly List Uncountables = new List(); static EnglishInflector() @@ -80,201 +81,74 @@ static EnglishInflector() AddUncountable("aircraft"); } - /// - /// Pluralises a word. - /// - /// - /// inflect.Pluralise("search").ShouldBe("searches"); - /// inflect.Pluralise("stack").ShouldBe("stacks"); - /// inflect.Pluralise("fish").ShouldBe("fish"); - /// - /// The word to pluralise. - /// The pluralised word. + /// public string Pluralise(string word) { return ApplyRules(Plurals, word); } - /// - /// Singularises a word. - /// - /// - /// inflect.Singularise("searches").ShouldBe("search"); - /// inflect.Singularise("stacks").ShouldBe("stack"); - /// inflect.Singularise("fish").ShouldBe("fish"); - /// - /// The word to signularise. - /// The signularised word. + /// public string Singularise(string word) { return ApplyRules(Singulars, word); } - /// - /// Titleises the word. (title => Title, the_brown_fox => TheBrownFox) - /// - /// - /// inflect.Titleise("some title").ShouldBe("Some Title"); - /// inflect.Titleise("some-title").ShouldBe("Some Title"); - /// inflect.Titleise("sometitle").ShouldBe("Sometitle"); - /// inflect.Titleise("some_title:_the_beginning").ShouldBe("Some Title: The Beginning"); - /// - /// The word to titleise. - /// The titleised word. + /// public string Titleise(string word) { return Regex.Replace(Humanise(Underscore(word)), @"\b([a-z])", match => match.Captures[0].Value.ToUpper()); } - /// - /// Humanizes the word. - /// - /// - /// inflect.Humanise("some_title").ShouldBe("Some title"); - /// inflect.Humanise("some-title").ShouldBe("Some-title"); - /// inflect.Humanise("Some_title").ShouldBe("Some title"); - /// inflect.Humanise("someTitle").ShouldBe("Sometitle"); - /// inflect.Humanise("someTitle_Another").ShouldBe("Sometitle another"); - /// - /// The word to humanise. - /// The humanized word. + /// public string Humanise(string lowercaseAndUnderscoredWord) { return Capitalise(Regex.Replace(lowercaseAndUnderscoredWord, @"_", " ")); } - /// - /// Pascalises the word. - /// - /// - /// inflect.Pascalise("customer").ShouldBe("Customer"); - /// inflect.Pascalise("customer_name").ShouldBe("CustomerName"); - /// inflect.Pascalise("customer name").ShouldBe("Customer name"); - /// - /// The word to pascalise. - /// The pascalied word. + /// public string Pascalise(string lowercaseAndUnderscoredWord) { return Regex.Replace(lowercaseAndUnderscoredWord, "(?:^|_)(.)", match => match.Groups[1].Value.ToUpper()); } - /// - /// Camelises the word. - /// - /// - /// inflect.Camelise("Customer").ShouldBe("customer"); - /// inflect.Camelise("customer_name").ShouldBe("customerName"); - /// inflect.Camelise("customer_first_name").ShouldBe("customerFirstName"); - /// inflect.Camelise("customer name").ShouldBe("customer name"); - /// - /// The word to camelise. - /// The camelised word. + /// public string Camelise(string lowercaseAndUnderscoredWord) { return Uncapitalise(Pascalise(lowercaseAndUnderscoredWord)); } - /// - /// Underscores and lowercases the word. - /// - /// - /// inflect.Underscore("SomeTitle").ShouldBe("some_title"); - /// inflect.Underscore("someTitle").ShouldBe("some_title"); - /// inflect.Underscore("some title that will be underscored").ShouldBe("some_title_that_will_be_underscored"); - /// inflect.Underscore("SomeTitleThatWillBeUnderscored").ShouldBe("some_title_that_will_be_underscored"); - /// - /// The word to underscore. - /// The underscored word. + /// public string Underscore(string pascalCasedWord) { - return Regex.Replace(Regex.Replace(Regex.Replace(pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])", "$1_$2"), @"[-\s]", "_").ToLower(); + return Regex.Replace(Regex.Replace(Regex.Replace( + pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])", "$1_$2"), @"[-\s]", "_").ToLower(); } - /// - /// Capitalises the word. - /// - /// - /// inflect.Capitalise("some title").ShouldBe("Some title"); - /// inflect.Capitalise("some Title").ShouldBe("Some title"); - /// inflect.Capitalise("SOMETITLE").ShouldBe("Sometitle"); - /// inflect.Capitalise("someTitle").ShouldBe("Sometitle"); - /// inflect.Capitalise("some title goes here").ShouldBe("Some title goes here"); - /// - /// The word to capitalise. - /// The capitalised word. + /// public string Capitalise(string word) { return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower(); } - /// - /// Uncapitalises the word. - /// - /// - /// inflect.Uncapitalise("Some title").ShouldBe("some title"); - /// inflect.Uncapitalise("Some Title").ShouldBe("some Title"); - /// inflect.Uncapitalise("SOMETITLE").ShouldBe("sOMETITLE"); - /// inflect.Uncapitalise("someTitle").ShouldBe("someTitle"); - /// inflect.Uncapitalise("Some title goes here").ShouldBe("some title goes here"); - /// - /// The word to uncapitalise. - /// The uncapitalised word. + /// public string Uncapitalise(string word) { return word.Substring(0, 1).ToLower() + word.Substring(1); } - /// - /// Ordinalises the number. - /// - /// - /// inflect.Ordinalise(0).ShouldBe("0th"); - /// inflect.Ordinalise(1).ShouldBe("1st"); - /// inflect.Ordinalise(2).ShouldBe("2nd"); - /// inflect.Ordinalise(3).ShouldBe("3rd"); - /// inflect.Ordinalise(101).ShouldBe("101st"); - /// inflect.Ordinalise(104).ShouldBe("104th"); - /// inflect.Ordinalise(1000).ShouldBe("1000th"); - /// inflect.Ordinalise(1001).ShouldBe("1001st"); - /// - /// The number to ordinalise. - /// The ordinalised number. + /// public string Ordinalise(string number) { - return Ordanise(int.Parse(number), number); + return Ordinalise(int.Parse(number), number); } - /// - /// Ordinalises the number. - /// - /// - /// inflect.Ordinalise("0").ShouldBe("0th"); - /// inflect.Ordinalise("1").ShouldBe("1st"); - /// inflect.Ordinalise("2").ShouldBe("2nd"); - /// inflect.Ordinalise("3").ShouldBe("3rd"); - /// inflect.Ordinalise("100").ShouldBe("100th"); - /// inflect.Ordinalise("101").ShouldBe("101st"); - /// inflect.Ordinalise("1000").ShouldBe("1000th"); - /// inflect.Ordinalise("1001").ShouldBe("1001st"); - /// - /// The number to ordinalise. - /// The ordinalised number. + /// public string Ordinalise(int number) { - return Ordanise(number, number.ToString()); + return Ordinalise(number, number.ToString()); } - /// - /// Dasherises the word. - /// - /// - /// inflect.Dasherise("some_title").ShouldBe("some-title"); - /// inflect.Dasherise("some-title").ShouldBe("some-title"); - /// inflect.Dasherise("some_title_goes_here").ShouldBe("some-title-goes-here"); - /// inflect.Dasherise("some_title and_another").ShouldBe("some-title and-another"); - /// - /// The word to dasherise. - /// The dasherised word. + /// public string Dasherise(string underscoredWord) { return underscoredWord.Replace('_', '-'); @@ -319,7 +193,7 @@ private static string ApplyRules(IList rules, string word) return result; } - private static string Ordanise(int number, string numberString) + private static string Ordinalise(int number, string numberString) { var nMod100 = number % 100; @@ -344,7 +218,6 @@ private static string Ordanise(int number, string numberString) private class Rule { private readonly Regex _regex; - private readonly string _replacement; public Rule(string pattern, string replacement) @@ -359,4 +232,4 @@ public string Apply(string word) } } } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/Inflection/IInflector.cs b/PetaPoco/Core/Inflection/IInflector.cs index 1b2e1bec..c8dad772 100644 --- a/PetaPoco/Core/Inflection/IInflector.cs +++ b/PetaPoco/Core/Inflection/IInflector.cs @@ -1,172 +1,256 @@ namespace PetaPoco.Core.Inflection { /// - /// Specifies the inflection contract. + /// The IInflector interface specifies the inflection contract all inheriting class implementations must implement. /// public interface IInflector { /// - /// Pluralises a word. + /// Pluralises a word. /// + /// + /// fox => foxes + /// /// - /// inflect.Pluralise("search").ShouldBe("searches"); - /// inflect.Pluralise("stack").ShouldBe("stacks"); - /// inflect.Pluralise("fish").ShouldBe("fish"); + /// + /// + /// /// /// The word to pluralise. /// The pluralised word. string Pluralise(string word); /// - /// Singularises a word. + /// Singularises a word. /// + /// + /// foxes => fox + /// /// - /// inflect.Singularise("searches").ShouldBe("search"); - /// inflect.Singularise("stacks").ShouldBe("stack"); - /// inflect.Singularise("fish").ShouldBe("fish"); + /// + /// + /// /// - /// The word to signularise. - /// The signularised word. + /// The word to singularise. + /// The singularised word. string Singularise(string word); /// - /// Titleises the word. (title => Title, the_brown_fox => TheBrownFox) + /// Titleises the word using a "Title Case" transformation. /// + /// + /// the_brown_fox => The Brown Fox + /// /// - /// inflect.Titleise("some title").ShouldBe("Some Title"); - /// inflect.Titleise("some-title").ShouldBe("Some Title"); - /// inflect.Titleise("sometitle").ShouldBe("Sometitle"); - /// inflect.Titleise("some_title:_the_beginning").ShouldBe("Some Title: The Beginning"); + /// + /// + /// /// /// The word to titleise. /// The titleised word. string Titleise(string word); /// - /// Humanizes the word. + /// Humanises the word using "Sentence case" transformation. /// + /// + /// the_brown_fox => The brown fox + /// /// - /// inflect.Humanise("some_title").ShouldBe("Some title"); - /// inflect.Humanise("some-title").ShouldBe("Some-title"); - /// inflect.Humanise("Some_title").ShouldBe("Some title"); - /// inflect.Humanise("someTitle").ShouldBe("Sometitle"); - /// inflect.Humanise("someTitle_Another").ShouldBe("Sometitle another"); + /// + /// + /// /// /// The word to humanise. - /// The humanized word. + /// The humanised word. string Humanise(string lowercaseAndUnderscoredWord); /// - /// Pascalises the word. + /// Pascalises the word using a "PascalCase" transformation. /// + /// + /// the_brown_fox => TheBrownFox + /// /// - /// inflect.Pascalise("customer").ShouldBe("Customer"); - /// inflect.Pascalise("customer_name").ShouldBe("CustomerName"); - /// inflect.Pascalise("customer name").ShouldBe("Customer name"); + /// + /// + /// /// /// The word to pascalise. - /// The pascalied word. + /// The pascalised word. string Pascalise(string lowercaseAndUnderscoredWord); /// - /// Camelises the word. + /// Camelises the word using a "camelCase" transformation. /// + /// + /// the_brown_fox => theBrownFox + /// /// - /// inflect.Camelise("Customer").ShouldBe("customer"); - /// inflect.Camelise("customer_name").ShouldBe("customerName"); - /// inflect.Camelise("customer_first_name").ShouldBe("customerFirstName"); - /// inflect.Camelise("customer name").ShouldBe("customer name"); + /// + /// + /// /// /// The word to camelise. /// The camelised word. string Camelise(string lowercaseAndUnderscoredWord); /// - /// Underscores and lowercases the word. + /// Underscores and lowercases the word using a "snake_case" transformation. /// + /// + /// TheBrownFox => the_brown_fox + /// /// - /// inflect.Underscore("SomeTitle").ShouldBe("some_title"); - /// inflect.Underscore("someTitle").ShouldBe("some_title"); - /// inflect.Underscore("some title that will be underscored").ShouldBe("some_title_that_will_be_underscored"); - /// inflect.Underscore("SomeTitleThatWillBeUnderscored").ShouldBe("some_title_that_will_be_underscored"); + /// + /// + /// /// /// The word to underscore. /// The underscored word. string Underscore(string pascalCasedWord); /// - /// Capitalises the word. + /// Capitalises the word using an "Initial upper case" transformation. /// + /// + /// the brown Fox => The brown fox + /// /// - /// inflect.Capitalise("some title").ShouldBe("Some title"); - /// inflect.Capitalise("some Title").ShouldBe("Some title"); - /// inflect.Capitalise("SOMETITLE").ShouldBe("Sometitle"); - /// inflect.Capitalise("someTitle").ShouldBe("Sometitle"); - /// inflect.Capitalise("some title goes here").ShouldBe("Some title goes here"); + /// + /// + /// /// /// The word to capitalise. /// The capitalised word. string Capitalise(string word); /// - /// Uncapitalises the word. + /// Uncapitalises the word using an "initial lower case" transformation. /// + /// + /// The brown Fox => the brown Fox + /// /// - /// inflect.Uncapitalise("Some title").ShouldBe("some title"); - /// inflect.Uncapitalise("Some Title").ShouldBe("some Title"); - /// inflect.Uncapitalise("SOMETITLE").ShouldBe("sOMETITLE"); - /// inflect.Uncapitalise("someTitle").ShouldBe("someTitle"); - /// inflect.Uncapitalise("Some title goes here").ShouldBe("some title goes here"); + /// + /// + /// /// /// The word to uncapitalise. /// The uncapitalised word. string Uncapitalise(string word); /// - /// Ordinalises the number. + /// Parses and Ordinalises the number string using a cardinal number (1,2,3...) to ordinal number (1st,2nd,3rd...) transformation. /// + /// + /// 1 => 1st, 2 => 2nd, 3 => 3rd... + /// /// - /// inflect.Ordinalise(0).ShouldBe("0th"); - /// inflect.Ordinalise(1).ShouldBe("1st"); - /// inflect.Ordinalise(2).ShouldBe("2nd"); - /// inflect.Ordinalise(3).ShouldBe("3rd"); - /// inflect.Ordinalise(101).ShouldBe("101st"); - /// inflect.Ordinalise(104).ShouldBe("104th"); - /// inflect.Ordinalise(1000).ShouldBe("1000th"); - /// inflect.Ordinalise(1001).ShouldBe("1001st"); + /// + /// + /// /// /// The number to ordinalise. /// The ordinalised number. string Ordinalise(string number); /// - /// Ordinalises the number. + /// Ordinalises the number using a cardinal number (1,2,3...) to ordinal number (1st,2nd,3rd...) transformation. /// + /// + /// 1 => 1st, 2 => 2nd, 3 => 3rd... + /// /// - /// inflect.Ordinalise("0").ShouldBe("0th"); - /// inflect.Ordinalise("1").ShouldBe("1st"); - /// inflect.Ordinalise("2").ShouldBe("2nd"); - /// inflect.Ordinalise("3").ShouldBe("3rd"); - /// inflect.Ordinalise("100").ShouldBe("100th"); - /// inflect.Ordinalise("101").ShouldBe("101st"); - /// inflect.Ordinalise("1000").ShouldBe("1000th"); - /// inflect.Ordinalise("1001").ShouldBe("1001st"); + /// + /// + /// /// /// The number to ordinalise. /// The ordinalised number. string Ordinalise(int number); /// - /// Dasherises the word. + /// Dasherises the word using a "kebob-case" transformation. /// + /// + /// the_brown_fox => the-brown-fox + /// /// - /// inflect.Dasherise("some_title").ShouldBe("some-title"); - /// inflect.Dasherise("some-title").ShouldBe("some-title"); - /// inflect.Dasherise("some_title_goes_here").ShouldBe("some-title-goes-here"); - /// inflect.Dasherise("some_title and_another").ShouldBe("some-title and-another"); + /// + /// + /// /// /// The word to dasherise. /// The dasherised word. string Dasherise(string underscoredWord); } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/Inflection/Inflector.cs b/PetaPoco/Core/Inflection/Inflector.cs index 167b24ec..71336f89 100644 --- a/PetaPoco/Core/Inflection/Inflector.cs +++ b/PetaPoco/Core/Inflection/Inflector.cs @@ -1,25 +1,21 @@ namespace PetaPoco.Core.Inflection { /// - /// Static inflection helper + /// Static inflection singleton helper. /// public static class Inflector { private static IInflector _inflector; /// - /// Gets or sets the instacne. + /// Gets or sets the instance. + /// Default is . /// - /// - /// The inflector to set as the default instance, or null to restore the default - /// . - /// /// - /// By default the instance used. + /// Set to to restore the default . /// - /// - /// The currently set instance. - /// + /// The currently set instance. + /// public static IInflector Instance { get { return _inflector; } @@ -31,4 +27,4 @@ static Inflector() _inflector = new EnglishInflector(); } } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/Mappers.cs b/PetaPoco/Core/Mappers.cs index ce240de9..075a7cfb 100644 --- a/PetaPoco/Core/Mappers.cs +++ b/PetaPoco/Core/Mappers.cs @@ -1,108 +1,108 @@ -using PetaPoco.Core; -using PetaPoco.Internal; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace PetaPoco -{ - /// - /// This static class manages registation of IMapper instances with PetaPoco - /// - public static class Mappers - { - private static ConcurrentDictionary> _mappers = new ConcurrentDictionary>(); - - /// - /// Registers a mapper for all types in a specific assembly - /// - /// The assembly whose types are to be managed by this mapper - /// The IMapper implementation - public static bool Register(Assembly assembly, IMapper mapper) => RegisterInternal(assembly, mapper); - - /// - /// Registers a mapper for a single POCO type - /// - /// The type to be managed by this mapper - /// The IMapper implementation - public static bool Register(Type type, IMapper mapper) => RegisterInternal(type, mapper); - - /// - /// Remove all mappers for all types in a specific assembly - /// - /// The assembly whose mappers are to be revoked - public static bool Revoke(Assembly assembly) => RevokeInternal(assembly); - - /// - /// Remove the mapper for a specific type - /// - /// The type whose mapper is to be removed - public static bool Revoke(Type type) => RevokeInternal(type); - - /// - /// Revoke an instance of a mapper - /// - /// The IMapper to be revkoed - public static bool Revoke(IMapper mapper) - { - var ret = false; - var m = _mappers.FirstOrDefault(v => v.Value.Value == mapper); - var key = m.Equals(default(KeyValuePair>)) ? null : m.Key; - if (key != null) - { - ret = _mappers.TryRemove(key, out var _); - if (ret) FlushCaches(); - } - return ret; - } - - /// - /// Revokes all registered mappers. - /// - public static void RevokeAll() - { - _mappers.Clear(); - FlushCaches(); - } - - /// - /// Retrieve the IMapper implementation to be used for a specified POCO type. - /// - /// The entity type to get the mapper for. - /// The default mapper to use when none is registered for the type. - /// The mapper for the given type. - public static IMapper GetMapper(Type entityType, IMapper defaultMapper) - { - if (_mappers.TryGetValue(entityType, out Lazy val)) - return val.Value; - if (_mappers.TryGetValue(entityType.Assembly, out val)) - return val.Value; - - return defaultMapper; - } - - private static bool RegisterInternal(object typeOrAssembly, IMapper mapper) - { - var ret = _mappers.TryAdd(typeOrAssembly, new Lazy(() => mapper)); - if (ret) FlushCaches(); - return ret; - } - - private static bool RevokeInternal(object typeOrAssembly) - { - var ret = _mappers.TryRemove(typeOrAssembly, out var _); - if (ret) FlushCaches(); - return ret; - } - - private static void FlushCaches() - { - // Whenever a mapper is registered or revoked, we have to assume any generated code is no longer valid. - // Since this should be a rare occurrence, the simplest approach is to simply dump everything and start over. - MultiPocoFactory.FlushCaches(); - PocoData.FlushCaches(); - } - } -} \ No newline at end of file +using PetaPoco.Core; +using PetaPoco.Internal; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PetaPoco +{ + /// + /// This static class manages registration of IMapper instances with PetaPoco. + /// + public static class Mappers + { + private static ConcurrentDictionary> _mappers = new ConcurrentDictionary>(); + + /// + /// Registers a mapper for all types in the specified assembly. + /// + /// The assembly whose types are to be managed by this mapper. + /// The mapper to register. + public static bool Register(Assembly assembly, IMapper mapper) => RegisterInternal(assembly, mapper); + + /// + /// Registers a mapper for a single POCO type. + /// + /// The type to be managed by this mapper. + /// The mapper to register. + public static bool Register(Type type, IMapper mapper) => RegisterInternal(type, mapper); + + /// + /// Revokes all mappers for all types in the specified assembly. + /// + /// The assembly containing the mappers to be revoked. + public static bool Revoke(Assembly assembly) => RevokeInternal(assembly); + + /// + /// Revokes a mapper for the specified POCO type. + /// + /// The type of the mapper to be revoked. + public static bool Revoke(Type type) => RevokeInternal(type); + + /// + /// Revokes the specified mapper instance. + /// + /// The IMapper to be revoked. + public static bool Revoke(IMapper mapper) + { + var ret = false; + var m = _mappers.FirstOrDefault(v => v.Value.Value == mapper); + var key = m.Equals(default(KeyValuePair>)) ? null : m.Key; + if (key != null) + { + ret = _mappers.TryRemove(key, out var _); + if (ret) FlushCaches(); + } + return ret; + } + + /// + /// Revokes all registered mappers. + /// + public static void RevokeAll() + { + _mappers.Clear(); + FlushCaches(); + } + + /// + /// Retrieves the IMapper implementation to be used for a specified POCO type. + /// + /// The entity type to get the mapper for. + /// The default mapper to use when none is registered for the type. + /// The mapper for the given type. + public static IMapper GetMapper(Type entityType, IMapper defaultMapper) + { + if (_mappers.TryGetValue(entityType, out Lazy val)) + return val.Value; + if (_mappers.TryGetValue(entityType.Assembly, out val)) + return val.Value; + + return defaultMapper; + } + + private static bool RegisterInternal(object typeOrAssembly, IMapper mapper) + { + var ret = _mappers.TryAdd(typeOrAssembly, new Lazy(() => mapper)); + if (ret) FlushCaches(); + return ret; + } + + private static bool RevokeInternal(object typeOrAssembly) + { + var ret = _mappers.TryRemove(typeOrAssembly, out var _); + if (ret) FlushCaches(); + return ret; + } + + private static void FlushCaches() + { + // Whenever a mapper is registered or revoked, we have to assume any generated code is no longer valid. + // Since this should be a rare occurrence, the simplest approach is to simply dump everything and start over. + MultiPocoFactory.FlushCaches(); + PocoData.FlushCaches(); + } + } +} diff --git a/PetaPoco/Core/MultiPocoFactory.cs b/PetaPoco/Core/MultiPocoFactory.cs index cce77042..41208a48 100644 --- a/PetaPoco/Core/MultiPocoFactory.cs +++ b/PetaPoco/Core/MultiPocoFactory.cs @@ -11,9 +11,11 @@ namespace PetaPoco.Internal internal class MultiPocoFactory { // Various cached stuff - private static readonly Cache, string, string, int>, object> MultiPocoFactories = new Cache, string, string, int>, object>(); + private static readonly Cache, string, string, int>, object> MultiPocoFactories + = new Cache, string, string, int>, object>(); - private static readonly Cache, object> AutoMappers = new Cache, object>(); + private static readonly Cache, object> AutoMappers + = new Cache, object>(); // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call private List _delegates; @@ -59,6 +61,7 @@ public static object GetAutoMapper(Type[] types) handled = true; } + // TODO: InvalidOperationException using `types[i]` in exception string; should probably be `types[i].Name` if (!handled) throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); } @@ -97,10 +100,11 @@ private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string conn usedColumns.Add(fieldName, true); } + // TODO: InvalidOperationException using `typeThis, typeNext` in exception string; should probably be `typeThis.Name, typeNext.Name` throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); } - // Create a multi-poco factory + // Create a multi-poco factory for a query private static Func CreateMultiPocoFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper) { var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, @@ -130,7 +134,7 @@ private static Func CreateMultiPocoFactory(Type il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack } - // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done + // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new[] { typeof(TRet) }).ToArray()).GetMethod("Invoke")); il.Emit(OpCodes.Ret); @@ -144,7 +148,7 @@ internal static void FlushCaches() AutoMappers.Flush(); } - // Get (or create) the multi-poco factory for a query + // Get (or create) a multi-poco factory for a query public static Func GetFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper) { var key = Tuple.Create(typeof(TRet), new ArrayKey(types), connectionString, sql, r.FieldCount); diff --git a/PetaPoco/Core/Page.cs b/PetaPoco/Core/Page.cs index ca9c0d62..7ae4496a 100644 --- a/PetaPoco/Core/Page.cs +++ b/PetaPoco/Core/Page.cs @@ -1,41 +1,45 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace PetaPoco { /// - /// Holds the results of a paged request. + /// Provides access to the result collection from a paged request. /// - /// The type of Poco in the returned result set + /// + /// Represents a paged result set, both providing access to the items on the current page and maintaining state information about the + /// pagination for additional queries. + /// + /// The POCO type representing a single result record. public class Page { /// - /// The current page number contained in this page of result set + /// Gets or sets the number of the current page in the result set. /// public long CurrentPage { get; set; } /// - /// The total number of pages in the full result set + /// Gets or sets the total number of pages in the full result set. /// public long TotalPages { get; set; } /// - /// The total number of records in the full result set + /// Gets or sets the total number of records in the full result set. /// public long TotalItems { get; set; } /// - /// The number of items per page + /// Gets or sets the number of items per page. /// public long ItemsPerPage { get; set; } /// - /// The actual records on this page + /// Gets or sets the result records on the current page. /// public List Items { get; set; } /// - /// User property to hold anything. + /// Gets or sets a context object, which can be used to store additional information about the result set. /// public object Context { get; set; } } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/PocoColumn.cs b/PetaPoco/Core/PocoColumn.cs index 3678ea4d..bb9a15a5 100644 --- a/PetaPoco/Core/PocoColumn.cs +++ b/PetaPoco/Core/PocoColumn.cs @@ -3,35 +3,54 @@ namespace PetaPoco.Core { + /// + /// Represents a property defined in a POCO object which is mapped to a column in that POCO's corresponding table. + /// public class PocoColumn { - public bool AutoSelectedResultColumn; - public string ColumnName; - public bool ForceToUtc; - public bool ForceToAnsiString; - public bool ForceToDateTime2; - public PropertyInfo PropertyInfo; - public bool ResultColumn; + /// + public string ColumnName { get; set; } + + /// + public bool ResultColumn { get; set; } + + /// + public bool AutoSelectedResultColumn { get; set; } + + /// + public bool ForceToAnsiString { get; set; } + + /// + public bool ForceToDateTime2 { get; set; } + + /// + public bool ForceToUtc { get; set; } + + /// public string InsertTemplate { get; set; } + + /// public string UpdateTemplate { get; set; } - public virtual void SetValue(object target, object val) - { - PropertyInfo.SetValue(target, val, null); - } + /// + /// Gets or sets the property info for the column-mapped property. + /// + public PropertyInfo PropertyInfo { get; set; } - public virtual object GetValue(object target) - { - return PropertyInfo.GetValue(target, null); - } + /// + public virtual void SetValue(object target, object val) => PropertyInfo.SetValue(target, val, null); + + /// + public virtual object GetValue(object target) => PropertyInfo.GetValue(target, null); + /// public virtual object ChangeType(object val) { var t = PropertyInfo.PropertyType; - if (val.GetType().IsValueType && PropertyInfo.PropertyType.IsGenericType && PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (val.GetType().IsValueType && PropertyInfo.PropertyType.IsGenericType && + PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) t = t.GetGenericArguments()[0]; - return Convert.ChangeType(val, t); } } -} \ No newline at end of file +} diff --git a/PetaPoco/Core/PocoData.cs b/PetaPoco/Core/PocoData.cs index c723bf0a..376f66a2 100644 --- a/PetaPoco/Core/PocoData.cs +++ b/PetaPoco/Core/PocoData.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -9,33 +9,71 @@ namespace PetaPoco.Core { + /// + /// Represents the core data structure for PetaPoco's database operations. + /// public class PocoData { + private static readonly object _converterLock = new object(); + private static Cache _pocoDatas = new Cache(); private static List> _converters = new List>(); - private static object _converterLock = new object(); private static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); private static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); private static FieldInfo fldConverters = typeof(PocoData).GetField("_converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); private static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); private static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); private Cache, Delegate> PocoFactories = new Cache, Delegate>(); - public Type Type; - public string[] QueryColumns { get; private set; } - public string[] UpdateColumns - { - // No need to cache as it's not used by PetaPoco internally - get { return (from c in Columns where !c.Value.ResultColumn && c.Value.ColumnName != TableInfo.PrimaryKey select c.Key).ToArray(); } - } + /// + /// Gets or sets the type of the POCO class represented by the PocoData instance. + /// + public Type Type { get; set; } + + /// + /// Gets the array of all queryable database column names used by auto-select for query operations when is enabled. + /// + /// + /// Column names are returned unescaped. Escaping should be applied based on the configured if + /// accessing this list to construct an SQL query. To access all + /// Excluded columns include: columns decorated with the , unannotated columns in a POCO marked + /// with the , and any that has opted out of auto-select + /// by setting to or through the property. + /// + public string[] QueryColumns { get; private set; } + /// + /// Gets the array of column names used for update operations, excluding result columns and the primary key. + /// + public string[] UpdateColumns // No need to cache as it's not used by PetaPoco internally + => (from c in Columns + where !c.Value.ResultColumn && c.Value.ColumnName != TableInfo.PrimaryKey + select c.Key).ToArray(); + + /// + /// Gets the metadata about the database table associated with the POCO class. + /// public TableInfo TableInfo { get; private set; } + + /// + /// Gets the dictionary of PocoColumn objects, containing column metadata for the database table mapped to the POCO class. + /// public Dictionary Columns { get; private set; } + /// + /// Initializes a new instance of the PocoData class with default values. + /// public PocoData() { } + /// + /// Initializes a new instance of the PocoData class with the specified type and mapper. + /// + /// The type of the POCO class. + /// The default mapper to use for the POCO type. public PocoData(Type type, IMapper defaultMapper) { Type = type; @@ -69,10 +107,34 @@ public PocoData(Type type, IMapper defaultMapper) Columns.Add(pc.ColumnName, pc); } - // Build column list for automatic select - QueryColumns = (from c in Columns where !c.Value.ResultColumn || c.Value.AutoSelectedResultColumn select c.Key).ToArray(); + // Build column list for auto-select queries + QueryColumns = (from c in Columns + where !c.Value.ResultColumn || c.Value.AutoSelectedResultColumn + select c.Key).ToArray(); + } + + /// + /// Creates a new PocoData instance for the specified class type and mapper. + /// + /// The type to create a PocoData instance for. + /// The default mapper to use for the type. + /// A new PocoData instance for the specified type. + /// Trying to use dynamic types with this method. + public static PocoData ForType(Type type, IMapper defaultMapper) + { + if (type == typeof(System.Dynamic.ExpandoObject)) + throw new InvalidOperationException("Cannot use dynamic types with this method"); + + return _pocoDatas.GetOrAdd(type, () => new PocoData(type, defaultMapper)); } + /// + /// Creates a new PocoData instance for the specified object, specifically a . + /// + /// The object to create a PocoData instance for. + /// The name of the primary key for the object. + /// The default mapper to use for the object. + /// A new PocoData instance for the specified object. public static PocoData ForObject(object obj, string primaryKeyName, IMapper defaultMapper) { var t = obj.GetType(); @@ -96,42 +158,41 @@ public static PocoData ForObject(object obj, string primaryKeyName, IMapper defa return ForType(t, defaultMapper); } - public static PocoData ForType(Type type, IMapper defaultMapper) + /// + /// Creates a factory function to generate and cache a POCO from a data reader record at runtime. Subsequent reads attempt to locate + /// the object in the for performance gains. + /// + /// The SQL statement. + /// The connection string. + /// The index of the first column in the record's database table. + /// The number of columns in the record's database table. + /// The data reader instance. + /// The default mapper to use for the POCO. + /// A delegate that can convert an record into a POCO. + /// The POCO type is a value type, or the POCO type has no default constructor, or the + /// POCO type is an interface or abstract class. + public Delegate GetFactory(string sql, string connectionString, int firstColumn, int columnCount, IDataReader reader, IMapper defaultMapper) { - if (type == typeof(System.Dynamic.ExpandoObject)) - throw new InvalidOperationException("Can't use dynamic types with this method"); - - return _pocoDatas.GetOrAdd(type, () => new PocoData(type, defaultMapper)); - } + // Create key for cache lookup + var key = Tuple.Create(sql, connectionString, firstColumn, columnCount); - private static bool IsIntegralType(Type type) - { - var tc = Type.GetTypeCode(type); - return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; - } - - // Create factory function that can convert a IDataReader record into a POCO - public Delegate GetFactory(string sql, string connectionString, int firstColumn, int countColumns, IDataReader reader, IMapper defaultMapper) - { // Check cache - var key = Tuple.Create(sql, connectionString, firstColumn, countColumns); - return PocoFactories.GetOrAdd(key, () => { // Create the method - var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), Type, new Type[] { typeof(IDataReader) }, true); + var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), returnType: Type, new Type[] { typeof(IDataReader) }, true); var il = m.GetILGenerator(); var mapper = Mappers.GetMapper(Type, defaultMapper); if (Type == typeof(object)) { - // var poco=new T() + // var poco = new T() il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) + for (int i = firstColumn; i < firstColumn + columnCount; i++) { var srcType = reader.GetFieldType(i); @@ -139,12 +200,11 @@ public Delegate GetFactory(string sql, string connectionString, int firstColumn, il.Emit(OpCodes.Ldstr, reader.GetName(i)); // obj, obj, fieldname // Get the converter - Func converter = mapper.GetFromDbConverter((PropertyInfo) null, srcType); + Func converter = mapper.GetFromDbConverter((PropertyInfo)null, srcType); + // TODO: No null check for converter? - /* - if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - */ + // if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) + // converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; // Setup stack for call to converter AddConverterToStack(il, converter); @@ -213,15 +273,15 @@ public Delegate GetFactory(string sql, string connectionString, int firstColumn, } else { - // var poco=new T() + // var poco = new T() var ctor = Type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null); if (ctor == null) - throw new InvalidOperationException("Type [" + Type.FullName + "] should have default public or non-public constructor"); + throw new InvalidOperationException("Type [" + Type.FullName + "] should have a default public or non-public constructor"); il.Emit(OpCodes.Newobj, ctor); - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) + // Enumerate all fields generating a `Set` assignment for the column + for (int i = firstColumn; i < firstColumn + columnCount; i++) { // Get the PocoColumn for this db column, ignore if not known PocoColumn pc; @@ -345,7 +405,7 @@ private static Func GetConverter(IMapper mapper, PocoColumn pc, // Standard DateTime->Utc mapper if (pc != null && pc.ForceToUtc && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) { - return delegate(object src) { return new DateTime(((DateTime) src).Ticks, DateTimeKind.Utc); }; + return delegate (object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; } // unwrap nullable types @@ -362,36 +422,42 @@ private static Func GetConverter(IMapper mapper, PocoColumn pc, if (underlyingDstType != null) { // if dstType is Nullable, convert to enum value - return delegate(object src) { return Enum.ToObject(dstType, src); }; + return delegate (object src) { return Enum.ToObject(dstType, src); }; } else if (srcType != backingDstType) { - return delegate(object src) { return Convert.ChangeType(src, backingDstType, null); }; + return delegate (object src) { return Convert.ChangeType(src, backingDstType, null); }; } } else if (!dstType.IsAssignableFrom(srcType)) { if (dstType.IsEnum && srcType == typeof(string)) { - return delegate(object src) { return EnumMapper.EnumFromString(dstType, (string) src); }; + return delegate (object src) { return EnumMapper.EnumFromString(dstType, (string)src); }; } if (dstType == typeof(Guid) && srcType == typeof(string)) { - return delegate(object src) { return Guid.Parse((string) src); }; + return delegate (object src) { return Guid.Parse((string)src); }; } if (dstType == typeof(string) && srcType == typeof(Guid)) { - return delegate(object src) { return Convert.ToString(src); }; + return delegate (object src) { return Convert.ToString(src); }; } - return delegate(object src) { return Convert.ChangeType(src, dstType, null); }; + return delegate (object src) { return Convert.ChangeType(src, dstType, null); }; } return null; } + private static bool IsIntegralType(Type type) + { + var tc = Type.GetTypeCode(type); + return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; + } + private static T RecurseInheritedTypes(Type t, Func cb) { while (t != null) @@ -405,12 +471,22 @@ private static T RecurseInheritedTypes(Type t, Func cb) return default(T); } + /// + /// Clears all cached PocoData instances. + /// + /// + /// Call if you have modified a POCO class and need to reset PetaPoco's internal cache. + /// public static void FlushCaches() => _pocoDatas.Flush(); + /// + /// Gets the column name that is mapped to the given property name using a string comparison. + /// + /// The name of the property. + /// The column name that maps to the given property name. + /// No mapped column exists for . public string GetColumnName(string propertyName) - { - return Columns.Values.First(c => c.PropertyInfo.Name.Equals(propertyName)).ColumnName; - } + => Columns.Values.First(c => c.PropertyInfo.Name.Equals(propertyName)).ColumnName; } } diff --git a/PetaPoco/Core/Sql.cs b/PetaPoco/Core/Sql.cs index 6e5ffbbd..f79a6e1f 100644 --- a/PetaPoco/Core/Sql.cs +++ b/PetaPoco/Core/Sql.cs @@ -1,269 +1,273 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using PetaPoco.Internal; - -namespace PetaPoco -{ - /// - /// A simple helper class for building SQL statements - /// - public class Sql - { - private object[] _args; - private object[] _argsFinal; - private Sql _rhs; - - private string _sql; - private string _sqlFinal; - - /// - /// Instantiate a new SQL Builder object. Weirdly implemented as a property but makes - /// for more elegantly readable fluent style construction of SQL Statements - /// eg: db.Query(Sql.Builder.Append(....)) - /// - public static Sql Builder - { - get { return new Sql(); } - } - - /// - /// Returns the final SQL statement represented by this builder - /// - public string SQL - { - get - { - Build(); - return _sqlFinal; - } - } - - /// - /// Gets the complete, final set of arguments collected by this builder. - /// - public object[] Arguments - { - get - { - Build(); - return _argsFinal; - } - } - - /// - /// Default, empty constructor - /// - public Sql() - { - } - - /// - /// Construct an SQL statement with the supplied SQL and arguments - /// - /// The SQL statement or fragment - /// Arguments to any parameters embedded in the SQL - public Sql(string sql, params object[] args) - { - _sql = sql; - _args = args; - } - - private void Build() - { - // already built? - if (_sqlFinal != null) - return; - - // Build it - var sb = new StringBuilder(); - var args = new List(); - Build(sb, args, null); - _sqlFinal = sb.ToString(); - _argsFinal = args.ToArray(); - } - - /// - /// Append another SQL builder instance to the right-hand-side of this SQL builder - /// - /// A reference to another SQL builder instance - /// A reference to this builder, allowing for fluent style concatenation - public Sql Append(Sql sql) - { - if (_rhs != null) - _rhs.Append(sql); - else - _rhs = sql; - - _sqlFinal = null; - return this; - } - - /// - /// Append an SQL fragment to the right-hand-side of this SQL builder - /// - /// The SQL statement or fragment - /// Arguments to any parameters embedded in the SQL - /// A reference to this builder, allowing for fluent style concatenation - public Sql Append(string sql, params object[] args) - { - return Append(new Sql(sql, args)); - } - - private static bool Is(Sql sql, string sqltype) - { - return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); - } - - private void Build(StringBuilder sb, List args, Sql lhs) - { - if (!string.IsNullOrEmpty(_sql)) - { - // Add SQL to the string - if (sb.Length > 0) - { - sb.Append("\n"); - } - - var sql = ParametersHelper.ProcessQueryParams(_sql, _args, args); - - if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) - sql = "AND " + sql.Substring(6); - if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) - sql = ", " + sql.Substring(9); - // add set clause - if (Is(lhs, "SET ") && Is(this, "SET ")) - sql = ", " + sql.Substring(4); - - sb.Append(sql); - } - - // Now do rhs - if (_rhs != null) - _rhs.Build(sb, args, this); - } - - /// - /// Appends an SQL SET clause to this SQL builder - /// - /// The SET clause like "{field} = {value}" - /// Arguments to any parameters embedded in the supplied SQL - /// A reference to this builder, allowing for fluent style concatenation - public Sql Set(string sql, params object[] args) - { - return Append(new Sql("SET " + sql, args)); - } - - /// - /// Appends an SQL WHERE clause to this SQL builder - /// - /// The condition of the WHERE clause - /// Arguments to any parameters embedded in the supplied SQL - /// A reference to this builder, allowing for fluent style concatenation - public Sql Where(string sql, params object[] args) - { - return Append(new Sql("WHERE (" + sql + ")", args)); - } - - /// - /// Appends an SQL ORDER BY clause to this SQL builder - /// - /// A collection of SQL column names to order by - /// A reference to this builder, allowing for fluent style concatenation - public Sql OrderBy(params object[] columns) - { - return Append(new Sql("ORDER BY " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL SELECT clause to this SQL builder - /// - /// A collection of SQL column names to select - /// A reference to this builder, allowing for fluent style concatenation - public Sql Select(params object[] columns) - { - return Append(new Sql("SELECT " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL FROM clause to this SQL builder - /// - /// A collection of table names to be used in the FROM clause - /// A reference to this builder, allowing for fluent style concatenation - public Sql From(params object[] tables) - { - return Append(new Sql("FROM " + string.Join(", ", (from x in tables select x.ToString()).ToArray()))); - } - - /// - /// Appends an SQL GROUP BY clause to this SQL builder - /// - /// A collection of column names to be grouped by - /// A reference to this builder, allowing for fluent style concatenation - public Sql GroupBy(params object[] columns) - { - return Append(new Sql("GROUP BY " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - private SqlJoinClause Join(string joinType, string table) - { - return new SqlJoinClause(Append(new Sql(joinType + table))); - } - - /// - /// Appends an SQL INNER JOIN clause to this SQL builder - /// - /// The name of the table to join - /// A reference an SqlJoinClause through which the join condition can be specified - public SqlJoinClause InnerJoin(string table) - { - return Join("INNER JOIN ", table); - } - - /// - /// Appends an SQL LEFT JOIN clause to this SQL builder - /// - /// The name of the table to join - /// A reference an SqlJoinClause through which the join condition can be specified - public SqlJoinClause LeftJoin(string table) - { - return Join("LEFT JOIN ", table); - } - - /// - /// Returns the SQL statement. - /// - /// - /// Returns the final SQL statement represented by this builder - /// - public override string ToString() - { - return SQL; - } - - /// - /// The SqlJoinClause is a simple helper class used in the construction of SQL JOIN statements with the SQL builder - /// - public class SqlJoinClause - { - private readonly Sql _sql; - - public SqlJoinClause(Sql sql) - { - _sql = sql; - } - - /// - /// Appends a SQL ON clause after a JOIN statement - /// - /// The ON clause to be appended - /// Arguments to any parameters embedded in the supplied SQL - /// A reference to the parent SQL builder, allowing for fluent style concatenation - public Sql On(string onClause, params object[] args) - { - return _sql.Append("ON " + onClause, args); - } - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco.Internal; + +namespace PetaPoco +{ + /// + /// A simple helper class for building mutable SQL statements. + /// + public class Sql + { + private Sql _rhs; + private string _sql; + private string _sqlFinal; + private object[] _args; + private object[] _argsFinal; + + /// + /// Gets a new initialized instance of the builder class. + /// + /// + /// Weirdly implemented as a property, but makes for more elegant and readable fluent-style construction of SQL Statements: + ///
db.Query(Sql.Builder.Append(/*...*/));. + ///
+ public static Sql Builder + { + get { return new Sql(); } + } + + /// + /// Gets the final SQL statement stored in this builder. + /// + public string SQL + { + get + { + Build(); + return _sqlFinal; + } + } + + /// + /// Gets the complete, final array of arguments collected by this builder. + /// + public object[] Arguments + { + get + { + Build(); + return _argsFinal; + } + } + + /// + /// Creates a new instance of the builder class with default values. + /// + public Sql() + { + } + + /// + /// Construct an SQL statement from the given SQL string and arguments. + /// + /// The SQL clause or statement. + /// The parameters to embed in the SQL string. + public Sql(string sql, params object[] args) + { + _sql = sql; + _args = args; + } + + private void Build() + { + // already built? + if (_sqlFinal != null) + return; + + // Build it + var sb = new StringBuilder(); + var args = new List(); + Build(sb, args, null); + _sqlFinal = sb.ToString(); + _argsFinal = args.ToArray(); + } + + /// + /// Appends another Sql builder instance to the right-hand-side of this Sql builder. + /// + /// An SQL builder instance. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql Append(Sql sql) + { + if (_rhs != null) + _rhs.Append(sql); + else + _rhs = sql; + + _sqlFinal = null; + return this; + } + + /// + /// Appends an SQL fragment to the right-hand-side of this Sql builder instance. + /// + /// The SQL clause or statement. + /// The parameters to embed in the SQL string. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql Append(string sql, params object[] args) + { + return Append(new Sql(sql, args)); + } + + private static bool Is(Sql sql, string sqltype) + { + return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); + } + + private void Build(StringBuilder sb, List args, Sql lhs) + { + if (!string.IsNullOrEmpty(_sql)) + { + // Add SQL to the string + if (sb.Length > 0) + { + sb.Append("\n"); + } + + var sql = ParametersHelper.ProcessQueryParams(_sql, _args, args); + + if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) + sql = "AND " + sql.Substring(6); + if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) + sql = ", " + sql.Substring(9); + // add set clause + if (Is(lhs, "SET ") && Is(this, "SET ")) + sql = ", " + sql.Substring(4); + + sb.Append(sql); + } + + // Now do rhs + if (_rhs != null) + _rhs.Build(sb, args, this); + } + + /// + /// Appends a SET clause to this Sql builder. + /// + /// The SQL string representing the assignment portion of the SET clause: {field} = {value}. + /// The parameters to embed in the SQL string. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql Set(string sql, params object[] args) + { + return Append(new Sql("SET " + sql, args)); + } + + /// + /// Appends a WHERE clause to this Sql builder. + /// + /// The SQL string representing the condition portion of the WHERE clause: {field} = {value}. + /// The parameters to embed in the SQL string. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql Where(string sql, params object[] args) + { + return Append(new Sql("WHERE (" + sql + ")", args)); + } + + /// + /// Appends an ORDER BY clause to this Sql builder. + /// + /// The column names to order by. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql OrderBy(params object[] columns) + { + return Append(new Sql("ORDER BY " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + /// + /// Appends a SELECT clause to this Sql builder. + /// + /// The column names to include in the SELECT clause. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql Select(params object[] columns) + { + return Append(new Sql("SELECT " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + /// + /// Appends a FROM clause to this Sql builder. + /// + /// The table names to include in the FROM clause. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql From(params object[] tables) + { + return Append(new Sql("FROM " + string.Join(", ", (from x in tables select x.ToString()).ToArray()))); + } + + /// + /// Appends a GROUP BY clause to this Sql builder. + /// + /// The column names to group by. + /// This Sql builder instance, allowing for fluent style concatenation. + public Sql GroupBy(params object[] columns) + { + return Append(new Sql("GROUP BY " + string.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + private SqlJoinClause Join(string joinType, string table) + { + return new SqlJoinClause(Append(new Sql(joinType + table))); + } + + /// + /// Appends an INNER JOIN clause to this Sql builder. + /// + /// The name of the table to join. + /// An SqlJoinClause instance, to be used to append the JOIN conditions. + public SqlJoinClause InnerJoin(string tableName) + { + return Join("INNER JOIN ", tableName); + } + + /// + /// Appends a LEFT JOIN clause to this Sql builder. + /// + /// The name of the table to join. + /// An SqlJoinClause instance, to be used to append the JOIN conditions. + public SqlJoinClause LeftJoin(string tableName) + { + return Join("LEFT JOIN ", tableName); + } + + /// + /// Returns the complete SQL statement represented by this builder. + /// + /// The complete SQL statement. + public override string ToString() + { + return SQL; + } + + /// + /// The SqlJoinClause is a simple builder helper class used to build JOIN clauses. + /// + public class SqlJoinClause + { + private readonly Sql _sql; + + /// + /// Creates a new instance from the specified builder. + /// + /// An SQL builder instance. + public SqlJoinClause(Sql sql) + { + _sql = sql; + } + + /// + /// Appends an ON expression to the JOIN clause. + /// + /// The SQL expression defining the ON condition for the JOIN clause: {table1}.{column1} = + /// {table2}.{column2}. + /// The parameters to embed in the SQL string. + /// The parent Sql builder, allowing for fluent style concatenation. + public Sql On(string onClause, params object[] args) + { + return _sql.Append("ON " + onClause, args); + } + } + } +} diff --git a/PetaPoco/Core/StandardMapper.cs b/PetaPoco/Core/StandardMapper.cs index 269e93df..c77f0550 100644 --- a/PetaPoco/Core/StandardMapper.cs +++ b/PetaPoco/Core/StandardMapper.cs @@ -1,64 +1,26 @@ -using System; -using System.Reflection; - -namespace PetaPoco -{ - /// - /// StandardMapper is the old default implementation of IMapper used by PetaPoco - /// - public class StandardMapper : IMapper - { - /// - /// Get information about the table associated with a POCO class - /// - /// The poco type. - /// A TableInfo instance - /// - /// This method must return a valid TableInfo. - /// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco - /// - public virtual TableInfo GetTableInfo(Type pocoType) - { - return TableInfo.FromPoco(pocoType); - } - - /// - /// Get information about the column associated with a property of a POCO - /// - /// The PropertyInfo of the property being queried - /// A reference to a ColumnInfo instance, or null to ignore this property - /// - /// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty - /// - public virtual ColumnInfo GetColumnInfo(PropertyInfo pocoProperty) - { - return ColumnInfo.FromProperty(pocoProperty); - } - - /// - /// Supply a function to convert a database value to the correct property value - /// - /// The target property - /// The type of data returned by the DB - /// A Func that can do the conversion, or null for no conversion - public virtual Func GetFromDbConverter(PropertyInfo targetProperty, Type sourceType) - { - return null; - } - - /// - /// Supply a function to convert a property value into a database value - /// - /// The property to be converted - /// A Func that can do the conversion - /// - /// This conversion is only used for converting values from POCOs that are - /// being Inserted or Updated. - /// Conversion is not available for parameter values passed directly to queries. - /// - public virtual Func GetToDbConverter(PropertyInfo sourceProperty) - { - return null; - } - } -} \ No newline at end of file +using System; +using System.Reflection; + +namespace PetaPoco +{ + /// + /// This is the original default implementation of used by PetaPoco. + /// + /// + /// PetaPoco now ships with an improved as the default mapper. + /// + public class StandardMapper : IMapper + { + /// + public virtual TableInfo GetTableInfo(Type pocoType) => TableInfo.FromPoco(pocoType); + + /// + public virtual ColumnInfo GetColumnInfo(PropertyInfo pocoProperty) => ColumnInfo.FromProperty(pocoProperty); + + /// + public virtual Func GetFromDbConverter(PropertyInfo targetProperty, Type sourceType) => null; + + /// + public virtual Func GetToDbConverter(PropertyInfo sourceProperty) => null; + } +} diff --git a/PetaPoco/Core/TableInfo.cs b/PetaPoco/Core/TableInfo.cs index dbc79f59..58e7e90d 100644 --- a/PetaPoco/Core/TableInfo.cs +++ b/PetaPoco/Core/TableInfo.cs @@ -1,82 +1,81 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace PetaPoco -{ - /// - /// Use by IMapper to override table bindings for an object - /// - public class TableInfo - { - /// - /// The database table name - /// - public string TableName { get; set; } - - /// - /// The name of the primary key column of the table - /// - public string PrimaryKey { get; set; } - - /// - /// True if the primary key column is an auto-incrementing - /// - public bool AutoIncrement { get; set; } - - /// - /// The name of the sequence used for auto-incrementing Oracle primary key fields - /// - public string SequenceName { get; set; } - - /// - /// Creates and populates a TableInfo from the attributes of a POCO - /// - /// The POCO type - /// A TableInfo instance - public static TableInfo FromPoco(Type t) - { - var ti = new TableInfo(); - PopulateTableNameFromPoco(t, ref ti, out _); - PopulatePrimaryKeyFromPoco(t, ref ti, out _, out _); - return ti; - } - - - internal static void PopulateTableNameFromPoco(Type t, ref TableInfo ti, out TableNameAttribute tblAttr) - { - ti = ti ?? new TableInfo(); - tblAttr = t.GetCustomAttributes(typeof(TableNameAttribute), true).FirstOrDefault() as TableNameAttribute; - ti.TableName = tblAttr?.Value ?? t.Name; - } - - internal static void PopulatePrimaryKeyFromPoco(Type t, ref TableInfo ti, out PrimaryKeyAttribute pkAttr, out PropertyInfo idProp) - { - ti = ti ?? new TableInfo(); - pkAttr = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true).FirstOrDefault() as PrimaryKeyAttribute; - idProp = null; - - ti.PrimaryKey = pkAttr?.Value; - ti.SequenceName = pkAttr?.SequenceName; - ti.AutoIncrement = pkAttr?.AutoIncrement ?? false; - - if (String.IsNullOrWhiteSpace(ti.PrimaryKey)) - { - bool isIdProp(PropertyInfo p) - { - bool hasName(string name) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase); - return hasName("id") - || hasName(t.Name + "id") - || hasName(t.Name + "_id"); - } - - idProp = t.GetProperties().FirstOrDefault(isIdProp) as PropertyInfo; - if (idProp != null) - { - ti.PrimaryKey = idProp.Name; - ti.AutoIncrement = idProp.PropertyType.IsValueType; - } - } - } - } -} \ No newline at end of file +using System; +using System.Linq; +using System.Reflection; + +namespace PetaPoco +{ + /// + /// A class used by to override table bindings for a POCO object. + /// + public class TableInfo + { + /// + /// Gets or sets the database table name. + /// + public string TableName { get; set; } + + /// + /// Gets or sets the name of the table's primary key column. + /// + public string PrimaryKey { get; set; } + + /// + /// Gets or sets whether the primary key column is auto-incrementing. + /// + public bool AutoIncrement { get; set; } + + /// + /// Gets or sets the name of the sequence used for auto-incrementing Oracle primary key fields. + /// + public string SequenceName { get; set; } + + /// + /// Constructs and initializes a TableInfo instance from the attributes of the specified POCO type. + /// + /// The POCO type representing a single result record in the associated database table. + /// The TableInfo instance. + public static TableInfo FromPoco(Type pocoType) + { + var ti = new TableInfo(); + PopulateTableNameFromPoco(pocoType, ref ti, out _); + PopulatePrimaryKeyFromPoco(pocoType, ref ti, out _, out _); + return ti; + } + + internal static void PopulateTableNameFromPoco(Type t, ref TableInfo ti, out TableNameAttribute tblAttr) + { + ti = ti ?? new TableInfo(); + tblAttr = t.GetCustomAttributes(typeof(TableNameAttribute), true).FirstOrDefault() as TableNameAttribute; + ti.TableName = tblAttr?.Value ?? t.Name; + } + + internal static void PopulatePrimaryKeyFromPoco(Type t, ref TableInfo ti, out PrimaryKeyAttribute pkAttr, out PropertyInfo idProp) + { + ti = ti ?? new TableInfo(); + pkAttr = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true).FirstOrDefault() as PrimaryKeyAttribute; + idProp = null; + + ti.PrimaryKey = pkAttr?.Value; + ti.SequenceName = pkAttr?.SequenceName; + ti.AutoIncrement = pkAttr?.AutoIncrement ?? false; + + if (String.IsNullOrWhiteSpace(ti.PrimaryKey)) + { + bool isIdProp(PropertyInfo p) + { + bool hasName(string name) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase); + return hasName("id") + || hasName(t.Name + "id") + || hasName(t.Name + "_id"); + } + + idProp = t.GetProperties().FirstOrDefault(isIdProp) as PropertyInfo; + if (idProp != null) + { + ti.PrimaryKey = idProp.Name; + ti.AutoIncrement = idProp.PropertyType.IsValueType; + } + } + } + } +} diff --git a/PetaPoco/Core/Transaction.cs b/PetaPoco/Core/Transaction.cs index 6533c01a..3df31ff8 100644 --- a/PetaPoco/Core/Transaction.cs +++ b/PetaPoco/Core/Transaction.cs @@ -1,28 +1,38 @@ namespace PetaPoco { /// - /// Transaction object helps maintain transaction depth counts + /// A scoped Transaction object to facilitate maintaining transaction depth counts and proper rollbacks. /// public class Transaction : ITransaction { private IDatabase _db; - public Transaction(IDatabase db) + /// + /// Creates a new Transaction instance for the specified database, and begins the transaction. + /// + /// The database instance that will execute the transaction. + /// + public Transaction(IDatabase database) { - _db = db; + _db = database; _db.BeginTransaction(); } + /// public void Complete() { _db.CompleteTransaction(); _db = null; } + + /// + /// Closes the transaction scope, rolling back the transaction if not completed by a call to . + /// + /// public void Dispose() { - if (_db != null) - _db.AbortTransaction(); + _db?.AbortTransaction(); } } -} \ No newline at end of file +} diff --git a/PetaPoco/Database.cs b/PetaPoco/Database.cs index 0ee05d82..b128720e 100644 --- a/PetaPoco/Database.cs +++ b/PetaPoco/Database.cs @@ -1,5 +1,8 @@ -using System; +using System; using System.Collections.Generic; +#if !NETSTANDARD +using System.Configuration; +#endif using System.Data; using System.Data.Common; using System.Dynamic; @@ -11,61 +14,40 @@ using PetaPoco.Core; using PetaPoco.Internal; using PetaPoco.Utilities; -#if !NETSTANDARD -using System.Configuration; - -#endif namespace PetaPoco { - /// + /// + /// Represents the core functionality and implementation of PetaPoco. + /// public class Database : IDatabase { -#region Internal operations - - internal void DoPreExecute(IDbCommand cmd) - { - if (CommandTimeout > 0 || OneTimeCommandTimeout > 0) - { - cmd.CommandTimeout = OneTimeCommandTimeout > 0 ? OneTimeCommandTimeout : CommandTimeout; - OneTimeCommandTimeout = 0; - } - - _provider.PreExecute(cmd); - OnExecutingCommand(cmd); - - _lastSql = cmd.CommandText; - _lastArgs = cmd.Parameters.Cast().Select(parameter => parameter.Value).ToArray(); - } - -#endregion - -#region Member Fields + #region Member Fields private IMapper _defaultMapper; private string _connectionString; private IProvider _provider; + private IsolationLevel? _isolationLevel; private IDbConnection _sharedConnection; private IDbTransaction _transaction; + private DbProviderFactory _factory; private int _sharedConnectionDepth; private int _transactionDepth; private bool _transactionCancelled; + private string _paramPrefix; private string _lastSql; private object[] _lastArgs; - private string _paramPrefix; - private DbProviderFactory _factory; - private IsolationLevel? _isolationLevel; -#endregion + #endregion -#region Constructors + #region Constructors #if !NETSTANDARD /// - /// Constructs an instance using the first connection string found in the app/web configuration file. + /// Constructs an instance with default values using the first connection string found in the app/web configuration file. /// /// The default mapper to use when no specific mapper has been registered. - /// Thrown when no connection strings can registered. + /// No connection strings are registered. public Database(IMapper defaultMapper = null) { if (ConfigurationManager.ConnectionStrings.Count == 0) @@ -77,13 +59,16 @@ public Database(IMapper defaultMapper = null) } /// - /// Constructs an instance using a supplied connection string name. The actual connection string and provider will be - /// read from app/web.config. + /// Constructs an instance with the specified connection string name. The connection string and database provider will be read from + /// the app or web configuration file. /// - /// The name of the connection. + /// + /// PetaPoco will automatically close and dispose of any connections it creates. + /// + /// The name of the connection string to locate. /// The default mapper to use when no specific mapper has been registered. - /// Thrown when is null or empty. - /// Thrown when a connection string cannot be found. + /// is null or empty. + /// A connection string cannot be found. public Database(string connectionStringName, IMapper defaultMapper = null) { if (string.IsNullOrEmpty(connectionStringName)) @@ -106,15 +91,14 @@ private void InitialiseFromEntry(ConnectionStringSettings entry, IMapper default #endif /// - /// Constructs an instance using a supplied IDbConnection. + /// Constructs an instance with the specified IDbConnection. /// - /// The IDbConnection to use. - /// The default mapper to use when no specific mapper has been registered. /// - /// The supplied IDbConnection will not be closed/disposed by PetaPoco - that remains - /// the responsibility of the caller. + /// The supplied IDbConnection will not be closed and disposed of by PetaPoco - that remains the responsibility of the caller. /// - /// Thrown when is null or empty. + /// The database connection. + /// The default mapper to use when no specific mapper has been registered. + /// is null or empty. public Database(IDbConnection connection, IMapper defaultMapper = null) { if (connection == null) @@ -124,25 +108,17 @@ public Database(IDbConnection connection, IMapper defaultMapper = null) Initialise(DatabaseProvider.Resolve(_sharedConnection.GetType(), false, _connectionString), defaultMapper); } - private void SetupFromConnection(IDbConnection connection) - { - _sharedConnection = connection; - _connectionString = connection.ConnectionString; - - // Prevent closing external connection - _sharedConnectionDepth = 2; - } - /// - /// Constructs an instance using a supplied connection string and provider name. + /// Constructs an instance with the specified connection string and database provider name. /// - /// The database connection string. - /// The database provider name. - /// The default mapper to use when no specific mapper has been registered. /// - /// PetaPoco will automatically close and dispose any connections it creates. + /// PetaPoco will automatically close and dispose of any connections it creates. /// - /// Thrown when is null or empty. + /// The connection string. + /// The database provider name. + /// The default mapper to use when no specific mapper has been registered. + /// or is null or + /// empty. public Database(string connectionString, string providerName, IMapper defaultMapper = null) { if (string.IsNullOrEmpty(connectionString)) @@ -155,18 +131,20 @@ public Database(string connectionString, string providerName, IMapper defaultMap } /// - /// Constructs an instance using the supplied connection string and DbProviderFactory. + /// Constructs an instance with the specified connection string and DbProviderFactory. /// - /// The database connection string. - /// The DbProviderFactory to use for instantiating IDbConnections. + /// + /// PetaPoco will automatically close and dispose of any connections it creates. + /// + /// The connection string. + /// The database provider factory to use for database connections. /// The default mapper to use when no specific mapper has been registered. - /// Thrown when is null or empty. - /// Thrown when is null. + /// is null or empty. + /// is null. public Database(string connectionString, DbProviderFactory factory, IMapper defaultMapper = null) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentException("Connection string must not be null or empty", nameof(connectionString)); - if (factory == null) throw new ArgumentNullException(nameof(factory)); @@ -175,18 +153,20 @@ public Database(string connectionString, DbProviderFactory factory, IMapper defa } /// - /// Constructs an instance using the supplied provider and optional default mapper. + /// Constructs an instance with the specified connection string and IProvider. /// - /// The database connection string. - /// The provider to use. + /// + /// PetaPoco will automatically close and dispose of any connections it creates. + /// + /// The connection string. + /// The database provider. /// The default mapper to use when no specific mapper has been registered. - /// Thrown when is null or empty. - /// Thrown when is null. + /// is null or empty. + /// is null. public Database(string connectionString, IProvider provider, IMapper defaultMapper = null) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentException("Connection string must not be null or empty", nameof(connectionString)); - if (provider == null) throw new ArgumentNullException(nameof(provider)); @@ -195,21 +175,18 @@ public Database(string connectionString, IProvider provider, IMapper defaultMapp } /// - /// Constructs an instance using the supplied . + /// Constructs an instance with the configured settings from the specified IDatabaseBuildConfiguration object. /// - /// The configuration for constructing an instance. - /// Thrown when is null. - /// - /// Thrown when no configuration string is configured and app/web config does - /// any connection string registered. - /// - /// Thrown when a connection string configured and no provider is configured. + /// The build configuration instance. + /// is null. + /// A connection string cannot be found or is not configured, or unable to locate a + /// provider. public Database(IDatabaseBuildConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - var settings = (IBuildConfigurationSettings) configuration; + var settings = (IBuildConfigurationSettings)configuration; IMapper defaultMapper = null; settings.TryGetSetting(DatabaseConfigurationExtensions.DefaultMapper, v => defaultMapper = v); @@ -243,12 +220,12 @@ public Database(IDatabaseBuildConfiguration configuration) { entry = ConfigurationManager.ConnectionStrings[connectionStringName]; if (entry == null) - throw new InvalidOperationException($"Can't find a connection string with the name '{connectionStringName}'"); + throw new InvalidOperationException($"Cannot find a connection string with the name '{connectionStringName}'"); } else { if (ConfigurationManager.ConnectionStrings.Count == 0) - throw new InvalidOperationException("One or more connection strings must be registered, when not providing a connection string"); + throw new InvalidOperationException("One or more connection strings must be registered when not providing a connection string"); entry = ConfigurationManager.ConnectionStrings[0]; } @@ -289,9 +266,15 @@ public Database(IDatabaseBuildConfiguration configuration) settings.TryGetSetting>(DatabaseConfigurationExtensions.ExceptionThrown, v => ExceptionThrown += v); } - /// - /// Provides common initialization for the various constructors. - /// + private void SetupFromConnection(IDbConnection connection) + { + _sharedConnection = connection; + _connectionString = connection.ConnectionString; + + // Prevent closing external connection + _sharedConnectionDepth = 2; + } + private void Initialise(IProvider provider, IMapper mapper) { // Reset @@ -307,38 +290,14 @@ private void Initialise(IProvider provider, IMapper mapper) _defaultMapper = mapper ?? new ConventionMapper(); } -#endregion - -#region Connection Management + #endregion - /// - /// When set to true the first opened connection is kept alive until - /// or is called. - /// - /// - public bool KeepConnectionAlive { get; set; } + #region Connection Management (IConnection implementation) - /// - /// Provides access to the currently open shared connection. - /// - /// - /// The currently open connection, or Null. - /// - /// - /// - /// + /// public IDbConnection Connection => _sharedConnection; - /// - /// Opens a connection that will be used for all subsequent queries. - /// - /// - /// Calls to / are reference - /// counted and should be balanced - /// - /// - /// - /// + /// public void OpenSharedConnection() { if (_sharedConnectionDepth == 0) @@ -364,15 +323,12 @@ public void OpenSharedConnection() } #if ASYNC - /// - /// The async version of . - /// + /// public Task OpenSharedConnectionAsync() => OpenSharedConnectionAsync(CancellationToken.None); - /// - /// The async version of . - /// + /// A cancellation token that can be used to cancel the operation. + /// public async Task OpenSharedConnectionAsync(CancellationToken cancellationToken) { if (_sharedConnectionDepth == 0) @@ -404,16 +360,7 @@ public async Task OpenSharedConnectionAsync(CancellationToken cancellationToken) } #endif - /// - /// Releases the shared connection. - /// - /// - /// Calls to / are reference - /// counted and should be balanced - /// - /// - /// - /// + /// public void CloseSharedConnection() { if (_sharedConnectionDepth > 0) @@ -429,31 +376,62 @@ public void CloseSharedConnection() } /// - /// Alias for . + /// Releases the shared connection. /// /// - /// Called implicitly when making use of the .NET `using` language feature. + /// Implicitly called when the instance goes out of scope at the end of a using block, calling to ensure the connection is properly closed. /// public void Dispose() { // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) + // (Works with KeepConnectionAlive and manually opening a shared connection) CloseSharedConnection(); } -#endregion + #endregion -#region Transaction Management + #region Transaction Management (ITransactionAccessor, IDatabase implementation) - /// + /// IDbTransaction ITransactionAccessor.Transaction => _transaction; - /// + /// + /// + /// This method facilitates proper transaction lifetime management, especially when nested. Transactions can be nested but they must + /// all include a call to prior to exiting the scope, otherwise the entire transaction is + /// aborted. + /// + /// + /// A basic example of using transactional scopes (part pseudocode) is shown below: + /// + /// + /// + /// + /// An reference that must be completed or disposed. public ITransaction GetTransaction() => new Transaction(this); /// - /// Called when a transaction starts. + /// Called immediately after opening a transaction, and invokes the event. /// public virtual void OnBeginTransaction() { @@ -461,14 +439,14 @@ public virtual void OnBeginTransaction() } /// - /// Called when a transaction ends. + /// Called immediately before closing a transaction, and invokes the event. /// public virtual void OnEndTransaction() { TransactionEnding?.Invoke(this, new DbTransactionEventArgs(_transaction)); } - /// + /// public void BeginTransaction() { _transactionDepth++; @@ -483,11 +461,11 @@ public void BeginTransaction() } #if ASYNC - /// + /// public Task BeginTransactionAsync() => BeginTransactionAsync(CancellationToken.None); - /// + /// public async Task BeginTransactionAsync(CancellationToken cancellationToken) { if (_sharedConnection is DbConnection asyncConn) @@ -516,9 +494,44 @@ public async Task BeginTransactionAsync(CancellationToken cancellationToken) } #endif - /// - /// Internal helper to cleanup transaction - /// + /// + /// + /// Called automatically by if the transaction wasn't completed. + /// + public void AbortTransaction() + { + _transactionCancelled = true; + CompleteTransaction(); + } + +#if ASYNC + /// + public Task AbortTransactionAsync() + { + _transactionCancelled = true; + return CompleteTransactionAsync(); + } +#endif + + /// + /// + /// Not calling complete will cause the transaction to rollback on . + /// + public void CompleteTransaction() + { + if ((--_transactionDepth) == 0) + CleanupTransaction(); + } + +#if ASYNC + /// + public async Task CompleteTransactionAsync() + { + if ((--_transactionDepth) == 0) + await CleanupTransactionAsync().ConfigureAwait(false); + } +#endif + private void CleanupTransaction() { OnEndTransaction(); @@ -534,21 +547,8 @@ private void CleanupTransaction() CloseSharedConnection(); } - /// - public void AbortTransaction() - { - _transactionCancelled = true; - CompleteTransaction(); - } - - /// - public void CompleteTransaction() - { - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - #if ASYNC +#pragma warning disable 1998 private async Task CleanupTransactionAsync() { #if NETSTANDARD2_1 @@ -572,357 +572,143 @@ private async Task CleanupTransactionAsync() CleanupTransaction(); } } +#pragma warning restore 1998 +#endif - public Task AbortTransactionAsync() + #endregion + + #region Exception Reporting and Logging + + // TODO: Not in IDatabase interface: `OnException(Exception)`, to allow GridReader's DI impl to accept IDatabase (IAsyncReader is able to correctly use the interface for it's IOC because it doesn't require the OnException)) + + /// + /// Called if an exception is thrown during a database operation, and invokes the event. + /// + /// The caught exception. + /// to re-throw the exception, to suppress it. + public virtual bool OnException(Exception ex) { - this._transactionCancelled = true; - return CompleteTransactionAsync(); + System.Diagnostics.Debug.WriteLine(ex.ToString()); + System.Diagnostics.Debug.WriteLine(LastCommand); + + var args = new ExceptionEventArgs(ex); + ExceptionThrown?.Invoke(this, new ExceptionEventArgs(ex)); + return args.Raise; } - public async Task CompleteTransactionAsync() + /// + /// Called immediately after a database connection is opened, and invokes the event. + /// + /// + /// Override this method to provide custom logging of opened connections, or to provide a proxy IDbConnection. + /// + /// The opened connection. + /// The same or a replacement IDbConnection. + public virtual IDbConnection OnConnectionOpened(IDbConnection connection) { - if ((--_transactionDepth) == 0) - await CleanupTransactionAsync().ConfigureAwait(false); + var args = new DbConnectionEventArgs(connection); + ConnectionOpened?.Invoke(this, args); + return args.Connection; } -#endif -#endregion + /// + /// Called immediately before a database connection is opened, and invokes the event. + /// + /// + /// Override this method to provide custom logging of opening connections, or to provide a proxy IDbConnection. + /// + /// The connection to be opened. + /// The same or a replacement IDbConnection. + public virtual IDbConnection OnConnectionOpening(IDbConnection connection) + { + var args = new DbConnectionEventArgs(connection); + ConnectionOpening?.Invoke(this, args); + return args.Connection; + } -#region Command Management + /// + /// Called immediately before a database connection is closed, and invokes the event. + /// + /// The connection to be closed. + public virtual void OnConnectionClosing(IDbConnection connection) + { + ConnectionClosing?.Invoke(this, new DbConnectionEventArgs(connection)); + } /// - /// Add a parameter to a DB command + /// Called immediately before a database command is executed, and invokes the event. /// - /// A reference to the IDbCommand to which the parameter is to be added - /// The value to assign to the parameter - /// Optional, a reference to the property info of the POCO property from which the value is coming. - private void AddParam(IDbCommand cmd, object value, PocoColumn pc) + /// + /// Override this method to provide custom logging of commands, modification of the IDbCommand before it's executed, or any other + /// custom actions that should be performed before every command + /// + /// The SQL command to be executed. + public virtual void OnExecutingCommand(IDbCommand cmd) { - // Convert value to from poco type to db type - if (pc?.PropertyInfo != null) - { - var mapper = Mappers.GetMapper(pc.PropertyInfo.DeclaringType, _defaultMapper); - var fn = mapper.GetToDbConverter(pc.PropertyInfo); - if (fn != null) - value = fn(value); - } + CommandExecuting?.Invoke(this, new DbCommandEventArgs(cmd)); + } - // Support passed in parameters - if (value is IDbDataParameter idbParam) - { - if (cmd.CommandType == CommandType.Text) - idbParam.ParameterName = cmd.Parameters.Count.EnsureParamPrefix(_paramPrefix); - else if (idbParam.ParameterName?.StartsWith(_paramPrefix) != true) - idbParam.ParameterName = idbParam.ParameterName.EnsureParamPrefix(_paramPrefix); + /// + /// Called immediately after a database command execution completes, and invokes the event. + /// + /// + /// Override this method to provide custom logging or other actions after every command has completed. + /// + /// The executed SQL command. + public virtual void OnExecutedCommand(IDbCommand cmd) + { + CommandExecuted?.Invoke(this, new DbCommandEventArgs(cmd)); + } - cmd.Parameters.Add(idbParam); - } - else - { - var p = cmd.CreateParameter(); - p.ParameterName = cmd.Parameters.Count.EnsureParamPrefix(_paramPrefix); - SetParameterProperties(p, value, pc); + #endregion - cmd.Parameters.Add(p); - } - } + #region ExecuteNonQuery, ExecuteAsync - private void SetParameterProperties(IDbDataParameter p, object value, PocoColumn pc) - { - // Assign the parameter value - if (value == null) - { - p.Value = DBNull.Value; + /// + public int Execute(Sql sql) + => Execute(sql.SQL, sql.Arguments); - if (pc?.PropertyInfo.PropertyType.Name == "Byte[]") - p.DbType = DbType.Binary; - } - else - { - // Give the database type first crack at converting to DB required type - value = _provider.MapParameterValue(value); + /// + public int Execute(string sql, params object[] args) + => ExecuteInternal(CommandType.Text, sql, args); - var t = value.GetType(); +#if ASYNC + /// + public Task ExecuteAsync(Sql sql) + => ExecuteInternalAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - if (t == typeof(string) && pc?.ForceToAnsiString == true) - { - t = typeof(AnsiString); - value = value.ToAnsiString(); - } - if (t == typeof(DateTime) && pc?.ForceToDateTime2 == true) - { - t = typeof(DateTime2); - value = ((DateTime)value).ToDateTime2(); - } + /// + public Task ExecuteAsync(string sql, params object[] args) + => ExecuteInternalAsync(CancellationToken.None, CommandType.Text, sql, args); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int + /// + public Task ExecuteAsync(CancellationToken cancellationToken, Sql sql) + => ExecuteInternalAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); + + /// + public Task ExecuteAsync(CancellationToken cancellationToken, string sql, params object[] args) + => ExecuteInternalAsync(cancellationToken, CommandType.Text, sql, args); +#endif + + /// The type of command to execute. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// + protected virtual int ExecuteInternal(CommandType commandType, string sql, params object[] args) + { + try + { + OpenSharedConnection(); + try { - p.Value = Convert.ChangeType(value, ((Enum)value).GetTypeCode()); + using (var cmd = CreateCommand(_sharedConnection, commandType, sql, args)) + { + return ExecuteNonQueryHelper(cmd); + } } - else if (t == typeof(Guid) && !_provider.HasNativeGuidSupport) + finally { - p.Value = value.ToString(); - p.DbType = DbType.String; - p.Size = 40; - } - else if (t == typeof(string)) - { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 - if ((value as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - - p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size - p.Value = value; - } - else if (t == typeof(AnsiString)) - { - var asValue = (value as AnsiString).Value; - if (asValue == null) - { - p.Size = 0; - p.Value = DBNull.Value; - } - else - { - p.Size = Math.Max(asValue.Length + 1, 4000); - p.Value = asValue; - } - // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar - p.DbType = DbType.AnsiString; - } - else if (t == typeof(DateTime2)) - { - var dt2Value = (value as DateTime2)?.Value; - p.Value = dt2Value ?? (object)DBNull.Value; - p.DbType = DbType.DateTime2; - } - else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - else if (t == typeof(byte[])) - { - p.Value = value; - p.DbType = DbType.Binary; - } - else - { - p.Value = value; - } - } - } - - public IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args) - => CreateCommand(connection, CommandType.Text, sql, args); - - public IDbCommand CreateCommand(IDbConnection connection, CommandType commandType, string sql, params object[] args) - { - var cmd = connection.CreateCommand(); - - try - { - cmd.CommandType = commandType; - cmd.Transaction = _transaction; - - switch (commandType) - { - case CommandType.Text: - // Perform named argument replacements - if (EnableNamedParams) - { - var newArgs = new List(); - sql = ParametersHelper.ProcessQueryParams(sql, args, newArgs); - args = newArgs.ToArray(); - } - - // Perform parameter prefix replacements - if (_paramPrefix != "@") - sql = sql.ReplaceParamPrefix(_paramPrefix); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ - break; - case CommandType.StoredProcedure: - args = ParametersHelper.ProcessStoredProcParams(cmd, args, SetParameterProperties); - break; - case CommandType.TableDirect: - break; - } - - cmd.CommandText = sql; - - foreach (var item in args) - AddParam(cmd, item, null); - - return cmd; - } - catch - { - cmd.Dispose(); - throw; - } - } - - /// - /// Create an IDbDataParameter with default values. - /// - /// The IDbDataParameter - public IDbDataParameter CreateParameter() => _factory.CreateParameter(); - - /// - /// Create an IDbDataParameter with the given ParameterName and Value. - /// - /// The ParameterName. - /// The Value of the parameter. - /// - public IDbDataParameter CreateParameter(string name, object value) - => CreateParameter(name, value, ParameterDirection.Input); - - /// - /// Create an IDbParameter with the given ParameterName and Direction. - /// - /// The ParameterName. - /// The Direction of the parameter. - /// - public IDbDataParameter CreateParameter(string name, ParameterDirection direction) - => CreateParameter(name, null, direction); - - /// - /// Create an IDbParameter with the given ParameterName, Value, and Direction. - /// - /// The ParameterName. - /// The Value of the parameter. - /// The Direction of the parameter. - /// - public IDbDataParameter CreateParameter(string name, object value, ParameterDirection direction) - { - var result = CreateParameter(); - result.ParameterName = name; - result.Value = value; - result.Direction = direction; - - return result; - } - -#endregion - -#region Exception Reporting and Logging - - /// - /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. - /// - /// The exception instance - /// True to re-throw the exception, false to suppress it - public virtual bool OnException(Exception x) - { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); - - var args = new ExceptionEventArgs(x); - ExceptionThrown?.Invoke(this, new ExceptionEventArgs(x)); - return args.Raise; - } - - /// - /// Called when DB connection opened - /// - /// The newly-opened IDbConnection - /// The same or a replacement IDbConnection - /// - /// Override this method to provide custom logging of opening connection, or - /// to provide a proxy IDbConnection. - /// - public virtual IDbConnection OnConnectionOpened(IDbConnection conn) - { - var args = new DbConnectionEventArgs(conn); - ConnectionOpened?.Invoke(this, args); - return args.Connection; - } - - /// - /// Called before a DB connection is opened - /// - /// The soon-to-be-opened IDbConnection - /// The same or a replacement IDbConnection - /// - /// Override this method to provide custom logging of opening connection, or - /// to provide a proxy IDbConnection. - /// - public virtual IDbConnection OnConnectionOpening(IDbConnection conn) - { - var args = new DbConnectionEventArgs(conn); - ConnectionOpening?.Invoke(this, args); - return args.Connection; - } - - /// - /// Called when DB connection closed - /// - /// The soon-to-be-closed IDBConnection - public virtual void OnConnectionClosing(IDbConnection conn) - { - ConnectionClosing?.Invoke(this, new DbConnectionEventArgs(conn)); - } - - /// - /// Called just before an DB command is executed - /// - /// The command to be executed - /// - /// Override this method to provide custom logging of commands, - /// modification of the IDbCommand before it's executed, or any - /// other custom actions that should be performed before every - /// command - /// - public virtual void OnExecutingCommand(IDbCommand cmd) - { - CommandExecuting?.Invoke(this, new DbCommandEventArgs(cmd)); - } - - /// - /// Called on completion of command execution - /// - /// The IDbCommand that finished executing - /// - /// Override this method to provide custom logging or other actions - /// after every command has completed. - /// - public virtual void OnExecutedCommand(IDbCommand cmd) - { - CommandExecuted?.Invoke(this, new DbCommandEventArgs(cmd)); - } - -#endregion - -#region operation: Execute - - /// - public int Execute(string sql, params object[] args) - => ExecuteInternal(CommandType.Text, sql, args); - - /// - public int Execute(Sql sql) - => Execute(sql.SQL, sql.Arguments); - - protected virtual int ExecuteInternal(CommandType commandType, string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, commandType, sql, args)) - { - return ExecuteNonQueryHelper(cmd); - } - } - finally - { - CloseSharedConnection(); + CloseSharedConnection(); } } catch (Exception x) @@ -934,19 +720,11 @@ protected virtual int ExecuteInternal(CommandType commandType, string sql, param } #if ASYNC - - public Task ExecuteAsync(string sql, params object[] args) - => ExecuteInternalAsync(CancellationToken.None, CommandType.Text, sql, args); - - public Task ExecuteAsync(CancellationToken cancellationToken, string sql, params object[] args) - => ExecuteInternalAsync(cancellationToken, CommandType.Text, sql, args); - - public Task ExecuteAsync(Sql sql) - => ExecuteInternalAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - - public Task ExecuteAsync(CancellationToken cancellationToken, Sql sql) - => ExecuteInternalAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// protected virtual async Task ExecuteInternalAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) { try @@ -971,21 +749,42 @@ protected virtual async Task ExecuteInternalAsync(CancellationToken cancell return -1; } } - #endif -#endregion + #endregion + + #region ExecuteScalar, ExecuteScalarAsync -#region operation: ExecuteScalar + /// + public T ExecuteScalar(Sql sql) + => ExecuteScalar(sql.SQL, sql.Arguments); - /// + /// public T ExecuteScalar(string sql, params object[] args) => ExecuteScalarInternal(CommandType.Text, sql, args); - /// - public T ExecuteScalar(Sql sql) - => ExecuteScalar(sql.SQL, sql.Arguments); +#if ASYNC + /// + public Task ExecuteScalarAsync(Sql sql) + => ExecuteScalarInternalAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); + + /// + public Task ExecuteScalarAsync(string sql, params object[] args) + => ExecuteScalarInternalAsync(CancellationToken.None, CommandType.Text, sql, args); + + /// + public Task ExecuteScalarAsync(CancellationToken cancellationToken, Sql sql) + => ExecuteScalarInternalAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); + + /// + public Task ExecuteScalarAsync(CancellationToken cancellationToken, string sql, params object[] args) + => ExecuteScalarInternalAsync(cancellationToken, CommandType.Text, sql, args); +#endif + /// The type of command to execute. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// protected virtual T ExecuteScalarInternal(CommandType commandType, string sql, params object[] args) { try @@ -1002,7 +801,7 @@ protected virtual T ExecuteScalarInternal(CommandType commandType, string sql if (u != null && (val == null || val == DBNull.Value)) return default(T); - return (T) Convert.ChangeType(val, u == null ? typeof(T) : u); + return (T)Convert.ChangeType(val, u == null ? typeof(T) : u); } } finally @@ -1019,25 +818,12 @@ protected virtual T ExecuteScalarInternal(CommandType commandType, string sql } #if ASYNC - - /// - public Task ExecuteScalarAsync(string sql, params object[] args) - => ExecuteScalarInternalAsync(CancellationToken.None, CommandType.Text, sql, args); - - /// - public Task ExecuteScalarAsync(CancellationToken cancellationToken, string sql, params object[] args) - => ExecuteScalarInternalAsync(cancellationToken, CommandType.Text, sql, args); - - /// - public Task ExecuteScalarAsync(Sql sql) - => ExecuteScalarInternalAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - - /// - public Task ExecuteScalarAsync(CancellationToken cancellationToken, Sql sql) - => ExecuteScalarInternalAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - - protected virtual async Task ExecuteScalarInternalAsync(CancellationToken cancellationToken, CommandType commandType, string sql, - params object[] args) + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// + protected virtual async Task ExecuteScalarInternalAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) { try { @@ -1052,7 +838,7 @@ protected virtual async Task ExecuteScalarInternalAsync(CancellationToken if (u != null && (val == null || val == DBNull.Value)) return default(T); - return (T) Convert.ChangeType(val, u == null ? typeof(T) : u); + return (T)Convert.ChangeType(val, u == null ? typeof(T) : u); } } finally @@ -1067,486 +853,185 @@ protected virtual async Task ExecuteScalarInternalAsync(CancellationToken return default(T); } } - #endif -#endregion - -#region operation: Fetch - - /// - public List Fetch() - => Fetch(string.Empty); + #endregion - /// - public List Fetch(string sql, params object[] args) - => Query(sql, args).ToList(); + #region Query, QueryAsync : Single-POCO - /// - public List Fetch(Sql sql) - => Fetch(sql.SQL, sql.Arguments); + /// + public IEnumerable Query() + => Query(string.Empty); - /// - public List Fetch(long page, long itemsPerPage) - => Fetch(page, itemsPerPage, string.Empty); + /// + public IEnumerable Query(Sql sql) + => Query(sql.SQL, sql.Arguments); - /// - public List Fetch(long page, long itemsPerPage, string sql, params object[] args) - => SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, args); + /// + public IEnumerable Query(string sql, params object[] args) + { + if (EnableAutoSelect) + sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); - /// - public List Fetch(long page, long itemsPerPage, Sql sql) - => SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); + return ExecuteReader(CommandType.Text, sql, args); + } #if ASYNC - /// - public Task> FetchAsync() - => FetchAsync(CancellationToken.None, CommandType.Text, string.Empty); - - /// - public Task> FetchAsync(CommandType commandType) - => FetchAsync(CancellationToken.None, CommandType.Text, string.Empty); + /// + public Task> QueryAsync() + => QueryAsync(CancellationToken.None, CommandType.Text, string.Empty); - /// - public Task> FetchAsync(CancellationToken cancellationToken) - => FetchAsync(cancellationToken, CommandType.Text, string.Empty); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType) - => FetchAsync(cancellationToken, commandType, string.Empty); - - /// - public Task> FetchAsync(string sql, params object[] args) - => FetchAsync(CancellationToken.None, CommandType.Text, sql, args); - - /// - public Task> FetchAsync(CommandType commandType, string sql, params object[] args) - => FetchAsync(CancellationToken.None, commandType, sql, args); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, string sql, params object[] args) - => FetchAsync(CancellationToken.None, CommandType.Text, sql, args); - - /// - public async Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) - { - var pocos = new List(); - await QueryAsync(p => pocos.Add(p), cancellationToken, commandType, sql, args).ConfigureAwait(false); - return pocos; - } - - /// - public Task> FetchAsync(Sql sql) - => FetchAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - - /// - public Task> FetchAsync(CommandType commandType, Sql sql) - => FetchAsync(CancellationToken.None, commandType, sql.SQL, sql.Arguments); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, Sql sql) - => FetchAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql) - => FetchAsync(cancellationToken, commandType, sql.SQL, sql.Arguments); - - /// - public Task> FetchAsync(long page, long itemsPerPage) - => FetchAsync(page, itemsPerPage, string.Empty); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage) - => FetchAsync(cancellationToken, page, itemsPerPage, string.Empty); - - /// - public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) - => FetchAsync(CancellationToken.None, page, itemsPerPage, sql, args); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sql, params object[] args) - => SkipTakeAsync(cancellationToken, (page - 1) * itemsPerPage, itemsPerPage, sql, args); - - /// - public Task> FetchAsync(long page, long itemsPerPage, Sql sql) - => FetchAsync(CancellationToken.None, page, itemsPerPage, sql); - - /// - public Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sql) - => SkipTakeAsync(cancellationToken, (page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); - -#endif - -#endregion - -#region operation: Page - - /// - /// Starting with a regular SELECT statement, derives the SQL statements required to query a - /// DB for a page of records and the total number of records - /// - /// The Type representing a row in the result set - /// The number of rows to skip before the start of the page - /// The number of rows in the page - /// The original SQL select statement - /// Arguments to any embedded parameters in the SQL - /// Outputs the SQL statement to query for the total number of matching rows - /// Outputs the SQL statement to retrieve a single page of matching rows - protected virtual void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) - { - if (EnableAutoSelect) - sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); - - SQLParts parts; - if (!Provider.PagingUtility.SplitSQL(sql, out parts)) - throw new Exception("Unable to parse SQL statement for paged query"); - - sqlPage = _provider.BuildPageQuery(skip, take, parts, ref args); - sqlCount = parts.SqlCount; - } - - /// - public Page Page(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs) - { - // Save the one-time command time out and use it for both queries - var saveTimeout = OneTimeCommandTimeout; - - // Setup the paged result - var result = new Page - { - CurrentPage = page, - ItemsPerPage = itemsPerPage, - TotalItems = ExecuteScalar(sqlCount, countArgs) - }; - result.TotalPages = result.TotalItems / itemsPerPage; - - if (result.TotalItems % itemsPerPage != 0) - result.TotalPages++; - - OneTimeCommandTimeout = saveTimeout; - - result.Items = Fetch(sqlPage, pageArgs); - - return result; - } - - /// - public Page Page(long page, long itemsPerPage) - => Page(page, itemsPerPage, string.Empty); - - /// - public Page Page(long page, long itemsPerPage, string sql, params object[] args) - { - BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out var sqlCount, out var sqlPage); - return Page(page, itemsPerPage, sqlCount, args, sqlPage, args); - } - - /// - public Page Page(long page, long itemsPerPage, Sql sql) - => Page(page, itemsPerPage, sql.SQL, sql.Arguments); - - /// - public Page Page(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) - => Page(page, itemsPerPage, sqlCount.SQL, sqlCount.Arguments, sqlPage.SQL, sqlPage.Arguments); - -#if ASYNC - - /// - public async Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sqlCount, object[] countArgs, - string sqlPage, object[] pageArgs) - { - var saveTimeout = OneTimeCommandTimeout; - - var result = new Page - { - CurrentPage = page, - ItemsPerPage = itemsPerPage, - TotalItems = await ExecuteScalarAsync(cancellationToken, sqlCount, countArgs).ConfigureAwait(false) - }; - result.TotalPages = result.TotalItems / itemsPerPage; - - if (result.TotalItems % itemsPerPage != 0) - result.TotalPages++; - - OneTimeCommandTimeout = saveTimeout; - - result.Items = await FetchAsync(cancellationToken, sqlPage, pageArgs).ConfigureAwait(false); - - return result; - } - - /// - public Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs) - => PageAsync(CancellationToken.None, page, itemsPerPage, sqlCount, countArgs, sqlPage, pageArgs); - - /// - public Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage) - => PageAsync(cancellationToken, page, itemsPerPage, string.Empty); - - /// - public Task> PageAsync(long page, long itemsPerPage) - => PageAsync(CancellationToken.None, page, itemsPerPage, string.Empty); - - /// - public Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sql, params object[] args) - { - BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out var sqlCount, out var sqlPage); - return PageAsync(cancellationToken, page, itemsPerPage, sqlCount, args, sqlPage, args); - } - - /// - public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) - => PageAsync(CancellationToken.None, page, itemsPerPage, sql, args); - - /// - public Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sql) - => PageAsync(cancellationToken, page, itemsPerPage, sql.SQL, sql.Arguments); - - /// - public Task> PageAsync(long page, long itemsPerPage, Sql sql) - => PageAsync(CancellationToken.None, page, itemsPerPage, sql.SQL, sql.Arguments); - - /// - public Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) - => PageAsync(cancellationToken, page, itemsPerPage, sqlCount.SQL, sqlCount.Arguments, sqlPage.SQL, sqlPage.Arguments); - - /// - public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) - => PageAsync(CancellationToken.None, page, itemsPerPage, sqlCount.SQL, sqlCount.Arguments, sqlPage.SQL, sqlPage.Arguments); - -#endif - -#endregion - -#region operation: SkipTake - - /// - public List SkipTake(long skip, long take) - => SkipTake(skip, take, string.Empty); - - /// - public List SkipTake(long skip, long take, Sql sql) - => SkipTake(skip, take, sql.SQL, sql.Arguments); - - /// - public List SkipTake(long skip, long take, string sql, params object[] args) - { - BuildPageQueries(skip, take, sql, ref args, out var sqlCount, out var sqlPage); - return Fetch(sqlPage, args); - } - -#if ASYNC + /// + public Task> QueryAsync(Sql sql) + => QueryAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - /// - public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take) - => SkipTakeAsync(cancellationToken, skip, take, string.Empty); + /// + public Task> QueryAsync(string sql, params object[] args) + => QueryAsync(CancellationToken.None, CommandType.Text, sql, args); - /// - public Task> SkipTakeAsync(long skip, long take) - => SkipTakeAsync(CancellationToken.None, skip, take, string.Empty); + /// + public Task> QueryAsync(CancellationToken cancellationToken) + => QueryAsync(cancellationToken, CommandType.Text, string.Empty); - /// - public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, string sql, params object[] args) - { - BuildPageQueries(skip, take, sql, ref args, out var sqlCount, out var sqlPage); - return FetchAsync(cancellationToken, sqlPage, args); - } + /// + public Task> QueryAsync(CancellationToken cancellationToken, Sql sql) + => QueryAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - /// - public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) - => SkipTakeAsync(CancellationToken.None, skip, take, sql, args); + // TODO: QueryAsync(CancellationToken, string, object[]) takes a caller-provided CancellationToken, but uses a default empty token for wrapped call - /// - public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, Sql sql) - => SkipTakeAsync(cancellationToken, skip, take, sql.SQL, sql.Arguments); + /// + public Task> QueryAsync(CancellationToken cancellationToken, string sql, params object[] args) + => QueryAsync(CancellationToken.None, CommandType.Text, sql, args); - /// - public Task> SkipTakeAsync(long skip, long take, Sql sql) - => SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); + /// + public Task> QueryAsync(CommandType commandType) + => QueryAsync(CancellationToken.None, commandType, string.Empty); -#endif + /// + public Task> QueryAsync(CommandType commandType, Sql sql) + => QueryAsync(CancellationToken.None, commandType, sql.SQL, sql.Arguments); -#endregion + /// + public Task> QueryAsync(CommandType commandType, string sql, params object[] args) + => QueryAsync(CancellationToken.None, commandType, sql, args); -#region operation: Query + /// + public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType) + => QueryAsync(cancellationToken, commandType, string.Empty); - /// - public IEnumerable Query() - => Query(string.Empty); + /// + public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql) + => QueryAsync(cancellationToken, commandType, sql.SQL, sql.Arguments); - /// - public IEnumerable Query(string sql, params object[] args) + /// + public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) { if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); - return ExecuteReader(CommandType.Text, sql, args); + return ExecuteReaderAsync(cancellationToken, commandType, sql, args); } - /// - public IEnumerable Query(Sql sql) - => Query(sql.SQL, sql.Arguments); - -#if ASYNC - /// - public Task QueryAsync(Action receivePocoCallback) - => QueryAsync(receivePocoCallback, CancellationToken.None, CommandType.Text, string.Empty); - - /// - public Task QueryAsync(Action receivePocoCallback, CommandType commandType) - => QueryAsync(receivePocoCallback, CancellationToken.None, commandType, string.Empty); - - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken) - => QueryAsync(receivePocoCallback, cancellationToken, CommandType.Text, string.Empty); - - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType) - => QueryAsync(receivePocoCallback, cancellationToken, commandType, string.Empty); - - /// - public Task QueryAsync(Action receivePocoCallback, string sql, params object[] args) - => QueryAsync(receivePocoCallback, CancellationToken.None, CommandType.Text, sql, args); - - /// - public Task QueryAsync(Action receivePocoCallback, CommandType commandType, string sql, params object[] args) - => QueryAsync(receivePocoCallback, CancellationToken.None, commandType, sql, args); - - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, string sql, params object[] args) - => QueryAsync(receivePocoCallback, CancellationToken.None, CommandType.Text, sql, args); - - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) - { - if (EnableAutoSelect) - sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); - return ExecuteReaderAsync(receivePocoCallback, cancellationToken, commandType, sql, args); - } + /// + public Task QueryAsync(Action action) + => QueryAsync(action, CancellationToken.None, CommandType.Text, string.Empty); - /// - public Task QueryAsync(Action receivePocoCallback, Sql sql) - => QueryAsync(receivePocoCallback, CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); + /// + public Task QueryAsync(Action action, Sql sql) + => QueryAsync(action, CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - /// - public Task QueryAsync(Action receivePocoCallback, CommandType commandType, Sql sql) - => QueryAsync(receivePocoCallback, CancellationToken.None, commandType, sql.SQL, sql.Arguments); + /// + public Task QueryAsync(Action action, string sql, params object[] args) + => QueryAsync(action, CancellationToken.None, CommandType.Text, sql, args); - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, Sql sql) - => QueryAsync(receivePocoCallback, cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken) + => QueryAsync(action, cancellationToken, CommandType.Text, string.Empty); - /// - public Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType, Sql sql) - => QueryAsync(receivePocoCallback, cancellationToken, commandType, sql.SQL, sql.Arguments); + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken, Sql sql) + => QueryAsync(action, cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - /// - public Task> QueryAsync() - => QueryAsync(CancellationToken.None, CommandType.Text, string.Empty); + // TODO: QueryAsync(Action, CancellationToken, string, object[]) takes a caller-provided CancellationToken, but uses a default empty token for wrapped call - /// - public Task> QueryAsync(CommandType commandType) - => QueryAsync(CancellationToken.None, commandType, string.Empty); + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken, string sql, params object[] args) + => QueryAsync(action, CancellationToken.None, CommandType.Text, sql, args); - /// - public Task> QueryAsync(CancellationToken cancellationToken) - => QueryAsync(cancellationToken, CommandType.Text, string.Empty); + /// + public Task QueryAsync(Action action, CommandType commandType) + => QueryAsync(action, CancellationToken.None, commandType, string.Empty); - /// - public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType) - => QueryAsync(cancellationToken, commandType, string.Empty); + /// + public Task QueryAsync(Action action, CommandType commandType, Sql sql) + => QueryAsync(action, CancellationToken.None, commandType, sql.SQL, sql.Arguments); - /// - public Task> QueryAsync(string sql, params object[] args) - => QueryAsync(CancellationToken.None, CommandType.Text, sql, args); + /// + public Task QueryAsync(Action action, CommandType commandType, string sql, params object[] args) + => QueryAsync(action, CancellationToken.None, commandType, sql, args); - /// - public Task> QueryAsync(CommandType commandType, string sql, params object[] args) - => QueryAsync(CancellationToken.None, commandType, sql, args); + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType) + => QueryAsync(action, cancellationToken, commandType, string.Empty); - /// - public Task> QueryAsync(CancellationToken cancellationToken, string sql, params object[] args) - => QueryAsync(CancellationToken.None, CommandType.Text, sql, args); + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType, Sql sql) + => QueryAsync(action, cancellationToken, commandType, sql.SQL, sql.Arguments); - /// - public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) + /// + public Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) { if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); - - return ExecuteReaderAsync(cancellationToken, commandType, sql, args); + return ExecuteReaderAsync(action, cancellationToken, commandType, sql, args); } +#endif - /// - public Task> QueryAsync(Sql sql) - => QueryAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); - - /// - public Task> QueryAsync(CommandType commandType, Sql sql) - => QueryAsync(CancellationToken.None, commandType, sql.SQL, sql.Arguments); - - /// - public Task> QueryAsync(CancellationToken cancellationToken, Sql sql) - => QueryAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - - /// - public Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql) - => QueryAsync(cancellationToken, commandType, sql.SQL, sql.Arguments); - - protected virtual async Task ExecuteReaderAsync(Action processPoco, CancellationToken cancellationToken, CommandType commandType, string sql, - object[] args) + /// The type of command to execute. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// + protected virtual IEnumerable ExecuteReader(CommandType commandType, string sql, params object[] args) { - await OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false); + OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, commandType, sql, args)) { - IDataReader reader; + IDataReader r; var pd = PocoData.ForType(typeof(T), _defaultMapper); - try { - reader = await ExecuteReaderHelperAsync(cancellationToken, cmd).ConfigureAwait(false); + r = ExecuteReaderHelper(cmd); } - catch (Exception e) + catch (Exception x) { - if (OnException(e)) + if (OnException(x)) throw; - return; + yield break; } - var readerAsync = reader as DbDataReader; - var factory = - pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, reader, - _defaultMapper) as Func; - - using (reader) + var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r, + _defaultMapper) as Func; + using (r) { while (true) { T poco; try { - if (readerAsync != null) - { - if (!await readerAsync.ReadAsync(cancellationToken).ConfigureAwait(false)) - return; - } - else - { - if (!reader.Read()) - return; - } - - poco = factory(reader); - processPoco(poco); + if (!r.Read()) + yield break; + poco = factory(r); } - catch (Exception e) + catch (Exception x) { - if (OnException(e)) + if (OnException(x)) throw; - return; + yield break; } + + yield return poco; } } } @@ -1557,8 +1042,9 @@ protected virtual async Task ExecuteReaderAsync(Action processPoco, Cancel } } - protected virtual async Task> ExecuteReaderAsync(CancellationToken cancellationToken, CommandType commandType, string sql, - object[] args) +#if ASYNC + /// + protected virtual async Task> ExecuteReaderAsync(CancellationToken cancellationToken, CommandType commandType, string sql, object[] args) { await OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false); var cmd = CreateCommand(_sharedConnection, commandType, sql, args); @@ -1592,17 +1078,148 @@ protected virtual async Task> ExecuteReaderAsync(Cancellation return new AsyncReader(this, cmd, reader, factory); } + /// + protected virtual async Task ExecuteReaderAsync(Action action, CancellationToken cancellationToken, CommandType commandType, string sql, object[] args) + { + await OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false); + try + { + using (var cmd = CreateCommand(_sharedConnection, commandType, sql, args)) + { + IDataReader reader; + var pd = PocoData.ForType(typeof(T), _defaultMapper); + + try + { + reader = await ExecuteReaderHelperAsync(cancellationToken, cmd).ConfigureAwait(false); + } + catch (Exception e) + { + if (OnException(e)) + throw; + return; + } + + var readerAsync = reader as DbDataReader; + var factory = + pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, reader, + _defaultMapper) as Func; + + using (reader) + { + while (true) + { + T poco; + try + { + if (readerAsync != null) + { + if (!await readerAsync.ReadAsync(cancellationToken).ConfigureAwait(false)) + return; + } + else + { + if (!reader.Read()) + return; + } + + poco = factory(reader); + action(poco); + } + catch (Exception e) + { + if (OnException(e)) + throw; + return; + } + } + } + } + } + finally + { + CloseSharedConnection(); + } + } #endif - protected virtual IEnumerable ExecuteReader(CommandType commandType, string sql, params object[] args) + #endregion + + #region Query : Multi-POCO + + /// + public IEnumerable Query(Sql sql) + => Query(new[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2) }, null, sql, args); + + /// + public IEnumerable Query(string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); + + /// + public IEnumerable Query(string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); + + /// + public IEnumerable Query(string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql, args); + + /// + public IEnumerable Query(Func projector, Sql sql) + => Query(new[] { typeof(T1), typeof(T2) }, projector, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Func projector, Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, projector, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Func projector, Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, projector, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Func projector, Sql sql) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, projector, sql.SQL, sql.Arguments); + + /// + public IEnumerable Query(Func projector, string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2) }, projector, sql, args); + + /// + public IEnumerable Query(Func projector, string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, projector, sql, args); + + /// + public IEnumerable Query(Func projector, string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, projector, sql, args); + + /// + public IEnumerable Query(Func projector, string sql, params object[] args) + => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, projector, sql, args); + + /// + public IEnumerable Query(Type[] types, object projector, string sql, params object[] args) { OpenSharedConnection(); try { - using (var cmd = CreateCommand(_sharedConnection, commandType, sql, args)) + using (var cmd = CreateCommand(_sharedConnection, sql, args)) { IDataReader r; - var pd = PocoData.ForType(typeof(T), _defaultMapper); try { r = ExecuteReaderHelper(cmd); @@ -1614,18 +1231,20 @@ protected virtual IEnumerable ExecuteReader(CommandType commandType, strin yield break; } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r, - _defaultMapper) as Func; + var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r, _defaultMapper); + if (projector == null) + projector = MultiPocoFactory.GetAutoMapper(types.ToArray()); + var bNeedTerminator = false; using (r) { while (true) { - T poco; + TResult poco; try { if (!r.Read()) - yield break; - poco = factory(r); + break; + poco = factory(r, projector); } catch (Exception x) { @@ -1634,7 +1253,19 @@ protected virtual IEnumerable ExecuteReader(CommandType commandType, strin yield break; } - yield return poco; + if (poco != null) + yield return poco; + else + bNeedTerminator = true; + } + + if (bNeedTerminator) + { + var poco = (TResult)(projector as Delegate).DynamicInvoke(new object[types.Length]); + if (poco != null) + yield return poco; + else + yield break; } } } @@ -1645,428 +1276,903 @@ protected virtual IEnumerable ExecuteReader(CommandType commandType, strin } } -#endregion + #endregion + + #region QueryMultiple : Multi-POCO Result Set IGridReader -#region operation: Exists + /// + public IGridReader QueryMultiple(Sql sql) + => QueryMultiple(sql.SQL, sql.Arguments); - /// - public bool Exists(string sqlCondition, params object[] args) + /// + public IGridReader QueryMultiple(string sql, params object[] args) { - var poco = PocoData.ForType(typeof(T), _defaultMapper).TableInfo; + OpenSharedConnection(); - if (sqlCondition.TrimStart().StartsWith("where", StringComparison.OrdinalIgnoreCase)) - sqlCondition = sqlCondition.TrimStart().Substring(5); + GridReader result = null; - return ExecuteScalar(string.Format(_provider.GetExistsSql(), Provider.EscapeTableName(poco.TableName), sqlCondition), args) != 0; - } + var cmd = CreateCommand(_sharedConnection, sql, args); - /// - public bool Exists(object primaryKey) - { - var poco = PocoData.ForType(typeof(T), _defaultMapper); - return Exists($"{_provider.EscapeSqlIdentifier(poco.TableInfo.PrimaryKey)}=@0", - primaryKey is T ? poco.Columns[poco.TableInfo.PrimaryKey].GetValue(primaryKey) : primaryKey); + try + { + var reader = ExecuteReaderHelper(cmd); + result = new GridReader(this, cmd, reader, _defaultMapper); + } + catch (Exception x) + { + if (OnException(x)) + throw; + } + + return result; } -#if ASYNC - public Task ExistsAsync(object primaryKey) - => ExistsAsync(CancellationToken.None, primaryKey); + #endregion - public Task ExistsAsync(CancellationToken cancellationToken, object primaryKey) - { - var poco = PocoData.ForType(typeof(T), _defaultMapper); - return ExistsAsync(cancellationToken, $"{_provider.EscapeSqlIdentifier(poco.TableInfo.PrimaryKey)}=@0", - primaryKey is T ? poco.Columns[poco.TableInfo.PrimaryKey].GetValue(primaryKey) : primaryKey); - } + #region Fetch, FetchAsync : Single-POCO - public Task ExistsAsync(string sqlCondition, params object[] args) - => ExistsAsync(CancellationToken.None, sqlCondition, args); + /// + public List Fetch() + => Fetch(string.Empty); - public async Task ExistsAsync(CancellationToken cancellationToken, string sqlCondition, params object[] args) - { - var poco = PocoData.ForType(typeof(T), _defaultMapper).TableInfo; + /// + public List Fetch(Sql sql) + => Fetch(sql.SQL, sql.Arguments); - if (sqlCondition.TrimStart().StartsWith("where", StringComparison.OrdinalIgnoreCase)) - sqlCondition = sqlCondition.TrimStart().Substring(5); + /// + public List Fetch(string sql, params object[] args) + => Query(sql, args).ToList(); - return await ExecuteScalarAsync(cancellationToken, - string.Format(_provider.GetExistsSql(), Provider.EscapeTableName(poco.TableName), sqlCondition), args).ConfigureAwait(false) != 0; - } -#endif +#if ASYNC + /// + public Task> FetchAsync() + => FetchAsync(CancellationToken.None, CommandType.Text, string.Empty); -#endregion + /// + public Task> FetchAsync(Sql sql) + => FetchAsync(CancellationToken.None, CommandType.Text, sql.SQL, sql.Arguments); -#region operation: Linq style (Exists, Single, SingleOrDefault etc...) + /// + public Task> FetchAsync(string sql, params object[] args) + => FetchAsync(CancellationToken.None, CommandType.Text, sql, args); - /// - public T Single(object primaryKey) - => Single(GenerateSingleByKeySql(primaryKey)); + /// + public Task> FetchAsync(CancellationToken cancellationToken) + => FetchAsync(cancellationToken, CommandType.Text, string.Empty); - /// - public T SingleOrDefault(object primaryKey) - => SingleOrDefault(GenerateSingleByKeySql(primaryKey)); + /// + public Task> FetchAsync(CancellationToken cancellationToken, Sql sql) + => FetchAsync(cancellationToken, CommandType.Text, sql.SQL, sql.Arguments); - /// - public T Single(string sql, params object[] args) - => Query(sql, args).Single(); + // TODO: FetchAsync(CancellationToken, string, object[]) takes a caller-provided CancellationToken, but uses a default empty token for wrapped call - /// - public T SingleOrDefault(string sql, params object[] args) - => Query(sql, args).SingleOrDefault(); + /// + public Task> FetchAsync(CancellationToken cancellationToken, string sql, params object[] args) + => FetchAsync(CancellationToken.None, CommandType.Text, sql, args); - /// - public T First(string sql, params object[] args) - => Query(sql, args).First(); + /// + public Task> FetchAsync(CommandType commandType) + => FetchAsync(CancellationToken.None, CommandType.Text, string.Empty); - /// - public T FirstOrDefault(string sql, params object[] args) - => Query(sql, args).FirstOrDefault(); + /// + public Task> FetchAsync(CommandType commandType, Sql sql) + => FetchAsync(CancellationToken.None, commandType, sql.SQL, sql.Arguments); - /// - public T Single(Sql sql) - => Query(sql).Single(); + /// + public Task> FetchAsync(CommandType commandType, string sql, params object[] args) + => FetchAsync(CancellationToken.None, commandType, sql, args); - /// - public T SingleOrDefault(Sql sql) - => Query(sql).SingleOrDefault(); + /// + public Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType) + => FetchAsync(cancellationToken, commandType, string.Empty); - /// - public T First(Sql sql) - => Query(sql).First(); + /// + public Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql) + => FetchAsync(cancellationToken, commandType, sql.SQL, sql.Arguments); - /// - public T FirstOrDefault(Sql sql) - => Query(sql).FirstOrDefault(); + /// + public async Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args) + { + var pocos = new List(); + await QueryAsync(p => pocos.Add(p), cancellationToken, commandType, sql, args).ConfigureAwait(false); + return pocos; + } +#endif -#if ASYNC + #endregion - /// - public Task SingleAsync(object primaryKey) - => SingleAsync(CancellationToken.None, primaryKey); + #region Fetch : Multi-POCO - /// - public Task SingleAsync(CancellationToken cancellationToken, object primaryKey) - => SingleAsync(cancellationToken, GenerateSingleByKeySql(primaryKey)); + /// + public List Fetch(Sql sql) + => Query(sql.SQL, sql.Arguments).ToList(); - /// - public Task SingleAsync(string sql, params object[] args) - => SingleAsync(CancellationToken.None, sql, args); + /// + public List Fetch(Sql sql) + => Query(sql.SQL, sql.Arguments).ToList(); - /// - public async Task SingleAsync(CancellationToken cancellationToken, string sql, params object[] args) - => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).Single(); + /// + public List Fetch(Sql sql) + => Query(sql.SQL, sql.Arguments).ToList(); - /// - public Task SingleAsync(Sql sql) - => SingleAsync(CancellationToken.None, sql); + /// + public List Fetch(Sql sql) + => Query(sql.SQL, sql.Arguments).ToList(); - /// - public Task SingleAsync(CancellationToken cancellationToken, Sql sql) - => SingleAsync(cancellationToken, sql.SQL, sql.Arguments); + /// + public List Fetch(string sql, params object[] args) + => Query(sql, args).ToList(); - /// - public Task SingleOrDefaultAsync(Sql sql) - => SingleOrDefaultAsync(CancellationToken.None, sql); + /// + public List Fetch(string sql, params object[] args) + => Query(sql, args).ToList(); - /// - public Task SingleOrDefaultAsync(CancellationToken cancellationToken, Sql sql) - => SingleOrDefaultAsync(cancellationToken, sql.SQL, sql.Arguments); + /// + public List Fetch(string sql, params object[] args) + => Query(sql, args).ToList(); - /// - public Task SingleOrDefaultAsync(object primaryKey) - => SingleOrDefaultAsync(CancellationToken.None, primaryKey); + /// + public List Fetch(string sql, params object[] args) + => Query(sql, args).ToList(); - /// - public Task SingleOrDefaultAsync(CancellationToken cancellationToken, object primaryKey) - => SingleOrDefaultAsync(cancellationToken, GenerateSingleByKeySql(primaryKey)); + /// + public List Fetch(Func projector, Sql sql) + => Query(projector, sql.SQL, sql.Arguments).ToList(); - /// - public Task SingleOrDefaultAsync(string sql, params object[] args) - => SingleOrDefaultAsync(CancellationToken.None, sql, args); + /// + public List Fetch(Func projector, Sql sql) + => Query(projector, sql.SQL, sql.Arguments).ToList(); - /// - public async Task SingleOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args) - => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).SingleOrDefault(); + /// + public List Fetch(Func projector, Sql sql) + => Query(projector, sql.SQL, sql.Arguments).ToList(); - /// - public Task FirstAsync(string sql, params object[] args) - => FirstAsync(CancellationToken.None, sql, args); + /// + public List Fetch(Func projector, Sql sql) + => Query(projector, sql.SQL, sql.Arguments).ToList(); - /// - public async Task FirstAsync(CancellationToken cancellationToken, string sql, params object[] args) - => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).First(); + /// + public List Fetch(Func projector, string sql, params object[] args) + => Query(projector, sql, args).ToList(); - /// - public Task FirstAsync(Sql sql) - => FirstAsync(CancellationToken.None, sql); + /// + public List Fetch(Func projector, string sql, params object[] args) + => Query(projector, sql, args).ToList(); - /// - public Task FirstAsync(CancellationToken cancellationToken, Sql sql) - => FirstAsync(cancellationToken, sql.SQL, sql.Arguments); + /// + public List Fetch(Func projector, string sql, params object[] args) + => Query(projector, sql, args).ToList(); - /// - public Task FirstOrDefaultAsync(string sql, params object[] args) - => FirstOrDefaultAsync(CancellationToken.None, sql, args); + /// + public List Fetch(Func projector, string sql, params object[] args) + => Query(projector, sql, args).ToList(); - /// - public async Task FirstOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args) - => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).FirstOrDefault(); + #endregion - /// - public Task FirstOrDefaultAsync(Sql sql) - => FirstOrDefaultAsync(CancellationToken.None, sql); + #region Fetch, FetchAsync : Paged SkipTake - /// - public Task FirstOrDefaultAsync(CancellationToken cancellationToken, Sql sql) - => FirstOrDefaultAsync(cancellationToken, sql.SQL, sql.Arguments); + /// + public List Fetch(long page, long maxItemsPerPage) + => Fetch(page, maxItemsPerPage, string.Empty); -#endif + /// + public List Fetch(long page, long maxItemsPerPage, Sql sql) + => SkipTake((page - 1) * maxItemsPerPage, maxItemsPerPage, sql.SQL, sql.Arguments); - private Sql GenerateSingleByKeySql(object primaryKey) - { - string pkName = _provider.EscapeSqlIdentifier(PocoData.ForType(typeof(T), _defaultMapper).TableInfo.PrimaryKey); - var sql = $"WHERE {pkName} = @0"; + /// + public List Fetch(long page, long maxItemsPerPage, string sql, params object[] args) + => SkipTake((page - 1) * maxItemsPerPage, maxItemsPerPage, sql, args); - if (!EnableAutoSelect) - // We're going to be nice and add the SELECT anyway - sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); +#if ASYNC + /// + public Task> FetchAsync(long page, long maxItemsPerPage) + => FetchAsync(page, maxItemsPerPage, string.Empty); - return new Sql(sql, primaryKey); - } + /// + public Task> FetchAsync(long page, long maxItemsPerPage, Sql sql) + => FetchAsync(CancellationToken.None, page, maxItemsPerPage, sql); -#endregion + /// + public Task> FetchAsync(long page, long maxItemsPerPage, string sql, params object[] args) + => FetchAsync(CancellationToken.None, page, maxItemsPerPage, sql, args); -#region operation: Insert + /// + public Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage) + => FetchAsync(cancellationToken, page, maxItemsPerPage, string.Empty); - /// - public object Insert(string tableName, object poco) - { - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); + /// + public Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql sql) + => SkipTakeAsync(cancellationToken, (page - 1) * maxItemsPerPage, maxItemsPerPage, sql.SQL, sql.Arguments); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + /// + public Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string sql, params object[] args) + => SkipTakeAsync(cancellationToken, (page - 1) * maxItemsPerPage, maxItemsPerPage, sql, args); +#endif - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + #endregion - return ExecuteInsert(tableName, pd == null ? null : pd.TableInfo.PrimaryKey, pd != null && pd.TableInfo.AutoIncrement, poco); - } + #region Page, PageAsync - /// - public object Insert(string tableName, string primaryKeyName, object poco) + /// + public Page Page(long page, long maxItemsPerPage) + => Page(page, maxItemsPerPage, string.Empty); + + /// + public Page Page(long page, long maxItemsPerPage, Sql sql) + => Page(page, maxItemsPerPage, sql.SQL, sql.Arguments); + + /// + public Page Page(long page, long maxItemsPerPage, string sql, params object[] args) { - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); + BuildPageQueries((page - 1) * maxItemsPerPage, maxItemsPerPage, sql, ref args, out var countSql, out var pageSql); + return Page(page, maxItemsPerPage, countSql, args, pageSql, args); + } - if (string.IsNullOrEmpty(primaryKeyName)) - throw new ArgumentNullException(nameof(primaryKeyName)); + /// + public Page Page(long page, long maxItemsPerPage, Sql countSql, Sql pageSql) + => Page(page, maxItemsPerPage, countSql.SQL, countSql.Arguments, pageSql.SQL, pageSql.Arguments); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); +#if ASYNC + /// + public Task> PageAsync(long page, long maxItemsPerPage) + => PageAsync(CancellationToken.None, page, maxItemsPerPage, string.Empty); - var t = poco.GetType(); - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - var autoIncrement = pd == null || pd.TableInfo.AutoIncrement || t.Name.Contains("AnonymousType") && - !t.GetProperties().Any(p => p.Name.Equals(primaryKeyName, StringComparison.OrdinalIgnoreCase)); + /// + public Task> PageAsync(long page, long maxItemsPerPage, Sql sql) + => PageAsync(CancellationToken.None, page, maxItemsPerPage, sql.SQL, sql.Arguments); - return ExecuteInsert(tableName, primaryKeyName, autoIncrement, poco); - } + /// + public Task> PageAsync(long page, long maxItemsPerPage, string sql, params object[] args) + => PageAsync(CancellationToken.None, page, maxItemsPerPage, sql, args); - /// - public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) - { - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); + /// + public Task> PageAsync(long page, long maxItemsPerPage, Sql countSql, Sql pageSql) + => PageAsync(CancellationToken.None, page, maxItemsPerPage, countSql.SQL, countSql.Arguments, pageSql.SQL, pageSql.Arguments); - if (string.IsNullOrEmpty(primaryKeyName)) - throw new ArgumentNullException(nameof(primaryKeyName)); + /// + public Task> PageAsync(long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs) + => PageAsync(CancellationToken.None, page, maxItemsPerPage, countSql, countArgs, pageSql, pageArgs); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + /// + public Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage) + => PageAsync(cancellationToken, page, maxItemsPerPage, string.Empty); - return ExecuteInsert(tableName, primaryKeyName, autoIncrement, poco); - } + /// + public Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql sql) + => PageAsync(cancellationToken, page, maxItemsPerPage, sql.SQL, sql.Arguments); - /// - public object Insert(object poco) + /// + public Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string sql, params object[] args) { - if (poco == null) - throw new ArgumentNullException(nameof(poco)); - - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return ExecuteInsert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); + BuildPageQueries((page - 1) * maxItemsPerPage, maxItemsPerPage, sql, ref args, out var countSql, out var pageSql); + return PageAsync(cancellationToken, page, maxItemsPerPage, countSql, args, pageSql, args); } - private object ExecuteInsert(string tableName, string primaryKeyName, bool autoIncrement, object poco) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, string.Empty)) - { - var pd = PocoData.ForObject(poco, primaryKeyName, _defaultMapper); - var names = new List(); - var values = new List(); + /// + public Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql countSql, Sql pageSql) + => PageAsync(cancellationToken, page, maxItemsPerPage, countSql.SQL, countSql.Arguments, pageSql.SQL, pageSql.Arguments); +#endif - PrepareExecuteInsert(tableName, primaryKeyName, autoIncrement, poco, pd, names, values, cmd); + /// + public Page Page(long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs) + { + // Save the one-time command time out and use it for both queries + var saveTimeout = OneTimeCommandTimeout; - if (!autoIncrement) - { - ExecuteNonQueryHelper(cmd); + // Setup the paged result + var result = new Page + { + CurrentPage = page, + ItemsPerPage = maxItemsPerPage, + TotalItems = ExecuteScalar(countSql, countArgs) + }; - if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out var pkColumn)) - return pkColumn.GetValue(poco); - else - return null; - } + result.TotalPages = result.TotalItems / maxItemsPerPage; + if (result.TotalItems % maxItemsPerPage != 0) + result.TotalPages++; - var id = _provider.ExecuteInsert(this, cmd, primaryKeyName); + OneTimeCommandTimeout = saveTimeout; - // Assign the ID back to the primary key property - if (primaryKeyName != null && !poco.GetType().Name.Contains("AnonymousType")) - if (pd.Columns.TryGetValue(primaryKeyName, out var pc)) - pc.SetValue(poco, pc.ChangeType(id)); + result.Items = Fetch(pageSql, pageArgs); - return id; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return null; - } + return result; } - private void PrepareExecuteInsert(string tableName, string primaryKeyName, bool autoIncrement, object poco, PocoData pd, List names, - List values, IDbCommand cmd) +#if ASYNC + /// + public async Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs) { - var index = 0; - foreach (var i in pd.Columns) - { - // Don't insert result columns - if (i.Value.ResultColumn) - continue; + var saveTimeout = OneTimeCommandTimeout; - // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, StringComparison.OrdinalIgnoreCase) == 0) - { - // Setup auto increment expression - var autoIncExpression = _provider.GetAutoIncrementExpression(pd.TableInfo); - if (autoIncExpression != null) - { - names.Add(_provider.EscapeSqlIdentifier(i.Key)); - values.Add(autoIncExpression); - } + var result = new Page + { + CurrentPage = page, + ItemsPerPage = maxItemsPerPage, + TotalItems = await ExecuteScalarAsync(cancellationToken, countSql, countArgs).ConfigureAwait(false) + }; - continue; - } + result.TotalPages = result.TotalItems / maxItemsPerPage; + if (result.TotalItems % maxItemsPerPage != 0) + result.TotalPages++; - names.Add(_provider.EscapeSqlIdentifier(i.Key)); - values.Add(string.Format(i.Value.InsertTemplate ?? "{0}{1}", _paramPrefix, index++)); - AddParam(cmd, i.Value.GetValue(poco), i.Value); - } + OneTimeCommandTimeout = saveTimeout; - var outputClause = string.Empty; - if (autoIncrement) - outputClause = _provider.GetInsertOutputClause(primaryKeyName); + result.Items = await FetchAsync(cancellationToken, pageSql, pageArgs).ConfigureAwait(false); - cmd.CommandText = - $"INSERT INTO {_provider.EscapeTableName(tableName)} ({string.Join(",", names.ToArray())}){outputClause} VALUES ({string.Join(",", values.ToArray())})"; + return result; } +#endif -#if ASYNC + #endregion - public Task InsertAsync(string tableName, object poco) - => InsertAsync(CancellationToken.None, tableName, poco); + #region SkipTake, SkipTakeAsync - public Task InsertAsync(CancellationToken cancellationToken, string tableName, object poco) - { - if (tableName == null) - throw new ArgumentNullException(nameof(tableName)); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + /// + public List SkipTake(long skip, long take) + => SkipTake(skip, take, string.Empty); - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return ExecuteInsertAsync(cancellationToken, tableName, pd?.TableInfo.PrimaryKey, pd != null && pd.TableInfo.AutoIncrement, poco); + /// + public List SkipTake(long skip, long take, Sql sql) + => SkipTake(skip, take, sql.SQL, sql.Arguments); + + /// + public List SkipTake(long skip, long take, string sql, params object[] args) + { + BuildPageQueries(skip, take, sql, ref args, out var countSql, out var pageSql); + return Fetch(pageSql, args); } - public Task InsertAsync(string tableName, string primaryKeyName, object poco) - => InsertAsync(CancellationToken.None, tableName, primaryKeyName, poco); +#if ASYNC + /// + public Task> SkipTakeAsync(long skip, long take) + => SkipTakeAsync(CancellationToken.None, skip, take, string.Empty); - public Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) - { - if (tableName == null) - throw new ArgumentNullException(nameof(tableName)); - if (primaryKeyName == null) - throw new ArgumentNullException(nameof(primaryKeyName)); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + // TODO: SkipTakeAsync(long, long, Sql) should forward call to an overload that receives a CancellationToken - var t = poco.GetType(); - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - var autoIncrement = pd == null || pd.TableInfo.AutoIncrement || t.Name.Contains("AnonymousType") && - !t.GetProperties().Any(p => p.Name.Equals(primaryKeyName, StringComparison.OrdinalIgnoreCase)); + /// + public Task> SkipTakeAsync(long skip, long take, Sql sql) + => SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); - return ExecuteInsertAsync(cancellationToken, tableName, primaryKeyName, autoIncrement, poco); - } + /// + public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) + => SkipTakeAsync(CancellationToken.None, skip, take, sql, args); - public Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco) - => InsertAsync(CancellationToken.None, tableName, primaryKeyName, autoIncrement, poco); + /// + public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take) + => SkipTakeAsync(cancellationToken, skip, take, string.Empty); - public Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, bool autoIncrement, object poco) + /// + public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, Sql sql) + => SkipTakeAsync(cancellationToken, skip, take, sql.SQL, sql.Arguments); + + /// + public Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, string sql, params object[] args) { - if (tableName == null) - throw new ArgumentNullException(nameof(tableName)); - if (primaryKeyName == null) - throw new ArgumentNullException(nameof(primaryKeyName)); - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + BuildPageQueries(skip, take, sql, ref args, out var countSql, out var pageSql); + return FetchAsync(cancellationToken, pageSql, args); + } +#endif - return ExecuteInsertAsync(cancellationToken, tableName, primaryKeyName, autoIncrement, poco); + /// + /// Starting with a regular SELECT statement, derives the SQL statements required to query a DB for a page of records and the + /// total number of records. + /// + /// The POCO type representing a single result record. + /// The number of records to skip before the start of the page. + /// The number of records in the page. + /// The SQL statement. + /// The parameters to embed in the SQL statement. + /// When this method returns, contains the SQL statement to query for the total number of records. + /// When this method returns, contains the SQL statement to retrieve a single page of records. + /// Unable to parse the given statement. + protected virtual void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string countSql, out string pageSql) + { + if (EnableAutoSelect) + sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); + + SQLParts parts; + if (!Provider.PagingUtility.SplitSQL(sql, out parts)) + throw new Exception("Unable to parse SQL statement for paged query"); + + pageSql = _provider.BuildPageQuery(skip, take, parts, ref args); + countSql = parts.SqlCount; } - public Task InsertAsync(object poco) - => InsertAsync(CancellationToken.None, poco); + #endregion - public Task InsertAsync(CancellationToken cancellationToken, object poco) - { - if (poco == null) - throw new ArgumentNullException(nameof(poco)); + #region Exists, ExistsAsync - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return ExecuteInsertAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); + /// + /// + /// + /// (7); // with a primary key value + /// db.Exists(person); // with a poco object (PetaPoco will extract the primary key value) + /// ]]> + /// + /// + public bool Exists(object pocoOrPrimaryKeyValue) + { + var poco = PocoData.ForType(typeof(T), _defaultMapper); + return Exists($"{_provider.EscapeSqlIdentifier(poco.TableInfo.PrimaryKey)}=@0", + pocoOrPrimaryKeyValue is T ? poco.Columns[poco.TableInfo.PrimaryKey].GetValue(pocoOrPrimaryKeyValue) : pocoOrPrimaryKeyValue); } - private async Task ExecuteInsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, bool autoIncrement, - object poco) + /// + /// + /// + /// ("[full_name] = @0", peta); + /// db.Exists("WHERE [full_name] = @0", peta); // with long-form syntax ("WHERE" unnecessary, but ok) + /// ]]> + /// + /// + public bool Exists(string sql, params object[] args) { - try - { - await OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false); - try - { - using (var cmd = CreateCommand(_sharedConnection, string.Empty)) - { - var pd = PocoData.ForObject(poco, primaryKeyName, _defaultMapper); - var names = new List(); - var values = new List(); + var poco = PocoData.ForType(typeof(T), _defaultMapper).TableInfo; - PrepareExecuteInsert(tableName, primaryKeyName, autoIncrement, poco, pd, names, values, cmd); + if (sql.TrimStart().StartsWith("WHERE", StringComparison.OrdinalIgnoreCase)) + sql = sql.TrimStart().Substring(5); - if (!autoIncrement) - { - await ExecuteNonQueryHelperAsync(cancellationToken, cmd).ConfigureAwait(false); + return ExecuteScalar(string.Format(_provider.GetExistsSql(), Provider.EscapeTableName(poco.TableName), sql), args) != 0; + } - if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out var pkColumn)) - return pkColumn.GetValue(poco); +#if ASYNC + /// + public Task ExistsAsync(object pocoOrPrimaryKeyValue) + => ExistsAsync(CancellationToken.None, pocoOrPrimaryKeyValue); + + /// + public Task ExistsAsync(string sql, params object[] args) + => ExistsAsync(CancellationToken.None, sql, args); + + /// + public Task ExistsAsync(CancellationToken cancellationToken, object pocoOrPrimaryKeyValue) + { + var poco = PocoData.ForType(typeof(T), _defaultMapper); + return ExistsAsync(cancellationToken, $"{_provider.EscapeSqlIdentifier(poco.TableInfo.PrimaryKey)}=@0", + pocoOrPrimaryKeyValue is T ? poco.Columns[poco.TableInfo.PrimaryKey].GetValue(pocoOrPrimaryKeyValue) : pocoOrPrimaryKeyValue); + } + + /// + public async Task ExistsAsync(CancellationToken cancellationToken, string sql, params object[] args) + { + var poco = PocoData.ForType(typeof(T), _defaultMapper).TableInfo; + + if (sql.TrimStart().StartsWith("WHERE", StringComparison.OrdinalIgnoreCase)) + sql = sql.TrimStart().Substring(5); + + return await ExecuteScalarAsync(cancellationToken, string.Format(_provider.GetExistsSql(), + Provider.EscapeTableName(poco.TableName), sql), args).ConfigureAwait(false) != 0; + } +#endif + + #endregion + + // TODO: Some, but not all operation methods use `String.IsNullOrEmpty/IsNullOrWhiteSpace` for string param checks. Most only do null check. Shouldn't all string params check for empty value, in addition to null check? + + #region Single, SingleAsync + + /// + public T Single(object primaryKey) + => Single(GenerateSingleByKeySql(primaryKey)); + + /// + public T Single(Sql sql) + => Query(sql).Single(); + + /// + public T Single(string sql, params object[] args) + => Query(sql, args).Single(); + +#if ASYNC + /// + public Task SingleAsync(object primaryKey) + => SingleAsync(CancellationToken.None, primaryKey); + + /// + public Task SingleAsync(Sql sql) + => SingleAsync(CancellationToken.None, sql); + + /// + public Task SingleAsync(string sql, params object[] args) + => SingleAsync(CancellationToken.None, sql, args); + + /// + public Task SingleAsync(CancellationToken cancellationToken, object primaryKey) + => SingleAsync(cancellationToken, GenerateSingleByKeySql(primaryKey)); + + /// + public Task SingleAsync(CancellationToken cancellationToken, Sql sql) + => SingleAsync(cancellationToken, sql.SQL, sql.Arguments); + + /// + public async Task SingleAsync(CancellationToken cancellationToken, string sql, params object[] args) + => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).Single(); +#endif + + #endregion + + #region SingleOrDefault, SingleOrDefaultAsync + + /// + public T SingleOrDefault(object primaryKey) + => SingleOrDefault(GenerateSingleByKeySql(primaryKey)); + + /// + public T SingleOrDefault(Sql sql) + => Query(sql).SingleOrDefault(); + + /// + public T SingleOrDefault(string sql, params object[] args) + => Query(sql, args).SingleOrDefault(); + +#if ASYNC + /// + public Task SingleOrDefaultAsync(object primaryKey) + => SingleOrDefaultAsync(CancellationToken.None, primaryKey); + + /// + public Task SingleOrDefaultAsync(Sql sql) + => SingleOrDefaultAsync(CancellationToken.None, sql); + + /// + public Task SingleOrDefaultAsync(string sql, params object[] args) + => SingleOrDefaultAsync(CancellationToken.None, sql, args); + + /// + public Task SingleOrDefaultAsync(CancellationToken cancellationToken, object primaryKey) + => SingleOrDefaultAsync(cancellationToken, GenerateSingleByKeySql(primaryKey)); + + /// + public Task SingleOrDefaultAsync(CancellationToken cancellationToken, Sql sql) + => SingleOrDefaultAsync(cancellationToken, sql.SQL, sql.Arguments); + + /// + public async Task SingleOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args) + => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).SingleOrDefault(); +#endif + + private Sql GenerateSingleByKeySql(object primaryKey) + { + string pkName = _provider.EscapeSqlIdentifier(PocoData.ForType(typeof(T), _defaultMapper).TableInfo.PrimaryKey); + var sql = $"WHERE {pkName} = @0"; + + if (!EnableAutoSelect) + // We're going to be nice and add the SELECT anyway + sql = AutoSelectHelper.AddSelectClause(_provider, sql, _defaultMapper); + + return new Sql(sql, primaryKey); + } + + #endregion + + #region First, FirstAsync + + /// + public T First(Sql sql) + => Query(sql).First(); + + /// + public T First(string sql, params object[] args) + => Query(sql, args).First(); + +#if ASYNC + /// + public Task FirstAsync(Sql sql) + => FirstAsync(CancellationToken.None, sql); + + /// + public Task FirstAsync(string sql, params object[] args) + => FirstAsync(CancellationToken.None, sql, args); + + /// + public Task FirstAsync(CancellationToken cancellationToken, Sql sql) + => FirstAsync(cancellationToken, sql.SQL, sql.Arguments); + + /// + public async Task FirstAsync(CancellationToken cancellationToken, string sql, params object[] args) + => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).First(); +#endif + + #endregion + + #region FirstOrDefault, FirstOrDefaultAsync + + /// + public T FirstOrDefault(Sql sql) + => Query(sql).FirstOrDefault(); + + /// + public T FirstOrDefault(string sql, params object[] args) + => Query(sql, args).FirstOrDefault(); + +#if ASYNC + /// + public Task FirstOrDefaultAsync(Sql sql) + => FirstOrDefaultAsync(CancellationToken.None, sql); + + /// + public Task FirstOrDefaultAsync(string sql, params object[] args) + => FirstOrDefaultAsync(CancellationToken.None, sql, args); + + /// + public Task FirstOrDefaultAsync(CancellationToken cancellationToken, Sql sql) + => FirstOrDefaultAsync(cancellationToken, sql.SQL, sql.Arguments); + + /// + public async Task FirstOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args) + => (await FetchAsync(cancellationToken, sql, args).ConfigureAwait(false)).FirstOrDefault(); +#endif + + #endregion + + #region Insert, InsertAsync + + /// + /// is null. + public object Insert(object poco) + { + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return ExecuteInsert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); + } + + /// + /// or is null or empty. + public object Insert(string tableName, object poco) + { + // TODO: Inconsistent use of `ArgumentNullException` vs `ArgumentException` for null/empty string params (find all) + if (string.IsNullOrEmpty(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + + return ExecuteInsert(tableName, pd == null ? null : pd.TableInfo.PrimaryKey, pd != null && pd.TableInfo.AutoIncrement, poco); + } + + /// + /// , , or is null or empty. + public object Insert(string tableName, string primaryKeyName, object poco) + { + if (string.IsNullOrEmpty(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + if (string.IsNullOrEmpty(primaryKeyName)) + throw new ArgumentNullException(nameof(primaryKeyName)); + + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var t = poco.GetType(); + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + var autoIncrement = pd == null || pd.TableInfo.AutoIncrement || t.Name.Contains("AnonymousType") && + !t.GetProperties().Any(p => p.Name.Equals(primaryKeyName, StringComparison.OrdinalIgnoreCase)); + + return ExecuteInsert(tableName, primaryKeyName, autoIncrement, poco); + } + + /// + /// , , or is null or empty. + public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) + { + if (string.IsNullOrEmpty(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + if (string.IsNullOrEmpty(primaryKeyName)) + throw new ArgumentNullException(nameof(primaryKeyName)); + + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + return ExecuteInsert(tableName, primaryKeyName, autoIncrement, poco); + } + +#if ASYNC + /// + /// is null. + public Task InsertAsync(object poco) + => InsertAsync(CancellationToken.None, poco); + + /// + /// or is null or empty. + public Task InsertAsync(string tableName, object poco) + => InsertAsync(CancellationToken.None, tableName, poco); + + /// + /// , , or is null or empty. + public Task InsertAsync(string tableName, string primaryKeyName, object poco) + => InsertAsync(CancellationToken.None, tableName, primaryKeyName, poco); + + /// + /// , , or is null or empty. + public Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco) + => InsertAsync(CancellationToken.None, tableName, primaryKeyName, autoIncrement, poco); + + /// + /// is null. + public Task InsertAsync(CancellationToken cancellationToken, object poco) + { + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return ExecuteInsertAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); + } + + /// + /// or is null or empty. + public Task InsertAsync(CancellationToken cancellationToken, string tableName, object poco) + { + // TODO: use `string.IsNullOrEmpty(tableName)` (not `tableName == null`) + if (tableName == null) + throw new ArgumentNullException(nameof(tableName)); + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return ExecuteInsertAsync(cancellationToken, tableName, pd?.TableInfo.PrimaryKey, pd != null && pd.TableInfo.AutoIncrement, poco); + } + + /// + /// , , or is null or empty. + public Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) + { + // TODO: use `string.IsNullOrEmpty(tableName)` (not `tableName == null`) + if (tableName == null) + throw new ArgumentNullException(nameof(tableName)); + // TODO: use `string.IsNullOrEmpty(tableName)` (not `tableName == null`) + if (primaryKeyName == null) + throw new ArgumentNullException(nameof(primaryKeyName)); + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + var t = poco.GetType(); + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + var autoIncrement = pd == null || pd.TableInfo.AutoIncrement || t.Name.Contains("AnonymousType") && + !t.GetProperties().Any(p => p.Name.Equals(primaryKeyName, StringComparison.OrdinalIgnoreCase)); + + return ExecuteInsertAsync(cancellationToken, tableName, primaryKeyName, autoIncrement, poco); + } + + /// + /// , , or is null or empty. + public Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, bool autoIncrement, object poco) + { + // TODO: use `string.IsNullOrEmpty(tableName)` (not `tableName == null`) + if (tableName == null) + throw new ArgumentNullException(nameof(tableName)); + // TODO: use `string.IsNullOrEmpty(tableName)` (not `tableName == null`) + if (primaryKeyName == null) + throw new ArgumentNullException(nameof(primaryKeyName)); + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + return ExecuteInsertAsync(cancellationToken, tableName, primaryKeyName, autoIncrement, poco); + } +#endif + + private void PrepareExecuteInsert(string tableName, string primaryKeyName, bool autoIncrement, object poco, PocoData pd, List names, List values, IDbCommand cmd) + { + var index = 0; + foreach (var i in pd.Columns) + { + // Don't insert result columns + if (i.Value.ResultColumn) + continue; + + // Don't insert the primary key (except under oracle where we need bring in the next sequence value) + if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, StringComparison.OrdinalIgnoreCase) == 0) + { + // Setup auto increment expression + var autoIncExpression = _provider.GetAutoIncrementExpression(pd.TableInfo); + if (autoIncExpression != null) + { + names.Add(_provider.EscapeSqlIdentifier(i.Key)); + values.Add(autoIncExpression); + } + + continue; + } + + names.Add(_provider.EscapeSqlIdentifier(i.Key)); + values.Add(string.Format(i.Value.InsertTemplate ?? "{0}{1}", _paramPrefix, index++)); + AddParameter(cmd, i.Value.GetValue(poco), i.Value); + } + + var outputClause = string.Empty; + if (autoIncrement) + outputClause = _provider.GetInsertOutputClause(primaryKeyName); + + cmd.CommandText = + $"INSERT INTO {_provider.EscapeTableName(tableName)} ({string.Join(",", names.ToArray())}){outputClause} VALUES ({string.Join(",", values.ToArray())})"; + } + + /// + private object ExecuteInsert(string tableName, string primaryKeyName, bool autoIncrement, object poco) + { + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, string.Empty)) + { + var pd = PocoData.ForObject(poco, primaryKeyName, _defaultMapper); + var names = new List(); + var values = new List(); + + PrepareExecuteInsert(tableName, primaryKeyName, autoIncrement, poco, pd, names, values, cmd); + + if (!autoIncrement) + { + ExecuteNonQueryHelper(cmd); + + if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out var pkColumn)) + return pkColumn.GetValue(poco); + else + return null; + } + + var id = _provider.ExecuteInsert(this, cmd, primaryKeyName); + + // Assign the ID back to the primary key property + if (primaryKeyName != null && !poco.GetType().Name.Contains("AnonymousType")) + if (pd.Columns.TryGetValue(primaryKeyName, out var pc)) + pc.SetValue(poco, pc.ChangeType(id)); + + return id; + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + if (OnException(x)) + throw; + return null; + } + } + +#if ASYNC + /// + private async Task ExecuteInsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, bool autoIncrement, object poco) + { + try + { + await OpenSharedConnectionAsync(cancellationToken).ConfigureAwait(false); + try + { + using (var cmd = CreateCommand(_sharedConnection, string.Empty)) + { + var pd = PocoData.ForObject(poco, primaryKeyName, _defaultMapper); + var names = new List(); + var values = new List(); + + PrepareExecuteInsert(tableName, primaryKeyName, autoIncrement, poco, pd, names, values, cmd); + + if (!autoIncrement) + { + await ExecuteNonQueryHelperAsync(cancellationToken, cmd).ConfigureAwait(false); + + if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out var pkColumn)) + return pkColumn.GetValue(poco); else return null; } @@ -2093,18 +2199,62 @@ private async Task ExecuteInsertAsync(CancellationToken cancellationToke return null; } } - #endif #endregion - #region operation: Update + #region Update, UpdateAsync + + /// + /// is null. + public int Update(object poco) + => Update(poco, null, null); + + /// + /// is null. + public int Update(object poco, IEnumerable columns) + => Update(poco, null, columns); + + /// + /// is null. + public int Update(object poco, object primaryKeyValue) + => Update(poco, primaryKeyValue, null); + + /// + /// is null. + public int Update(object poco, object primaryKeyValue, IEnumerable columns) + { + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + if (columns?.Any() == false) + return 0; + + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return ExecuteUpdate(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); + } + + /// + /// , , or is null or empty. + public int Update(string tableName, string primaryKeyName, object poco) + => Update(tableName, primaryKeyName, poco, null, null); + + /// + /// , , or is null or empty. + public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) + => Update(tableName, primaryKeyName, poco, null, columns); - /// + /// + /// , , or is null or empty. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) => Update(tableName, primaryKeyName, poco, primaryKeyValue, null); - /// + /// + /// , , or is null or empty. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { if (string.IsNullOrEmpty(tableName)) @@ -2122,84 +2272,172 @@ public int Update(string tableName, string primaryKeyName, object poco, object p return ExecuteUpdate(tableName, primaryKeyName, poco, primaryKeyValue, columns); } - /// - public int Update(string tableName, string primaryKeyName, object poco) - => Update(tableName, primaryKeyName, poco, null, null); + /// + /// is null. + public int Update(Sql sql) + { + if (sql == null) + throw new ArgumentNullException(nameof(sql)); - /// - public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) - => Update(tableName, primaryKeyName, poco, null, columns); + var pd = PocoData.ForType(typeof(T), _defaultMapper); + return Execute(new Sql($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); + } - /// - public int Update(object poco, IEnumerable columns) - => Update(poco, null, columns); + /// + /// is null or empty. + public int Update(string sql, params object[] args) + { + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException(nameof(sql)); - /// - public int Update(object poco) - => Update(poco, null, null); + var pd = PocoData.ForType(typeof(T), _defaultMapper); + return Execute($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); + } - /// - public int Update(object poco, object primaryKeyValue) - => Update(poco, primaryKeyValue, null); +#if ASYNC + /// + /// is null. + public Task UpdateAsync(object poco) + => UpdateAsync(CancellationToken.None, poco); - /// - public int Update(object poco, object primaryKeyValue, IEnumerable columns) + /// + /// is null. + public Task UpdateAsync(object poco, IEnumerable columns) + => UpdateAsync(CancellationToken.None, poco, columns); + + /// + /// is null. + public Task UpdateAsync(object poco, object primaryKeyValue) + => UpdateAsync(CancellationToken.None, poco, primaryKeyValue); + + /// + /// is null. + public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns) + => UpdateAsync(CancellationToken.None, poco, primaryKeyValue, columns); + + /// + /// , , or is null or empty. + public Task UpdateAsync(string tableName, string primaryKeyName, object poco) + => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco); + + /// + /// , , or is null or empty. + public Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns) + => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, columns); + + /// + /// , , or is null or empty. + public Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) + => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, primaryKeyValue); + + /// + /// , , or is null or empty. + public Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, primaryKeyValue, columns); + + /// + /// is null. + public Task UpdateAsync(Sql sql) + => UpdateAsync(CancellationToken.None, sql); + + /// + /// is null or empty. + public Task UpdateAsync(string sql, params object[] args) + => UpdateAsync(CancellationToken.None, sql, args); + + /// + /// is null. + public Task UpdateAsync(CancellationToken cancellationToken, object poco) + => UpdateAsync(cancellationToken, poco, null, null); + + /// + /// is null. + public Task UpdateAsync(CancellationToken cancellationToken, object poco, IEnumerable columns) + => UpdateAsync(cancellationToken, poco, null, columns); + + /// + /// is null. + public Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue) + => UpdateAsync(cancellationToken, poco, primaryKeyValue, null); + + /// + /// is null. + public Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue, IEnumerable columns) { if (poco == null) throw new ArgumentNullException(nameof(poco)); if (columns?.Any() == false) - return 0; + return Task.FromResult(0); var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return ExecuteUpdate(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); + return ExecuteUpdateAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); } - /// - public int Update(string sql, params object[] args) + /// + /// , , or is null or empty. + public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) + => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, null); + + /// + /// , , or is null or empty. + public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, IEnumerable columns) + => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, null, columns); + + /// + /// , , or is null or empty. + public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue) + => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, primaryKeyValue, null); + + /// + /// , , or is null or empty. + public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { - if (string.IsNullOrEmpty(sql)) - throw new ArgumentNullException(nameof(sql)); + if (string.IsNullOrEmpty(tableName)) + throw new ArgumentNullException(nameof(tableName)); - var pd = PocoData.ForType(typeof(T), _defaultMapper); - return Execute($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); + if (string.IsNullOrEmpty(primaryKeyName)) + throw new ArgumentNullException(nameof(primaryKeyName)); + + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + if (columns?.Any() == false) + return Task.FromResult(0); + + return ExecuteUpdateAsync(cancellationToken, tableName, primaryKeyName, poco, primaryKeyValue, columns); } - /// - public int Update(Sql sql) + /// + /// is null. + public Task UpdateAsync(CancellationToken cancellationToken, Sql sql) { if (sql == null) throw new ArgumentNullException(nameof(sql)); var pd = PocoData.ForType(typeof(T), _defaultMapper); - return Execute(new Sql($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); + return ExecuteAsync(cancellationToken, new Sql($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); } - private int ExecuteUpdate(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + /// + /// is null or empty. + public Task UpdateAsync(CancellationToken cancellationToken, string sql, params object[] args) { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, string.Empty)) - { - PreExecuteUpdate(tableName, primaryKeyName, poco, primaryKeyValue, columns, cmd); - return ExecuteNonQueryHelper(cmd); - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - if (OnException(x)) - throw; - return -1; - } + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException(nameof(sql)); + + var pd = PocoData.ForType(typeof(T), _defaultMapper); + return ExecuteAsync(cancellationToken, $"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); } +#endif private void PreExecuteUpdate(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, IDbCommand cmd) { @@ -2228,7 +2466,7 @@ private void PreExecuteUpdate(string tableName, string primaryKeyName, object po sb.AppendFormat(i.Value.UpdateTemplate ?? "{0} = {1}{2}", _provider.EscapeSqlIdentifier(i.Key), _paramPrefix, index++); // Store the parameter in the command - AddParam(cmd, i.Value.GetValue(poco), i.Value); + AddParameter(cmd, i.Value.GetValue(poco), i.Value); } } else @@ -2243,7 +2481,7 @@ private void PreExecuteUpdate(string tableName, string primaryKeyName, object po sb.AppendFormat(pc.UpdateTemplate ?? "{0} = {1}{2}", _provider.EscapeSqlIdentifier(colname), _paramPrefix, index++); // Store the parameter in the command - AddParam(cmd, pc.GetValue(poco), pc); + AddParameter(cmd, pc.GetValue(poco), pc); } // Grab primary key value @@ -2263,133 +2501,43 @@ private void PreExecuteUpdate(string tableName, string primaryKeyName, object po var pkpi = new { Id = primaryKeyValue }.GetType().GetProperty("Id"); col = new PocoColumn() { PropertyInfo = pkpi }; } - } - - cmd.CommandText = - $"UPDATE {_provider.EscapeTableName(tableName)} SET {sb} WHERE {_provider.EscapeSqlIdentifier(primaryKeyName)} = {_paramPrefix}{index++}"; - AddParam(cmd, primaryKeyValue, col); - } - -#if ASYNC - - /// - public Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, primaryKeyValue); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue) - => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, primaryKeyValue, null); - - /// - public Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) - => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, primaryKeyValue, columns); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue, - IEnumerable columns) - { - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); - - if (string.IsNullOrEmpty(primaryKeyName)) - throw new ArgumentNullException(nameof(primaryKeyName)); - - if (poco == null) - throw new ArgumentNullException(nameof(poco)); - - if (columns?.Any() == false) - return Task.FromResult(0); - - return ExecuteUpdateAsync(cancellationToken, tableName, primaryKeyName, poco, primaryKeyValue, columns); - } - - /// - public Task UpdateAsync(string tableName, string primaryKeyName, object poco) - => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) - => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, null); - - /// - public Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns) - => UpdateAsync(CancellationToken.None, tableName, primaryKeyName, poco, columns); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, IEnumerable columns) - => UpdateAsync(cancellationToken, tableName, primaryKeyName, poco, null, columns); - - /// - public Task UpdateAsync(object poco, IEnumerable columns) - => UpdateAsync(CancellationToken.None, poco, columns); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, object poco, IEnumerable columns) - => UpdateAsync(cancellationToken, poco, null, columns); - - /// - public Task UpdateAsync(object poco) - => UpdateAsync(CancellationToken.None, poco); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, object poco) - => UpdateAsync(cancellationToken, poco, null, null); - - /// - public Task UpdateAsync(object poco, object primaryKeyValue) - => UpdateAsync(CancellationToken.None, poco, primaryKeyValue); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue) - => UpdateAsync(cancellationToken, poco, primaryKeyValue, null); - - /// - public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns) - => UpdateAsync(CancellationToken.None, poco, primaryKeyValue, columns); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue, IEnumerable columns) - { - if (poco == null) - throw new ArgumentNullException(nameof(poco)); - - if (columns?.Any() == false) - return Task.FromResult(0); - - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return ExecuteUpdateAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); - } - - /// - public Task UpdateAsync(string sql, params object[] args) - => UpdateAsync(CancellationToken.None, sql, args); - - /// - public Task UpdateAsync(CancellationToken cancellationToken, string sql, params object[] args) - { - if (string.IsNullOrEmpty(sql)) - throw new ArgumentNullException(nameof(sql)); - - var pd = PocoData.ForType(typeof(T), _defaultMapper); - return ExecuteAsync(cancellationToken, $"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); - } + } - /// - public Task UpdateAsync(Sql sql) - => UpdateAsync(CancellationToken.None, sql); + cmd.CommandText = + $"UPDATE {_provider.EscapeTableName(tableName)} SET {sb} WHERE {_provider.EscapeSqlIdentifier(primaryKeyName)} = {_paramPrefix}{index++}"; + AddParameter(cmd, primaryKeyValue, col); + } - /// - public Task UpdateAsync(CancellationToken cancellationToken, Sql sql) + /// + private int ExecuteUpdate(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { - if (sql == null) - throw new ArgumentNullException(nameof(sql)); - - var pd = PocoData.ForType(typeof(T), _defaultMapper); - return ExecuteAsync(cancellationToken, new Sql($"UPDATE {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, string.Empty)) + { + PreExecuteUpdate(tableName, primaryKeyName, poco, primaryKeyValue, columns, cmd); + return ExecuteNonQueryHelper(cmd); + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + if (OnException(x)) + throw; + return -1; + } } - private async Task ExecuteUpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, - object primaryKeyValue, IEnumerable columns) +#if ASYNC + /// + private async Task ExecuteUpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { try { @@ -2414,18 +2562,24 @@ private async Task ExecuteUpdateAsync(CancellationToken cancellationToken, return -1; } } - #endif -#endregion + #endregion + + #region Delete, DeleteAsync -#region operation: Delete + /// + public int Delete(object poco) + { + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } - /// + /// public int Delete(string tableName, string primaryKeyName, object poco) => Delete(tableName, primaryKeyName, poco, null); - /// + /// public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { if (primaryKeyValue == null) @@ -2439,63 +2593,80 @@ public int Delete(string tableName, string primaryKeyName, object poco, object p return Execute(sql, primaryKeyValue); } - /// - public int Delete(object poco) - { - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - /// - public int Delete(object pocoOrPrimaryKey) + /// + /// Anonymous type does not contain an id for primary key column. + public int Delete(object pocoOrPrimaryKeyValue) { - if (pocoOrPrimaryKey.GetType() == typeof(T)) - return Delete(pocoOrPrimaryKey); + if (pocoOrPrimaryKeyValue.GetType() == typeof(T)) + return Delete(pocoOrPrimaryKeyValue); var pd = PocoData.ForType(typeof(T), _defaultMapper); - if (pocoOrPrimaryKey.GetType().Name.Contains("AnonymousType")) + if (pocoOrPrimaryKeyValue.GetType().Name.Contains("AnonymousType")) { - var pi = pocoOrPrimaryKey.GetType().GetProperty(pd.TableInfo.PrimaryKey); + var pi = pocoOrPrimaryKeyValue.GetType().GetProperty(pd.TableInfo.PrimaryKey); if (pi == null) throw new InvalidOperationException($"Anonymous type does not contain an id for PK column `{pd.TableInfo.PrimaryKey}`."); - pocoOrPrimaryKey = pi.GetValue(pocoOrPrimaryKey, new object[0]); + pocoOrPrimaryKeyValue = pi.GetValue(pocoOrPrimaryKeyValue, new object[0]); } - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); + return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKeyValue); } - /// - public int Delete(string sql, params object[] args) + /// + public int Delete(Sql sql) { var pd = PocoData.ForType(typeof(T), _defaultMapper); - return Execute($"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); + return Execute(new Sql($"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); } - /// - public int Delete(Sql sql) + /// + public int Delete(string sql, params object[] args) { var pd = PocoData.ForType(typeof(T), _defaultMapper); - return Execute(new Sql($"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); + return Execute($"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); } #if ASYNC + /// + public Task DeleteAsync(object poco) + => DeleteAsync(CancellationToken.None, poco); - /// + /// public Task DeleteAsync(string tableName, string primaryKeyName, object poco) => DeleteAsync(CancellationToken.None, tableName, primaryKeyName, poco); - /// - public Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) - => DeleteAsync(cancellationToken, tableName, primaryKeyName, poco, null); - - /// + /// public Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) => DeleteAsync(CancellationToken.None, tableName, primaryKeyName, poco, primaryKeyValue); - /// + /// + /// Anonymous type does not contain an id for primary key column. + public Task DeleteAsync(object pocoOrPrimaryKeyValue) + => DeleteAsync(CancellationToken.None, pocoOrPrimaryKeyValue); + + /// + public Task DeleteAsync(Sql sql) + => DeleteAsync(CancellationToken.None, sql); + + /// + public Task DeleteAsync(string sql, params object[] args) + => DeleteAsync(CancellationToken.None, sql, args); + + /// + public Task DeleteAsync(CancellationToken cancellationToken, object poco) + { + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return DeleteAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } + + /// + public Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) + => DeleteAsync(cancellationToken, tableName, primaryKeyName, poco, null); + + /// public Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue) { if (primaryKeyValue == null) @@ -2509,634 +2680,567 @@ public Task DeleteAsync(CancellationToken cancellationToken, string tableNa return ExecuteAsync(cancellationToken, sql, primaryKeyValue); } - /// - public Task DeleteAsync(object poco) - => DeleteAsync(CancellationToken.None, poco); - - /// - public Task DeleteAsync(CancellationToken cancellationToken, object poco) - { - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return DeleteAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - /// - public Task DeleteAsync(object pocoOrPrimaryKey) - => DeleteAsync(CancellationToken.None, pocoOrPrimaryKey); - - /// - public Task DeleteAsync(CancellationToken cancellationToken, object pocoOrPrimaryKey) + /// + /// Anonymous type does not contain an id for primary key column. + public Task DeleteAsync(CancellationToken cancellationToken, object pocoOrPrimaryKeyValue) { - if (pocoOrPrimaryKey.GetType() == typeof(T)) - return DeleteAsync(cancellationToken, pocoOrPrimaryKey); + if (pocoOrPrimaryKeyValue.GetType() == typeof(T)) + return DeleteAsync(cancellationToken, pocoOrPrimaryKeyValue); var pd = PocoData.ForType(typeof(T), _defaultMapper); - if (pocoOrPrimaryKey.GetType().Name.Contains("AnonymousType")) + if (pocoOrPrimaryKeyValue.GetType().Name.Contains("AnonymousType")) { - var pi = pocoOrPrimaryKey.GetType().GetProperty(pd.TableInfo.PrimaryKey); + var pi = pocoOrPrimaryKeyValue.GetType().GetProperty(pd.TableInfo.PrimaryKey); if (pi == null) throw new InvalidOperationException($"Anonymous type does not contain an id for PK column `{pd.TableInfo.PrimaryKey}`."); - pocoOrPrimaryKey = pi.GetValue(pocoOrPrimaryKey, new object[0]); + pocoOrPrimaryKeyValue = pi.GetValue(pocoOrPrimaryKeyValue, new object[0]); } - return DeleteAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); - } - - /// - public Task DeleteAsync(string sql, params object[] args) - => DeleteAsync(CancellationToken.None, sql, args); - - /// - public Task DeleteAsync(CancellationToken cancellationToken, string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T), _defaultMapper); - return ExecuteAsync(cancellationToken, $"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); + return DeleteAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKeyValue); } - /// - public Task DeleteAsync(Sql sql) - => DeleteAsync(CancellationToken.None, sql); - - /// + /// public Task DeleteAsync(CancellationToken cancellationToken, Sql sql) { var pd = PocoData.ForType(typeof(T), _defaultMapper); return ExecuteAsync(cancellationToken, new Sql($"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)}").Append(sql)); - } - -#endif - -#endregion - -#region operation: IsNew - - /// - public bool IsNew(string primaryKeyName, object poco) - { - if (poco == null) - throw new ArgumentNullException(nameof(poco)); - - if (string.IsNullOrEmpty(primaryKeyName)) - throw new ArgumentException("primaryKeyName"); - - return IsNew(primaryKeyName, PocoData.ForObject(poco, primaryKeyName, _defaultMapper), poco); - } - - /// - public bool IsNew(object poco) - { - if (poco == null) - throw new ArgumentNullException(nameof(poco)); - - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return IsNew(pd.TableInfo.PrimaryKey, pd, poco); - } - - protected virtual bool IsNew(string primaryKeyName, PocoData pd, object poco) - { - if (string.IsNullOrEmpty(primaryKeyName) || poco is ExpandoObject) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with identity (inc auto-increment) primary key columns"); - - object pk; - PocoColumn pc; - PropertyInfo pi; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pk = pc.GetValue(poco); - pi = pc.PropertyInfo; - } - else - { - pi = poco.GetType().GetProperty(primaryKeyName); - if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); - pk = pi.GetValue(poco, null); - } - - var type = pk != null ? pk.GetType() : pi.PropertyType; - - if (type == typeof(string)) - return string.IsNullOrEmpty((string)pk); - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) || !type.IsValueType) - return pk == null; - if (!pi.PropertyType.IsValueType) - return pk == null; - if (type == typeof(long)) - return (long) pk == default(long); - if (type == typeof(int)) - return (int) pk == default(int); - if (type == typeof(Guid)) - return (Guid) pk == default(Guid); - if (type == typeof(ulong)) - return (ulong) pk == default(ulong); - if (type == typeof(uint)) - return (uint) pk == default(uint); - if (type == typeof(short)) - return (short) pk == default(short); - if (type == typeof(ushort)) - return (ushort) pk == default(ushort); - if (type == typeof(decimal)) - return (decimal) pk == default(decimal); - - // Create a default instance and compare - return pk == Activator.CreateInstance(pk.GetType()); - } - -#endregion - -#region operation: Save - - /// - public void Save(string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - Insert(tableName, primaryKeyName, true, poco); - else - Update(tableName, primaryKeyName, poco); - } - - /// - public void Save(object poco) - { - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - -#if ASYNC - - /// - public Task SaveAsync(string tableName, string primaryKeyName, object poco) - => SaveAsync(CancellationToken.None, tableName, primaryKeyName, poco); - - /// - public Task SaveAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - return InsertAsync(cancellationToken, tableName, primaryKeyName, true, poco); - - return UpdateAsync(cancellationToken, tableName, primaryKeyName, poco); - } - - /// - public Task SaveAsync(object poco) - => SaveAsync(CancellationToken.None, poco); - - /// - public Task SaveAsync(CancellationToken cancellationToken, object poco) - { - var pd = PocoData.ForType(poco.GetType(), _defaultMapper); - return SaveAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - -#endif - -#endregion - -#region operation: Multi-Poco Query/Fetch - - /// - public List Fetch(Func cb, string sql, params object[] args) - => Query(cb, sql, args).ToList(); - - /// - public List Fetch(Func cb, string sql, params object[] args) - => Query(cb, sql, args).ToList(); - - /// - public List Fetch(Func cb, string sql, params object[] args) - => Query(cb, sql, args).ToList(); - - /// - public List Fetch(Func cb, string sql, params object[] args) - => Query(cb, sql, args).ToList(); - - /// - public IEnumerable Query(Func cb, string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2) }, cb, sql, args); - - /// - public IEnumerable Query(Func cb, string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); - - /// - public IEnumerable Query(Func cb, string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); - - public IEnumerable Query(Func cb, string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql, args); - - /// - public List Fetch(Func cb, Sql sql) - => Query(cb, sql.SQL, sql.Arguments).ToList(); - - /// - public List Fetch(Func cb, Sql sql) - => Query(cb, sql.SQL, sql.Arguments).ToList(); - - /// - public List Fetch(Func cb, Sql sql) - => Query(cb, sql.SQL, sql.Arguments).ToList(); - - public List Fetch(Func cb, Sql sql) - => Query(cb, sql.SQL, sql.Arguments).ToList(); - - /// - public IEnumerable Query(Func cb, Sql sql) - => Query(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); - - /// - public IEnumerable Query(Func cb, Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); - - /// - public IEnumerable Query(Func cb, Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); - - /// - public IEnumerable Query(Func cb, Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql.SQL, sql.Arguments); - - /// - public List Fetch(string sql, params object[] args) - => Query(sql, args).ToList(); - - /// - public List Fetch(string sql, params object[] args) - => Query(sql, args).ToList(); - - /// - public List Fetch(string sql, params object[] args) - => Query(sql, args).ToList(); - - /// - public List Fetch(string sql, params object[] args) - => Query(sql, args).ToList(); - - /// - public IEnumerable Query(string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2) }, null, sql, args); - - /// - public IEnumerable Query(string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); - - /// - public IEnumerable Query(string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); - - /// - public IEnumerable Query(string sql, params object[] args) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql, args); - - /// - public List Fetch(Sql sql) - => Query(sql.SQL, sql.Arguments).ToList(); - - /// - public List Fetch(Sql sql) - => Query(sql.SQL, sql.Arguments).ToList(); - - /// - public List Fetch(Sql sql) - => Query(sql.SQL, sql.Arguments).ToList(); + } - /// - public List Fetch(Sql sql) - => Query(sql.SQL, sql.Arguments).ToList(); + /// + public Task DeleteAsync(CancellationToken cancellationToken, string sql, params object[] args) + { + var pd = PocoData.ForType(typeof(T), _defaultMapper); + return ExecuteAsync(cancellationToken, $"DELETE FROM {_provider.EscapeTableName(pd.TableInfo.TableName)} {sql}", args); + } +#endif - /// - public IEnumerable Query(Sql sql) - => Query(new[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); + #endregion - /// - public IEnumerable Query(Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); + #region IsNew - public IEnumerable Query(Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); + /// + /// is null. + public bool IsNew(object poco) + { + if (poco == null) + throw new ArgumentNullException(nameof(poco)); - /// - public IEnumerable Query(Sql sql) - => Query(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql.SQL, sql.Arguments); + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return IsNew(pd.TableInfo.PrimaryKey, pd, poco); + } - /// - public IEnumerable Query(Type[] types, object cb, string sql, params object[] args) + /// + /// is empty. + /// or is null. + public bool IsNew(string primaryKeyName, object poco) { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - try - { - r = ExecuteReaderHelper(cmd); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } + if (primaryKeyName == null) + throw new ArgumentNullException(nameof(primaryKeyName)); + if (string.IsNullOrEmpty(primaryKeyName)) + throw new ArgumentException(nameof(primaryKeyName)); + if (poco == null) + throw new ArgumentNullException(nameof(poco)); - var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r, _defaultMapper); - if (cb == null) - cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); - var bNeedTerminator = false; - using (r) - { - while (true) - { - TRet poco; - try - { - if (!r.Read()) - break; - poco = factory(r, cb); - } - catch (Exception x) - { - if (OnException(x)) - throw; - yield break; - } + return IsNew(primaryKeyName, PocoData.ForObject(poco, primaryKeyName, _defaultMapper), poco); + } - if (poco != null) - yield return poco; - else - bNeedTerminator = true; - } + /// + /// The table's primary key column name. + /// The PocoData instance for the specified POCO. + /// The POCO instance to check. + /// is null or empty, or is an + /// ExpandoObject. + /// The doesn't have a property matching the primary key column + /// name. + protected virtual bool IsNew(string primaryKeyName, PocoData pocoData, object poco) + { + if (string.IsNullOrEmpty(primaryKeyName) || poco is ExpandoObject) + throw new InvalidOperationException("IsNew() and Save() are only supported on tables with identity (inc auto-increment) primary key columns"); - if (bNeedTerminator) - { - var poco = (TRet) (cb as Delegate).DynamicInvoke(new object[types.Length]); - if (poco != null) - yield return poco; - else - yield break; - } - } - } + object pk; + PocoColumn pc; + PropertyInfo pi; + if (pocoData.Columns.TryGetValue(primaryKeyName, out pc)) + { + pk = pc.GetValue(poco); + pi = pc.PropertyInfo; } - finally + else { - CloseSharedConnection(); + pi = poco.GetType().GetProperty(primaryKeyName); + if (pi == null) + throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); + pk = pi.GetValue(poco, null); } + + var type = pk != null ? pk.GetType() : pi.PropertyType; + + if (type == typeof(string)) + return string.IsNullOrEmpty((string)pk); + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) || !type.IsValueType) + return pk == null; + if (!pi.PropertyType.IsValueType) + return pk == null; + if (type == typeof(long)) + return (long)pk == default(long); + if (type == typeof(int)) + return (int)pk == default(int); + if (type == typeof(Guid)) + return (Guid)pk == default(Guid); + if (type == typeof(ulong)) + return (ulong)pk == default(ulong); + if (type == typeof(uint)) + return (uint)pk == default(uint); + if (type == typeof(short)) + return (short)pk == default(short); + if (type == typeof(ushort)) + return (ushort)pk == default(ushort); + if (type == typeof(decimal)) + return (decimal)pk == default(decimal); + + // Create a default instance and compare + return pk == Activator.CreateInstance(pk.GetType()); } -#endregion + #endregion -#region operation: Multi-Result Set + #region Save, SaveAsync - public IGridReader QueryMultiple(Sql sql) - => QueryMultiple(sql.SQL, sql.Arguments); + /// + /// is null. + public void Save(object poco) + { + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } - public IGridReader QueryMultiple(string sql, params object[] args) + // TODO: Inconsistent exception types for same params. Save(string,string,object) throws: first 2 types (2 lines) from IsNew, last 3 types (1 line) from Insert and Update. `primaryKeyName` overlaps with 2 different exception types. + + /// + /// is null or empty. + /// is null. + /// , , or is null or empty. + public void Save(string tableName, string primaryKeyName, object poco) { - OpenSharedConnection(); + if (IsNew(primaryKeyName, poco)) + Insert(tableName, primaryKeyName, true, poco); + else + Update(tableName, primaryKeyName, poco); + } - GridReader result = null; +#if ASYNC + /// + /// is null. + public Task SaveAsync(object poco) + => SaveAsync(CancellationToken.None, poco); - var cmd = CreateCommand(_sharedConnection, sql, args); + /// + /// is null or empty. + /// , , or is null or empty. + public Task SaveAsync(string tableName, string primaryKeyName, object poco) + => SaveAsync(CancellationToken.None, tableName, primaryKeyName, poco); - try - { - var reader = ExecuteReaderHelper(cmd); - result = new GridReader(this, cmd, reader, _defaultMapper); - } - catch (Exception x) - { - if (OnException(x)) - throw; - } + /// + /// is null. + public Task SaveAsync(CancellationToken cancellationToken, object poco) + { + var pd = PocoData.ForType(poco.GetType(), _defaultMapper); + return SaveAsync(cancellationToken, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } - return result; + /// + /// is null or empty. + /// , , or is null or empty. + public Task SaveAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco) + { + if (IsNew(primaryKeyName, poco)) + return InsertAsync(cancellationToken, tableName, primaryKeyName, true, poco); + + return UpdateAsync(cancellationToken, tableName, primaryKeyName, poco); } +#endif + + #endregion -#endregion + #region StoredProcedures, StoredProceduresAsync -#region operation: StoredProc + /// + public int ExecuteNonQueryProc(string storedProcedureName, params object[] args) + => ExecuteInternal(CommandType.StoredProcedure, storedProcedureName, args); + + /// + public T ExecuteScalarProc(string storedProcedureName, params object[] args) + => ExecuteScalarInternal(CommandType.StoredProcedure, storedProcedureName, args); - /// + /// public IEnumerable QueryProc(string storedProcedureName, params object[] args) => ExecuteReader(CommandType.StoredProcedure, storedProcedureName, args); - /// + /// public List FetchProc(string storedProcedureName, params object[] args) => QueryProc(storedProcedureName, args).ToList(); - /// - public T ExecuteScalarProc(string storedProcedureName, params object[] args) - => ExecuteScalarInternal(CommandType.StoredProcedure, storedProcedureName, args); - - /// - public int ExecuteNonQueryProc(string storedProcedureName, params object[] args) - => ExecuteInternal(CommandType.StoredProcedure, storedProcedureName, args); - #if ASYNC - /// - public Task QueryProcAsync(Action receivePocoCallback, string storedProcedureName, params object[] args) - => QueryProcAsync(receivePocoCallback, CancellationToken.None, storedProcedureName, args); + /// + public Task ExecuteNonQueryProcAsync(string storedProcedureName, params object[] args) + => ExecuteNonQueryProcAsync(CancellationToken.None, storedProcedureName, args); - /// - public Task QueryProcAsync(Action receivePocoCallback, CancellationToken cancellationToken, string storedProcedureName, params object[] args) - => ExecuteReaderAsync(receivePocoCallback, cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + /// + public Task ExecuteScalarProcAsync(string storedProcedureName, params object[] args) + => ExecuteScalarProcAsync(CancellationToken.None, storedProcedureName, args); - /// + /// public Task> QueryProcAsync(string storedProcedureName, params object[] args) => QueryProcAsync(CancellationToken.None, storedProcedureName, args); - /// - public Task> QueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) - => ExecuteReaderAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + /// + public Task QueryProcAsync(Action action, string storedProcedureName, params object[] args) + => QueryProcAsync(action, CancellationToken.None, storedProcedureName, args); - /// + /// public Task> FetchProcAsync(string storedProcedureName, params object[] args) => FetchProcAsync(CancellationToken.None, storedProcedureName, args); - /// + /// + public Task ExecuteNonQueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) + => ExecuteInternalAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + + /// + public Task ExecuteScalarProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) + => ExecuteScalarInternalAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + + /// + public Task> QueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) + => ExecuteReaderAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + + /// + public Task QueryProcAsync(Action action, CancellationToken cancellationToken, string storedProcedureName, params object[] args) + => ExecuteReaderAsync(action, cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); + + /// public async Task> FetchProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) { var pocos = new List(); await ExecuteReaderAsync(p => pocos.Add(p), cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); return pocos; } - - /// - public Task ExecuteScalarProcAsync(string storedProcedureName, params object[] args) - => ExecuteScalarProcAsync(CancellationToken.None, storedProcedureName, args); - - /// - public Task ExecuteScalarProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) - => ExecuteScalarInternalAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); - - /// - public Task ExecuteNonQueryProcAsync(string storedProcedureName, params object[] args) - => ExecuteNonQueryProcAsync(CancellationToken.None, storedProcedureName, args); - - /// - public Task ExecuteNonQueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args) - => ExecuteInternalAsync(cancellationToken, CommandType.StoredProcedure, storedProcedureName, args); #endif -#endregion - -#region Last Command - - /// - /// Retrieves the SQL of the last executed statement - /// - public string LastSQL => _lastSql; + #endregion - /// - /// Retrieves the arguments to the last execute statement - /// - public object[] LastArgs => _lastArgs; + #region Command & Parameter Creation /// - /// Returns a formatted string describing the last executed SQL statement and its argument values + /// Creates a IDbCommand with the given connection, SQL command text, and arguments. /// - public string LastCommand => FormatCommand(_lastSql, _lastArgs); - -#endregion - -#region FormatCommand + /// The connection that will execute the SQL command. + /// The SQL command string. + /// The parameters to embed in the SQL string. + /// An IDbCommand object that represents the SQL command to execute against the provided IDbConnection. + public IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args) + => CreateCommand(connection, CommandType.Text, sql, args); /// - /// Formats the contents of a DB command for display + /// Creates an IDbCommand with the given connection, command type, SQL command text, and arguments. /// - /// - /// - public string FormatCommand(IDbCommand cmd) + /// The connection that will execute the SQL command. + /// The type of SQL command to create. + /// The SQL command string. + /// The parameters to embed in the SQL string. + /// An IDbCommand object that represents the SQL command to execute against the provided IDbConnection. + public IDbCommand CreateCommand(IDbConnection connection, CommandType commandType, string sql, params object[] args) { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); - } + var cmd = connection.CreateCommand(); - /// - /// Formats an SQL query and its arguments for display - /// - /// - /// - /// - public string FormatCommand(string sql, object[] args) - { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) + try { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) + cmd.CommandType = commandType; + cmd.Transaction = _transaction; + + switch (commandType) { - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); + case CommandType.Text: + // Perform named argument replacements + if (EnableNamedParams) + { + var newArgs = new List(); + sql = ParametersHelper.ProcessQueryParams(sql, args, newArgs); + args = newArgs.ToArray(); + } + + // Perform parameter prefix replacements + if (_paramPrefix != "@") + sql = sql.ReplaceParamPrefix(_paramPrefix); + sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ + break; + case CommandType.StoredProcedure: + args = ParametersHelper.ProcessStoredProcParams(cmd, args, SetParameterProperties); + break; + case CommandType.TableDirect: + break; } - sb.Remove(sb.Length - 1, 1); - } - - return sb.ToString(); - } + cmd.CommandText = sql; -#endregion + foreach (var item in args) + AddParameter(cmd, item, null); -#region Public Properties + return cmd; + } + catch + { + cmd.Dispose(); + throw; + } + } /// - /// Gets the default mapper. + /// Creates an IDbDataParameter with default values. /// - public IMapper DefaultMapper => _defaultMapper; + /// The IDbDataParameter. + public IDbDataParameter CreateParameter() + => _factory.CreateParameter(); /// - /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it - /// needs it + /// Creates an IDbDataParameter with the given name and value. /// - public bool EnableAutoSelect { get; set; } + /// The parameter name. + /// The parameter value. + /// The IDbDataParameter. + public IDbDataParameter CreateParameter(string name, object value) + => CreateParameter(name, value, ParameterDirection.Input); /// - /// When set to true, parameters can be named ?myparam and populated from properties of the passed-in argument values. + /// Creates an IDbDataParameter with the given name and direction. /// - public bool EnableNamedParams { get; set; } + /// The parameter name. + /// The parameter direction. + /// The IDbDataParameter. + public IDbDataParameter CreateParameter(string name, ParameterDirection direction) + => CreateParameter(name, null, direction); /// - /// Sets the timeout value for all SQL statements. + /// Creates an IDbParameter with the given name, value, and direction. /// - public int CommandTimeout { get; set; } + /// The parameter name. + /// The parameter value. + /// The parameter direction. + /// The IDbDataParameter. + public IDbDataParameter CreateParameter(string name, object value, ParameterDirection direction) + { + var result = CreateParameter(); + result.ParameterName = name; + result.Value = value; + result.Direction = direction; + return result; + } /// - /// Sets the timeout value for the next (and only next) SQL statement + /// Prepares an IDbDataParameter by setting its properties prior to being added to a command. /// - public int OneTimeCommandTimeout { get; set; } + /// The IDbDataParameter to which the properties will be set. + /// The value to be assigned to the IDbDataParameter. + /// The PocoColumn instance for the POCO's column-mapped property. + private void SetParameterProperties(IDbDataParameter param, object value, PocoColumn pocoColumn) + { + // Assign the parameter value + if (value == null) + { + param.Value = DBNull.Value; - /// - /// Gets the loaded database provider. . - /// - /// - /// The loaded database type. - /// - public IProvider Provider => _provider; + if (pocoColumn?.PropertyInfo.PropertyType.Name == "Byte[]") + param.DbType = DbType.Binary; + } + else + { + // Give the database type first crack at converting to DB required type + value = _provider.MapParameterValue(value); - /// - /// Gets the connection string. - /// - /// - /// The connection string. - /// - public string ConnectionString => _connectionString; + var t = value.GetType(); + + if (t == typeof(string) && pocoColumn?.ForceToAnsiString == true) + { + t = typeof(AnsiString); + value = value.ToAnsiString(); + } + if (t == typeof(DateTime) && pocoColumn?.ForceToDateTime2 == true) + { + t = typeof(DateTime2); + value = ((DateTime)value).ToDateTime2(); + } + + if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int + { + param.Value = Convert.ChangeType(value, ((Enum)value).GetTypeCode()); + } + else if (t == typeof(Guid) && !_provider.HasNativeGuidSupport) + { + param.Value = value.ToString(); + param.DbType = DbType.String; + param.Size = 40; + } + else if (t == typeof(string)) + { + // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 + if ((value as string).Length + 1 > 4000 && param.GetType().Name == "SqlCeParameter") + param.GetType().GetProperty("SqlDbType").SetValue(param, SqlDbType.NText, null); + + param.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size + param.Value = value; + } + else if (t == typeof(AnsiString)) + { + var asValue = (value as AnsiString).Value; + if (asValue == null) + { + param.Size = 0; + param.Value = DBNull.Value; + } + else + { + param.Size = Math.Max(asValue.Length + 1, 4000); + param.Value = asValue; + } + // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar + param.DbType = DbType.AnsiString; + } + else if (t == typeof(DateTime2)) + { + var dt2Value = (value as DateTime2)?.Value; + param.Value = dt2Value ?? (object)DBNull.Value; + param.DbType = DbType.DateTime2; + } + else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type + { + param.GetType().GetProperty("UdtTypeName").SetValue(param, "geography", null); //geography is the equivalent SQL Server Type + param.Value = value; + } + else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type + { + param.GetType().GetProperty("UdtTypeName").SetValue(param, "geometry", null); //geography is the equivalent SQL Server Type + param.Value = value; + } + else if (t == typeof(byte[])) + { + param.Value = value; + param.DbType = DbType.Binary; + } + else + { + param.Value = value; + } + } + } /// - /// Gets or sets the transaction isolation level. + /// Adds an IDbDataParameter to a command. /// - /// - /// When value is null, the underlying providers default isolation level is used. - /// - public IsolationLevel? IsolationLevel + /// The SQL command receiving the parameter. + /// The value to assign to the parameter. + /// An optional reference to the PocoColumn instance the value originated from. + private void AddParameter(IDbCommand cmd, object value, PocoColumn pc) { - get => _isolationLevel; - set + // Convert value to from poco type to db type + if (pc?.PropertyInfo != null) + { + var mapper = Mappers.GetMapper(pc.PropertyInfo.DeclaringType, _defaultMapper); + var fn = mapper.GetToDbConverter(pc.PropertyInfo); + if (fn != null) + value = fn(value); + } + + // Support passed in parameters + if (value is IDbDataParameter idbParam) + { + if (cmd.CommandType == CommandType.Text) + idbParam.ParameterName = cmd.Parameters.Count.EnsureParamPrefix(_paramPrefix); + else if (idbParam.ParameterName?.StartsWith(_paramPrefix) != true) + idbParam.ParameterName = idbParam.ParameterName.EnsureParamPrefix(_paramPrefix); + + cmd.Parameters.Add(idbParam); + } + else { - if (_transaction != null) - throw new InvalidOperationException("Isolation level can't be changed during a transaction."); + var p = cmd.CreateParameter(); + p.ParameterName = cmd.Parameters.Count.EnsureParamPrefix(_paramPrefix); + SetParameterProperties(p, value, pc); - _isolationLevel = value; + cmd.Parameters.Add(p); } } #endregion - #region Helpers + #region Execute Command Helpers + + /// + /// Executes an SQL query command and returns a data reader for reading the result set. + /// + /// The SQL command to execute. + /// A data reader for reading the result set. + /// internal protected IDataReader ExecuteReaderHelper(IDbCommand cmd) { return (IDataReader)CommandHelper(cmd, c => c.ExecuteReader()); } + /// + /// Executes an SQL non-query command and returns the number of rows affected. + /// + /// The SQL command to execute. + /// The number of rows affected. + /// internal protected int ExecuteNonQueryHelper(IDbCommand cmd) { return (int)CommandHelper(cmd, c => c.ExecuteNonQuery()); } + /// + /// Executes an SQL scalar command and returns the first column of the first row in the result set. + /// + /// The SQL command to execute. + /// The first column of the first row in the result set. + /// internal protected object ExecuteScalarHelper(IDbCommand cmd) { return CommandHelper(cmd, c => c.ExecuteScalar()); } - private object CommandHelper(IDbCommand cmd, Func cmdFunc) + /// + /// Executes an SQL command using the provided function and returns the result. + /// + /// The SQL command to execute. + /// The function to execute the SQL command and return the result. + /// The result of the SQL command execution. + private object CommandHelper(IDbCommand cmd, Func executionFunction) { DoPreExecute(cmd); - var result = cmdFunc(cmd); + var result = executionFunction(cmd); OnExecutedCommand(cmd); return result; } #if ASYNC + /// + /// Asynchronously executes an SQL command and returns a data reader for reading the result set. + /// + /// A cancellation token that can be used to cancel the operation. + /// The SQL command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains a data reader for reading the result set. + /// + /// internal protected async Task ExecuteReaderHelperAsync(CancellationToken cancellationToken, IDbCommand cmd) { if (cmd is DbCommand dbCommand) { - var task = CommandHelper(cancellationToken, dbCommand, + var task = CommandHelperAsync(cancellationToken, dbCommand, async (t, c) => await c.ExecuteReaderAsync(t).ConfigureAwait(false)); return (IDataReader)await task.ConfigureAwait(false); } @@ -3144,11 +3248,20 @@ internal protected async Task ExecuteReaderHelperAsync(Cancellation return ExecuteReaderHelper(cmd); } + /// + /// Asynchronously executes an SQL non-query command and returns the number of rows affected. + /// + /// A cancellation token that can be used to cancel the operation. + /// The SQL command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected. + /// + /// internal protected async Task ExecuteNonQueryHelperAsync(CancellationToken cancellationToken, IDbCommand cmd) { if (cmd is DbCommand dbCommand) { - var task = CommandHelper(cancellationToken, dbCommand, + var task = CommandHelperAsync(cancellationToken, dbCommand, async (t, c) => await c.ExecuteNonQueryAsync(t).ConfigureAwait(false)); return (int)await task.ConfigureAwait(false); } @@ -3156,79 +3269,227 @@ internal protected async Task ExecuteNonQueryHelperAsync(CancellationToken return ExecuteNonQueryHelper(cmd); } + /// + /// Asynchronously executes an SQL scalar command and returns the first column of the first row in the result set. + /// + /// A cancellation token that can be used to cancel the operation. + /// The SQL command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains the first column of the first row in the result set. + /// + /// internal protected Task ExecuteScalarHelperAsync(CancellationToken cancellationToken, IDbCommand cmd) { if (cmd is DbCommand dbCommand) - return CommandHelper(cancellationToken, dbCommand, + { + return CommandHelperAsync(cancellationToken, dbCommand, async (t, c) => await c.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false)); + } else return Task.FromResult(ExecuteScalarHelper(cmd)); } - private async Task CommandHelper(CancellationToken cancellationToken, DbCommand cmd, - Func> cmdFunc) + // TODO: `CommandHelperAsync(CancellationToken, DbCommand, Func>` takes DBCommand type; inconsistent with synchronous version `CommandHelper(IDbCommand, Func)` which uses IDbCommand type (is this intentional?) + + /// + /// Asynchronously executes an SQL command using the provided function and returns the result. + /// + /// A cancellation token that can be used to cancel the operation. + /// The SQL command to execute. + /// The function to execute the SQL command and return the result. + /// + /// A task that represents the asynchronous operation. The task result contains an object containing the result of the SQL command + /// execution. + /// + private async Task CommandHelperAsync(CancellationToken cancellationToken, DbCommand cmd, Func> executionFunction) { DoPreExecute(cmd); - var result = await cmdFunc(cancellationToken, cmd).ConfigureAwait(false); + var result = await executionFunction(cancellationToken, cmd).ConfigureAwait(false); OnExecutedCommand(cmd); return result; } #endif + + internal void DoPreExecute(IDbCommand cmd) + { + if (CommandTimeout > 0 || OneTimeCommandTimeout > 0) + { + cmd.CommandTimeout = OneTimeCommandTimeout > 0 ? OneTimeCommandTimeout : CommandTimeout; + OneTimeCommandTimeout = 0; + } + + _provider.PreExecute(cmd); + OnExecutingCommand(cmd); + + _lastSql = cmd.CommandText; + _lastArgs = cmd.Parameters.Cast().Select(parameter => parameter.Value).ToArray(); + } + #endregion - #region Events + #region Last Command + + /// + public string LastSQL => _lastSql; + + /// + public object[] LastArgs => _lastArgs; + + /// + public string LastCommand => FormatCommand(_lastSql, _lastArgs); /// - /// Occurs when a new transaction has started. + /// Formats an IDbCommand for display. /// - public event EventHandler TransactionStarted; + /// The SQL command to format. + /// The formatted SQL command. + public string FormatCommand(IDbCommand cmd) + { + return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); + } /// - /// Occurs when a transaction is about to be rolled back or committed. + /// Formats an SQL statement and its arguments for display. /// - public event EventHandler TransactionEnding; + /// The SQL statement. + /// The parameters embedded in the SQL statement. + /// The formatted SQL statement. + public string FormatCommand(string sql, object[] args) + { + var sb = new StringBuilder(); + if (sql == null) + return ""; + sb.Append(sql); + if (args != null && args.Length > 0) + { + sb.Append("\n"); + for (int i = 0; i < args.Length; i++) + { + sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); + } + + sb.Remove(sb.Length - 1, 1); + } + + return sb.ToString(); + } + + #endregion + + #region Configuration Properties /// - /// Occurs when a database command is about to be executed. + /// + /// Default is . /// - public event EventHandler CommandExecuting; + public IMapper DefaultMapper => _defaultMapper; + + /// + public string ConnectionString => _connectionString; + + /// + public IProvider Provider => _provider; /// - /// Occurs when a database command has been executed. + /// + /// Default is . /// - public event EventHandler CommandExecuted; + /// If , the default isolation level of the underlying is used. + /// If changed while a transaction is in progress. + public IsolationLevel? IsolationLevel + { + get => _isolationLevel; + set => _isolationLevel = _transaction == null + ? value + : throw new InvalidOperationException("Isolation level can not be changed during a transaction."); + } /// - /// Occurs when a database connection is about to be closed. + /// + /// Default is . /// - public event EventHandler ConnectionClosing; + /// + public bool KeepConnectionAlive { get; set; } /// - /// Occurs when a database connection has been opened. + /// + /// Default is . /// - public event EventHandler ConnectionOpened; + /// If , PetaPoco will automatically generate the SELECT portion of the query when needed if not + /// explicitly provided in the supplied SQL statement. + /// + /// In the following example, all three queries below will result in the same outcome: + /// + /// ("WHERE `id` = @0", 123); + /// var note = db.Single("FROM `notes` WHERE `id` = @0", 123); + /// var note = db.Single("SELECT * FROM `notes` WHERE `id` = @0", 123); + /// ]]> + /// + /// The generated SQL produced by PetaPoco, shown below, is identical for all three: + /// + /// + /// + /// + public bool EnableAutoSelect { get; set; } /// - /// Occurs when a database connection is about to be opened. + /// Default is . /// - public event EventHandler ConnectionOpening; + /// If , parameters can be named "?myparam" in the SQL string, and populated from properties of the + /// passed-in argument values. + public bool EnableNamedParams { get; set; } + + /// + /// Default is 0. + /// + /// If 0, PetaPoco will use the default value for the active database (typically 30 seconds). + public int CommandTimeout { get; set; } /// - /// Occurs when a database exception has been thrown. + /// /// + public int OneTimeCommandTimeout { get; set; } + + #endregion + + #region Events + + /// + public event EventHandler TransactionStarted; + + /// + public event EventHandler TransactionEnding; + + /// + public event EventHandler CommandExecuting; + + /// + public event EventHandler CommandExecuted; + + /// + public event EventHandler ConnectionOpening; + + /// + public event EventHandler ConnectionOpened; + + /// + public event EventHandler ConnectionClosing; + + /// public event EventHandler ExceptionThrown; -#endregion + #endregion } + /// + /// The provider type, which must implement the interface. public class Database : Database where TDatabaseProvider : IProvider { - /// - /// Constructs an instance using a supplied connection string and provider type. - /// - /// The database connection string. - /// The default mapper to use when no specific mapper has been registered. - /// Thrown when is null or empty. + /// public Database(string connectionString, IMapper defaultMapper = null) : base(connectionString, typeof(TDatabaseProvider).Name, defaultMapper) { diff --git a/PetaPoco/DatabaseConfiguration.cs b/PetaPoco/DatabaseConfiguration.cs index 95ad1706..e8798c63 100644 --- a/PetaPoco/DatabaseConfiguration.cs +++ b/PetaPoco/DatabaseConfiguration.cs @@ -4,14 +4,14 @@ namespace PetaPoco { /// - /// A helper class which enables fluent configuration. + /// A helper class which enables fluent configuration. /// public class DatabaseConfiguration : IDatabaseBuildConfiguration, IBuildConfigurationSettings, IHideObjectMethods { private readonly IDictionary _settings = new Dictionary(); /// - /// Private constructor to force usage of static build method. + /// Private constructor to force usage of static build method. /// private DatabaseConfiguration() { @@ -26,22 +26,22 @@ void IBuildConfigurationSettings.SetSetting(string key, object value) _settings.Remove(key); } - void IBuildConfigurationSettings.TryGetSetting(string key, Action setSetting, Action onFail) + void IBuildConfigurationSettings.TryGetSetting(string key, Action onGetAction, Action onFailAction) { // Note: no argument checking because, pref, enduser unlikely and handled by RT/FW if (_settings.TryGetValue(key, out var setting)) - setSetting((T) setting); + onGetAction((T) setting); else - onFail?.Invoke(); + onFailAction?.Invoke(); } /// - /// Starts a new PetaPoco build configuration. + /// Starts a new PetaPoco build configuration. /// - /// An instance of to form a fluent interface. + /// An instance of to form a fluent interface. public static IDatabaseBuildConfiguration Build() { return new DatabaseConfiguration(); } } -} \ No newline at end of file +} diff --git a/PetaPoco/DatabaseConfigurationExtensions.cs b/PetaPoco/DatabaseConfigurationExtensions.cs index 107d45d2..ae25e88f 100644 --- a/PetaPoco/DatabaseConfigurationExtensions.cs +++ b/PetaPoco/DatabaseConfigurationExtensions.cs @@ -5,20 +5,20 @@ namespace PetaPoco { /// - /// A static helper class where extensions for are placed. + /// Extension methods for , and the fluent interface integration. /// public static class DatabaseConfigurationExtensions { internal const string CommandTimeout = "CommandTimeout"; internal const string EnableAutoSelect = "EnableAutoSelect"; internal const string EnableNamedParams = "EnableNamedParams"; - internal const string Provider = "Provider"; - internal const string ConnectionString = "ConnectionString"; - internal const string ProviderName = "ProviderName"; #if !NETSTANDARD internal const string ConnectionStringName = "ConnectionStringName"; #endif + internal const string ConnectionString = "ConnectionString"; + internal const string ProviderName = "ProviderName"; + internal const string Provider = "Provider"; internal const string DefaultMapper = "DefaultMapper"; internal const string IsolationLevel = "IsolationLevel"; @@ -34,16 +34,18 @@ public static class DatabaseConfigurationExtensions private static void SetSetting(this IDatabaseBuildConfiguration source, string key, object value) { - ((IBuildConfigurationSettings) source).SetSetting(key, value); + ((IBuildConfigurationSettings)source).SetSetting(key, value); } + #region Timeout Settings + /// - /// Adds a command timeout - see . + /// Sets to the specified value. /// /// The configuration source. /// The timeout in seconds. - /// Thrown when seconds is less than 1. - /// The configuration source, to form a fluent interface. + /// The original configuration, to form a fluent interface. + /// is less than 1. public static IDatabaseBuildConfiguration UsingCommandTimeout(this IDatabaseBuildConfiguration source, int seconds) { if (seconds < 1) @@ -52,265 +54,322 @@ public static IDatabaseBuildConfiguration UsingCommandTimeout(this IDatabaseBuil return source; } + #endregion + + #region AutoSelect Settings + /// - /// Enables named params - see . + /// Enables auto-select, equivalent to setting to . /// /// The configuration source. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration WithNamedParams(this IDatabaseBuildConfiguration source) + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration WithAutoSelect(this IDatabaseBuildConfiguration source) { - source.SetSetting(EnableNamedParams, true); + source.SetSetting(EnableAutoSelect, true); return source; } /// - /// Disables named params - see . + /// Disables auto-select, equivalent to setting to . /// /// The configuration source. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration WithoutNamedParams(this IDatabaseBuildConfiguration source) + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration WithoutAutoSelect(this IDatabaseBuildConfiguration source) { - source.SetSetting(EnableNamedParams, false); + source.SetSetting(EnableAutoSelect, false); return source; } + #endregion + + #region NamedParams Settings + /// - /// Specifies the provider to be used. - see . - /// This takes precedence over . + /// Enables named parameters, equivalent to setting to . /// /// The configuration source. - /// The provider to use. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, T provider) where T : class, IProvider + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration WithNamedParams(this IDatabaseBuildConfiguration source) { - if (provider == null) - throw new ArgumentNullException(nameof(provider)); - source.SetSetting(Provider, provider); + source.SetSetting(EnableNamedParams, true); return source; } /// - /// Specifies the provider to be used. - see . - /// This takes precedence over . + /// Disables named parameters, equivalent to setting to . /// /// The configuration source. - /// The configure provider callback. - /// The provider to use. - /// Thrown when is null. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, T provider, Action configure) where T : class, IProvider + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration WithoutNamedParams(this IDatabaseBuildConfiguration source) { - if (provider == null) - throw new ArgumentNullException(nameof(provider)); - if (configure == null) - throw new ArgumentNullException(nameof(configure)); - configure(provider); - source.SetSetting(Provider, provider); + source.SetSetting(EnableNamedParams, false); return source; } + #endregion + + #region Connection Settings + +#if !NETSTANDARD /// - /// Specifies the provider to be used. - see . - /// This takes precedence over . + /// Specifies a connection string name to be used to locate a connection string. The and + /// will be read from the app or web configuration file. /// + /// + /// PetaPoco will automatically close and dispose of any connections it creates. + /// /// The configuration source. - /// The provider type. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source) where T : class, IProvider, new() + /// The name of the connection string to locate. + /// The original configuration, to form a fluent interface. + /// is null or empty. + public static IDatabaseBuildConfiguration UsingConnectionStringName(this IDatabaseBuildConfiguration source, string connectionStringName) { - source.SetSetting(Provider, new T()); + if (string.IsNullOrEmpty(connectionStringName)) + throw new ArgumentException("Argument is null or empty", nameof(connectionStringName)); + source.SetSetting(ConnectionStringName, connectionStringName); return source; } +#endif /// - /// Specifies the provider to be used. - see . - /// This takes precedence over . + /// Specifies a connection string to use. /// + /// + /// PetaPoco will automatically close and dispose of any connections it creates. + /// /// The configuration source. - /// The configure provider callback. - /// The provider type. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, Action configure) where T : class, IProvider, new() + /// The connection string. + /// The original configuration, to form a fluent interface. + /// is null or empty. + public static IDatabaseBuildConfiguration UsingConnectionString(this IDatabaseBuildConfiguration source, string connectionString) { - if (configure == null) - throw new ArgumentNullException(nameof(configure)); - var provider = new T(); - configure(provider); - source.SetSetting(Provider, provider); + if (string.IsNullOrEmpty(connectionString)) + throw new ArgumentException("Argument is null or empty", nameof(connectionString)); + source.SetSetting(ConnectionString, connectionString); return source; } /// - /// Enables auto select - see . + /// Specifies an existing to use. /// + /// + /// The supplied IDbConnection will not be closed and disposed of by PetaPoco - that remains the responsibility of the caller. + /// /// The configuration source. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration WithAutoSelect(this IDatabaseBuildConfiguration source) + /// The database connection. + /// The original configuration, to form a fluent interface. + /// is null. + public static IDatabaseBuildConfiguration UsingConnection(this IDatabaseBuildConfiguration source, IDbConnection connection) { - source.SetSetting(EnableAutoSelect, true); + if (connection == null) + throw new ArgumentNullException(nameof(connection)); + source.SetSetting(Connection, connection); return source; } + #endregion + + #region Provider Settings + /// - /// Disables auto select - see . + /// Specifies the provider name to be used when resolving the . /// /// The configuration source. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration WithoutAutoSelect(this IDatabaseBuildConfiguration source) + /// The provider name to resolve. + /// The original configuration, to form a fluent interface. + /// is null or empty. + public static IDatabaseBuildConfiguration UsingProviderName(this IDatabaseBuildConfiguration source, string providerName) { - source.SetSetting(EnableAutoSelect, false); + if (string.IsNullOrEmpty(providerName)) + throw new ArgumentException("Argument is null or empty", nameof(providerName)); + source.SetSetting(ProviderName, providerName); return source; } /// - /// Adds a connection string - see . + /// Specifies the to use. /// + /// + /// This takes precedence over . + /// + /// The provider type, which must implement the the interface. /// The configuration source. - /// The connection string. - /// Thrown when is null or empty. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingConnectionString(this IDatabaseBuildConfiguration source, string connectionString) + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source) + where TProvider : class, IProvider, new() { - if (string.IsNullOrEmpty(connectionString)) - throw new ArgumentException("Argument is null or empty", nameof(connectionString)); - source.SetSetting(ConnectionString, connectionString); + source.SetSetting(Provider, new TProvider()); return source; } -#if !NETSTANDARD /// - /// Adds a connection string name. + /// Specifies the to use, with an accompanying configuration provider action. /// + /// + /// This takes precedence over . + /// + /// The provider type, which must implement the the interface. /// The configuration source. - /// The connection string name. - /// Thrown when is null or empty. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingConnectionStringName(this IDatabaseBuildConfiguration source, string connectionStringName) + /// The action used to configure the provider. + /// The original configuration, to form a fluent interface. + /// is null. + public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, Action configurer) + where TProvider : class, IProvider, new() { - if (string.IsNullOrEmpty(connectionStringName)) - throw new ArgumentException("Argument is null or empty", nameof(connectionStringName)); - source.SetSetting(ConnectionStringName, connectionStringName); + if (configurer == null) + throw new ArgumentNullException(nameof(configurer)); + var provider = new TProvider(); + configurer(provider); + source.SetSetting(Provider, provider); return source; } -#endif /// - /// Adds a provider name string - see . + /// Specifies the to use. /// + /// + /// This takes precedence over . + /// + /// The provider type, which must implement the the interface. /// The configuration source. - /// The provider name. - /// Thrown when is null or empty. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingProviderName(this IDatabaseBuildConfiguration source, string providerName) + /// The database provider. + /// The original configuration, to form a fluent interface. + /// is null. + public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, TProvider provider) + where TProvider : class, IProvider { - if (string.IsNullOrEmpty(providerName)) - throw new ArgumentException("Argument is null or empty", nameof(providerName)); - source.SetSetting(ProviderName, providerName); + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + source.SetSetting(Provider, provider); return source; } /// - /// Specifies a to use. + /// Specifies the to use, with an accompanying configuration provider action. /// - /// The configuration source - /// The connection to use. - /// - public static IDatabaseBuildConfiguration UsingConnection(this IDatabaseBuildConfiguration source, IDbConnection connection) + /// + /// This takes precedence over . + /// + /// The provider type, which must implement the the interface. + /// The configuration source. + /// The action used to configure the provider. + /// The database provider. + /// The original configuration, to form a fluent interface. + /// or is null. + public static IDatabaseBuildConfiguration UsingProvider(this IDatabaseBuildConfiguration source, TProvider provider, Action configurer) + where TProvider : class, IProvider { - if (connection == null) - throw new ArgumentNullException(nameof(connection)); - - source.SetSetting(Connection, connection); + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (configurer == null) + throw new ArgumentNullException(nameof(configurer)); + configurer(provider); + source.SetSetting(Provider, provider); return source; } + #endregion + + #region Mapper Settings + /// - /// Specifies the default mapper to use when no specific mapper has been registered. + /// Specifies the default to use when no specific mapper has been registered. /// + /// The mapper type, which must implement the the interface. /// The configuration source. - /// The mapper to use as the default. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, T mapper) where T : class, IMapper + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source) + where TMapper : class, IMapper, new() { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - source.SetSetting(DefaultMapper, mapper); + source.SetSetting(DefaultMapper, new TMapper()); return source; } /// - /// Specifies the default mapper to use when no specific mapper has been registered. + /// Specifies the default to use when no specific mapper has been registered, with an accompanying + /// configuration mapper action. /// + /// The mapper type, which must implement the the interface. /// The configuration source. - /// The mapper to use as the default. - /// The configure mapper callback. - /// Thrown when is null. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, T mapper, Action configure) where T : class, IMapper + /// The action used to configure the mapper. + /// The original configuration, to form a fluent interface. + /// action is null. + public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, Action configurer) + where TMapper : class, IMapper, new() { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - if (configure == null) - throw new ArgumentNullException(nameof(configure)); - configure(mapper); + if (configurer == null) + throw new ArgumentNullException(nameof(configurer)); + var mapper = new TMapper(); + configurer(mapper); source.SetSetting(DefaultMapper, mapper); return source; } /// - /// Specifies the default mapper to use when no specific mapper has been registered. + /// Specifies the default to use when no specific mapper has been registered. /// + /// The mapper type, which must implement the the interface. /// The configuration source. - /// The mapper type. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source) where T : class, IMapper, new() + /// The default mapper to use when no specific mapper has been registered. + /// The original configuration, to form a fluent interface. + /// is null. + public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, TMapper mapper) + where TMapper : class, IMapper { - source.SetSetting(DefaultMapper, new T()); + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + source.SetSetting(DefaultMapper, mapper); return source; } /// - /// Specifies the default mapper to use when no specific mapper has been registered. + /// Specifies the default to use when no specific mapper has been registered, with an accompanying + /// configuration mapper action. /// + /// The mapper type, which must implement the the interface. /// The configuration source. - /// The configure mapper callback. - /// The mapper type. - /// Thrown when is null. - /// The configuration source, to form a fluent interface. - public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, Action configure) where T : class, IMapper, new() + /// The default mapper to use when no specific mapper has been registered. + /// The action used to configure the mapper. + /// The original configuration, to form a fluent interface. + /// or is null. + public static IDatabaseBuildConfiguration UsingDefaultMapper(this IDatabaseBuildConfiguration source, TMapper mapper, Action configurer) + where TMapper : class, IMapper { - if (configure == null) - throw new ArgumentNullException(nameof(configure)); - - var mapper = new T(); - configure(mapper); + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + if (configurer == null) + throw new ArgumentNullException(nameof(configurer)); + configurer(mapper); source.SetSetting(DefaultMapper, mapper); return source; } + #endregion + + #region Transaction Settings + /// - /// Specifies the transaction isolation level to use. + /// Specifies the transaction isolation level to use. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// The isolation level. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingIsolationLevel(this IDatabaseBuildConfiguration source, IsolationLevel isolationLevel) { source.SetSetting(IsolationLevel, isolationLevel); return source; } + #endregion + + #region Event Settings + /// - /// Specifies an event handler to use when a new transaction has been started. + /// Specifies an event handler to use when a transaction has been started. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingTransactionStarted(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(TransactionStarted, handler); @@ -318,11 +377,11 @@ public static IDatabaseBuildConfiguration UsingTransactionStarted(this IDatabase } /// - /// Specifies an event handler to use when a transaction is about to be rolled back or committed. + /// Specifies an event handler to use when a transaction is about to be rolled back or committed. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingTransactionEnding(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(TransactionEnding, handler); @@ -330,11 +389,11 @@ public static IDatabaseBuildConfiguration UsingTransactionEnding(this IDatabaseB } /// - /// Specifies an event handler to use when a database command is about to be executed. + /// Specifies an event handler to use when a database command is about to be executed. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingCommandExecuting(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(CommandExecuting, handler); @@ -342,11 +401,11 @@ public static IDatabaseBuildConfiguration UsingCommandExecuting(this IDatabaseBu } /// - /// Specifies an event handler to use when a database command has been executed. + /// Specifies an event handler to use when a database command has been executed. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingCommandExecuted(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(CommandExecuted, handler); @@ -354,23 +413,23 @@ public static IDatabaseBuildConfiguration UsingCommandExecuted(this IDatabaseBui } /// - /// Specifies an event handler to use before a connection is opened. + /// Specifies an event handler to use when a connection is about to be opened. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingConnectionOpening(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(ConnectionOpening, handler); return source; } - + /// - /// Specifies an event handler to use when a database connection has been opened. + /// Specifies an event handler to use when a database connection has been opened. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingConnectionOpened(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(ConnectionOpened, handler); @@ -378,11 +437,11 @@ public static IDatabaseBuildConfiguration UsingConnectionOpened(this IDatabaseBu } /// - /// Specifies an event handler to use when a database connection is about to be closed. + /// Specifies an event handler to use when a database connection is about to be closed. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingConnectionClosing(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(ConnectionClosing, handler); @@ -390,19 +449,23 @@ public static IDatabaseBuildConfiguration UsingConnectionClosing(this IDatabaseB } /// - /// Specifies an event handler to use when a database exception has been thrown. + /// Specifies an event handler to use when a database exception has been thrown. /// /// The configuration source. - /// - /// The configuration source, to form a fluent interface. + /// A callback function for handling events. + /// The original configuration, to form a fluent interface. public static IDatabaseBuildConfiguration UsingExceptionThrown(this IDatabaseBuildConfiguration source, EventHandler handler) { source.SetSetting(ExceptionThrown, handler); return source; } + #endregion + + #region Finalize Fluent Configuration + /// - /// Creates an instance of PetaPoco using the specified . + /// Creates an instance of PetaPoco using the specified . /// /// The configuration source used to create and configure an instance of PetaPoco. /// An instance of PetaPoco. @@ -410,5 +473,7 @@ public static IDatabase Create(this IDatabaseBuildConfiguration source) { return new Database(source); } + + #endregion } -} \ No newline at end of file +} diff --git a/PetaPoco/EventArgs.cs b/PetaPoco/EventArgs.cs index 3aa1e24e..de92ac98 100644 --- a/PetaPoco/EventArgs.cs +++ b/PetaPoco/EventArgs.cs @@ -3,50 +3,77 @@ namespace PetaPoco { + /// + /// Event arguments for an event. + /// public class DbTransactionEventArgs : EventArgs { + /// + /// Gets the database transaction associated with the event. + /// public IDbTransaction Transaction { get; } - public DbTransactionEventArgs(IDbTransaction transaction) - { - Transaction = transaction; - } + /// + /// Initializes a new instance of the class. + /// + /// The database transaction associated with the event. + public DbTransactionEventArgs(IDbTransaction transaction) => Transaction = transaction; } + /// + /// Event arguments for an event. + /// public class DbCommandEventArgs : EventArgs { + /// + /// Gets the database command associated with the event. + /// public IDbCommand Command { get; } - public DbCommandEventArgs(IDbCommand cmd) - { - Command = cmd; - } + /// + /// Initializes a new instance of the class. + /// + /// The database command associated with the event. + public DbCommandEventArgs(IDbCommand command) => Command = command; } + /// + /// Event arguments for an event. + /// public class DbConnectionEventArgs : EventArgs { + /// + /// Gets or sets the database connection associated with the event. + /// public IDbConnection Connection { get; set; } - public DbConnectionEventArgs(IDbConnection connection) - { - Connection = connection; - } + /// + /// Initializes a new instance of the class. + /// + /// The database connection associated with the event. + public DbConnectionEventArgs(IDbConnection connection) => Connection = connection; } - /// + /// + /// Event arguments for an event. + /// public class ExceptionEventArgs : EventArgs { /// - /// A flag which specifies whether the exception should be raised or ignored. + /// Gets or sets a flag specifying whether the exception should be raised or ignored. + /// Default is . /// public bool Raise { get; set; } = true; /// - /// The exception which was caught. + /// Gets the caught exception. /// public Exception Exception { get; } - public ExceptionEventArgs(Exception ex) - => Exception = ex; + /// + /// Initializes a new instance of the class. + /// + /// The caught exception. + public ExceptionEventArgs(Exception ex) => Exception = ex; } -} \ No newline at end of file +} diff --git a/PetaPoco/IAlterPoco.cs b/PetaPoco/IAlterPoco.cs index 26a51a87..b46305bf 100644 --- a/PetaPoco/IAlterPoco.cs +++ b/PetaPoco/IAlterPoco.cs @@ -2,232 +2,233 @@ namespace PetaPoco { + /// + /// Specifies a set of methods for performing SQL operations on POCOs such as Insert, Update, Delete, and Save. + /// public interface IAlterPoco { + #region Insert + /// - /// Performs an SQL Insert. + /// Inserts a new record and returns the primary key of the newly inserted record. /// - /// The name of the table to insert into. - /// The POCO object that specifies the column values to be inserted. - /// The auto allocated primary key of the new record, or null for non-auto-increment tables. + /// + object Insert(object poco); + + /// + /// If a mapped primary key column is auto-incrementing and is , the + /// primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// object Insert(string tableName, object poco); - /// - /// Performs an SQL Insert. - /// - /// The name of the table to insert into. - /// The name of the primary key column of the table. - /// The POCO object that specifies the column values to be inserted. - /// The auto allocated primary key of the new record, or null for non-auto-increment tables. + /// + /// If represents an auto-incrementing column and is , the primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// object Insert(string tableName, string primaryKeyName, object poco); /// - /// Performs an SQL Insert. + /// Inserts a new record into the specified table and returns the primary key of the newly inserted record. /// - /// The name of the table to insert into. - /// The name of the primary key column of the table. - /// True if the primary key is automatically allocated by the DB. - /// The POCO object that specifies the column values to be inserted. - /// The auto allocated primary key of the new record, or null for non-auto-increment tables. /// - /// Inserts a POCO into a table. If the POCO has a property with the same name - /// as the primary key, the id of the new record is assigned to it. Either way, - /// the new id is returned. + /// If is , the primary key property of the POCO will be updated with the new + /// record's auto-incremented ID. /// + /// The name of the table where the record will be inserted. + /// The table's primary key column name. + /// Specifies whether the primary key column in the database is auto-incrementing. + /// The POCO instance to insert. + /// The primary key of the new record if the table has a primary key column; otherwise, . object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco); + #endregion + + #region Update + /// - /// Performs an SQL Insert. + /// Updates a record and returns the number of rows affected by the update operation. /// - /// The POCO object that specifies the column values to be inserted. - /// The auto allocated primary key of the new record, or null for non-auto-increment tables. - /// - /// The name of the table, its primary key and whether it's an auto-allocated primary key are retrieved - /// from the POCO's attributes - /// - object Insert(object poco); + /// + int Update(object poco); /// - /// Performs an SQL update + /// Updates the specified columns of a record and returns the number of rows affected by the update operation. /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The number of affected records - int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + /// + int Update(object poco, IEnumerable columns); /// - /// Performs an SQL update + /// Updates a record with the given ID and returns the number of rows affected by the update operation. /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); + /// + int Update(object poco, object primaryKeyValue); /// - /// Performs an SQL update + /// Updates the specified columns of a record with the given ID and returns the number of rows affected by the update operation. /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The number of affected rows - int Update(string tableName, string primaryKeyName, object poco); + /// + int Update(object poco, object primaryKeyValue, IEnumerable columns); /// - /// Performs an SQL update + /// Updates a record in the provided table and returns the number of rows affected by the update operation. /// - /// The name of the table to update - /// The name of the primary key column of the table - /// The POCO object that specifies the column values to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns); + /// + int Update(string tableName, string primaryKeyName, object poco); /// - /// Performs an SQL update + /// Updates the specified columns of a record in the provided table and returns the number of rows affected by the update operation. /// - /// The POCO object that specifies the column values to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - int Update(object poco, IEnumerable columns); + /// + int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns); /// - /// Performs an SQL update + /// Updates a record with the given ID in the provided table and returns the number of rows affected by the update operation. /// - /// The POCO object that specifies the column values to be updated - /// The number of affected rows - int Update(object poco); + /// + int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue); /// - /// Performs an SQL update + /// Updates the specified columns of a record with the given ID in the provided table and returns the number of rows affected by the + /// update operation. /// - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The number of affected rows - int Update(object poco, object primaryKeyValue); + /// The name of the table to update. + /// The table's primary key column name. + /// The POCO instance containing the column values to update. + /// The primary key value identifying the record to update. + /// A list of column names to update, or to update all columns. + /// The number of rows affected by the update operation. + int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); /// - /// Performs an SQL update + /// Executes an SQL update and returns the number of rows affected by the update operation. /// - /// The POCO object that specifies the column values to be updated - /// The primary key of the record to be updated - /// The column names of the columns to be updated, or null for all - /// The number of affected rows - int Update(object poco, object primaryKeyValue, IEnumerable columns); + /// The POCO type associated with the table to update. + /// An SQL builder instance representing the condition portion of the WHERE clause identifying the row to update + /// (everything after UPDATE tablename) and its parameters. + /// The number of rows affected by the update operation. + int Update(Sql sql); /// - /// Performs an SQL update + /// Executes an SQL update and returns the number of rows affected by the update operation. /// - /// The POCO class whose attributes specify the name of the table to update - /// The SQL update and condition clause (ie: everything after "UPDATE tablename" - /// Arguments to any embedded parameters in the SQL - /// The number of affected rows + /// The POCO type associated with the table to update. + /// The SQL string representing the condition portion of the WHERE clause identifying the row to update + /// (everything after UPDATE tablename). + /// The parameters to embed in the SQL string. + /// The number of rows affected by the update operation. int Update(string sql, params object[] args); + #endregion + + #region Delete + /// - /// Performs an SQL update + /// Deletes a record and returns the number of rows affected by the update operation. /// - /// The POCO class whose attributes specify the name of the table to update - /// - /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE - /// tablename" - /// - /// The number of affected rows - int Update(Sql sql); + /// The POCO instance representing the record to delete. + /// The number of rows affected by the delete operation. + int Delete(object poco); /// - /// Performs an SQL Delete + /// Deletes a record in the provided table and returns the number of rows affected by the update operation. /// - /// The name of the table to delete from - /// The name of the primary key column - /// The POCO object whose primary key value will be used to delete the row - /// The number of rows affected + /// The name of the table containing the record to delete. + /// The table's primary key column name. + /// The POCO instance representing the record to delete. + /// The number of rows affected by the delete operation. int Delete(string tableName, string primaryKeyName, object poco); /// - /// Performs an SQL Delete + /// Deletes a record with the given ID in the provided table and returns the number of rows affected by the update operation. /// - /// The name of the table to delete from - /// The name of the primary key column - /// - /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied - /// primary key value) - /// - /// - /// The value of the primary key identifing the record to be deleted (or null, or get this - /// value from the POCO instance) - /// - /// The number of rows affected + /// The name of the table containing the record to delete. + /// The table's primary key column name. + /// The POCO instance representing the record to delete, or to use the provided . + /// The primary key value identifying the record to delete, used if is . + /// The number of rows affected by the delete operation. int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue); /// - /// Performs an SQL Delete + /// Deletes a record and returns the number of rows affected by the update operation. /// - /// The POCO object specifying the table name and primary key value of the row to be deleted - /// The number of rows affected - int Delete(object poco); + /// The POCO type associated with the table to delete. + /// The primary key value, or a POCO containing an assigned primary key value. + /// The number of rows affected by the delete operation. + int Delete(object pocoOrPrimaryKeyValue); /// - /// Performs an SQL Delete + /// Executes an SQL delete and returns the number of rows affected by the delete operation. /// - /// The POCO class whose attributes identify the table and primary key to be used in the delete - /// The value of the primary key of the row to delete - /// - int Delete(object pocoOrPrimaryKey); + /// The POCO type associated with the table to delete. + /// An SQL builder instance representing the condition portion of the WHERE clause identifying the row to delete + /// (everything after DELETE FROM tablename) and its parameters. + /// The number of rows affected by the delete operation. + int Delete(Sql sql); /// - /// Performs an SQL Delete + /// Executes an SQL delete and returns the number of rows affected by the delete operation. /// - /// The POCO class whose attributes specify the name of the table to delete from - /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" - /// Arguments to any embedded parameters in the SQL - /// The number of affected rows + /// The POCO type associated with the table to delete. + /// The SQL string representing the condition portion of the WHERE clause identifying the row to delete + /// (everything after DELETE FROM tablename). + /// The parameters to embed in the SQL string. + /// The number of rows affected by the delete operation. int Delete(string sql, params object[] args); - /// - /// Performs an SQL Delete - /// - /// The POCO class whose attributes specify the name of the table to delete from - /// - /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: - /// everything after "UPDATE tablename" - /// - /// The number of affected rows - int Delete(Sql sql); + #endregion + + #region IsNew + + /// + /// A POCO instance is considered "new" if the property that maps to the associated table's primary key + /// column contains a default value. + /// + /// + bool IsNew(object poco); /// - /// Check if a poco represents a new row + /// Determines whether the specified POCO represents a new record that has not yet been saved to the database. /// - /// The name of the primary key column - /// The object instance whose "newness" is to be tested - /// True if the POCO represents a record already in the database - /// This method simply tests if the POCO's primary key column property has a non-default value. + /// + /// A POCO instance is considered "new" if the property that maps to the associated table's provided column + /// name contains a default value. + /// + /// The table's primary key column name. + /// The POCO instance to check. + /// if the POCO represents a new record; otherwise, . bool IsNew(string primaryKeyName, object poco); - /// - /// Check if a poco represents a new row - /// - /// The object instance whose "newness" is to be tested - /// True if the POCO represents a record already in the database - /// This method simply tests if the POCO's primary key column property has a non-default value. - bool IsNew(object poco); + #endregion + + #region Save + + /// + /// Performs an operation if the POCO is new (as determined by ), and an + /// operation otherwise. + /// If an Insert operation is performed, and a mapped primary key column is auto-incrementing, the primary key property of the + /// POCO will be updated with the new record's auto-incremented ID. + /// + /// + void Save(object poco); /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// Saves the specified POCO to the database by performing either an insert or an update operation, as appropriate. /// - /// The name of the table to be updated - /// The name of the primary key column - /// The POCO object to be saved + /// + /// Performs an operation if the POCO is new (as determined by ), and an operation otherwise. + /// If an Insert operation is performed, and represents an auto-incrementing column, the + /// primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// The name of the table where the POCO will be saved. + /// The table's primary key column name. + /// The POCO instance to save. void Save(string tableName, string primaryKeyName, object poco); - /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update - /// - /// The POCO object to be saved - void Save(object poco); + #endregion } -} \ No newline at end of file +} diff --git a/PetaPoco/IAlterPocoAsync.cs b/PetaPoco/IAlterPocoAsync.cs index 591d5bb7..932d0fbc 100644 --- a/PetaPoco/IAlterPocoAsync.cs +++ b/PetaPoco/IAlterPocoAsync.cs @@ -5,228 +5,315 @@ namespace PetaPoco { #if ASYNC + /// + /// Specifies a set of methods for asynchronously performing SQL operations on POCOs such as Insert, Update, Delete, and Save. + /// public interface IAlterPocoAsync { - /// - /// Async version of . - /// - Task InsertAsync(string tableName, object poco); + #region InsertAsync - /// - /// Async version of . - /// - Task InsertAsync(CancellationToken cancellationToken, string tableName, object poco); + /// + Task InsertAsync(object poco); - /// - /// Async version of . - /// + /// + Task InsertAsync(string tableName, object poco); + + /// Task InsertAsync(string tableName, string primaryKeyName, object poco); - /// - /// Async version of . - /// - Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); + /// + Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco); /// - /// Async version of . + /// Asynchronously inserts a new record and returns the primary key of the newly inserted record. /// - Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco); + /// + Task InsertAsync(CancellationToken cancellationToken, object poco); + + /// + /// If a mapped primary key column is auto-incrementing and is , the + /// primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// + Task InsertAsync(CancellationToken cancellationToken, string tableName, object poco); + + /// + /// If represents an auto-incrementing column and is , the primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// + Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); /// - /// Async version of . - /// + /// Asynchronously inserts a new record into the specified table and returns the primary key of the newly inserted record. + /// + /// + /// If is , the primary key property of the POCO will be updated with the new + /// record's auto-incremented ID. + /// + /// A cancellation token that can be used to cancel the operation. + /// The name of the table where the record will be inserted. + /// The table's primary key column name. + /// Specifies whether the primary key column in the database is auto-incrementing. + /// The POCO instance to insert. + /// + /// A task that represents the asynchronous operation. The task result contains the primary key of the new record if the table has a + /// primary key column; otherwise, . + /// Task InsertAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, bool autoIncrement, object poco); - /// - /// Async version of . - /// - Task InsertAsync(object poco); + #endregion - /// - /// Async version of . - /// - Task InsertAsync(CancellationToken cancellationToken, object poco); + #region UpdateAsync - /// - /// Async version of . - /// - Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + /// + Task UpdateAsync(object poco); - /// - /// Async version of . - /// - Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue); + /// + Task UpdateAsync(object poco, IEnumerable columns); - /// - /// Async version of . - /// - Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); + /// + Task UpdateAsync(object poco, object primaryKeyValue); - /// - /// Async version of . - /// - Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue, - IEnumerable columns); + /// + Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns); - /// - /// Async version of . - /// + /// Task UpdateAsync(string tableName, string primaryKeyName, object poco); - /// - /// Async version of . - /// - Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); - - /// - /// Async version of . - /// + /// Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns); - /// - /// Async version of . - /// - Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, IEnumerable columns); + /// + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); - /// - /// Async version of . - /// - Task UpdateAsync(object poco, IEnumerable columns); + /// + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); - /// - /// Async version of . - /// - Task UpdateAsync(CancellationToken cancellationToken, object poco, IEnumerable columns); + /// + Task UpdateAsync(Sql sql); - /// - /// Async version of . - /// - Task UpdateAsync(object poco); + /// + Task UpdateAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously updates a record and returns the number of rows affected by the update operation. /// + /// Task UpdateAsync(CancellationToken cancellationToken, object poco); /// - /// Async version of . + /// Asynchronously updates the specified columns of a record and returns the number of rows affected by the update operation. /// - Task UpdateAsync(object poco, object primaryKeyValue); + /// + Task UpdateAsync(CancellationToken cancellationToken, object poco, IEnumerable columns); /// - /// Async version of . + /// Asynchronously updates a record with the given ID and returns the number of rows affected by the update operation. /// + /// Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue); /// - /// Async version of . + /// Asynchronously updates the specified columns of a record with the given ID and returns the number of rows affected by the update + /// operation. /// - Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns); + /// + Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue, IEnumerable columns); /// - /// Async version of . + /// Asynchronously a record in the provided table and returns the number of rows affected by the update operation. /// - Task UpdateAsync(CancellationToken cancellationToken, object poco, object primaryKeyValue, IEnumerable columns); + /// + Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); /// - /// Async version of . + /// Asynchronously updates the specified columns of a record in the provided table and returns the number of rows affected by the + /// update operation. /// - Task UpdateAsync(string sql, params object[] args); + /// + Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, IEnumerable columns); /// - /// Async version of . + /// Asynchronously a record with the given ID in the provided table and returns the number of rows affected by the update operation. /// - Task UpdateAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// + Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue); /// - /// Async version of . + /// Asynchronously updates the specified columns of a record with the given ID in the provided table and returns the number of rows + /// affected by the update operation. /// - Task UpdateAsync(Sql sql); + /// A cancellation token that can be used to cancel the operation. + /// The name of the table to update. + /// The table's primary key column name. + /// The POCO instance containing the column values to update. + /// The primary key value identifying the record to update. + /// A list of column names to update, or to update all columns. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the update operation. + /// + Task UpdateAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); /// - /// Async version of . + /// Asynchronously executes an SQL update and returns the number of rows affected by the update operation. /// + /// The POCO type associated with the table to update. + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the condition portion of the WHERE clause identifying the row to update + /// (everything after UPDATE tablename) and its parameters. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the update operation. + /// Task UpdateAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously executes an SQL update and returns the number of rows affected by the update operation. /// - Task DeleteAsync(string tableName, string primaryKeyName, object poco); + /// The POCO type associated with the table to update. + /// A cancellation token that can be used to cancel the operation. + /// The SQL string representing the condition portion of the WHERE clause identifying the row to update + /// (everything after UPDATE tablename). + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the update operation. + /// + Task UpdateAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); + #endregion - /// - /// Async version of . - /// + #region DeleteAsync + + /// + Task DeleteAsync(object poco); + + /// + Task DeleteAsync(string tableName, string primaryKeyName, object poco); + + /// Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); - /// - /// Async version of . - /// - Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue); + /// + Task DeleteAsync(object pocoOrPrimaryKeyValue); - /// - /// Async version of . - /// - Task DeleteAsync(object poco); + /// + Task DeleteAsync(Sql sql); + + /// + Task DeleteAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously deletes a record and returns the number of rows affected by the update operation. /// + /// A cancellation token that can be used to cancel the operation. + /// The POCO instance representing the record to delete. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// Task DeleteAsync(CancellationToken cancellationToken, object poco); /// - /// Async version of . + /// Asynchronously deletes a record from the provided table and returns the number of rows affected by the delete operation. /// - Task DeleteAsync(object pocoOrPrimaryKey); + /// A cancellation token that can be used to cancel the operation. + /// The name of the table containing the record to delete. + /// The table's primary key column name. + /// The POCO instance representing the record to delete. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// + Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); /// - /// Async version of . + /// Asynchronously deletes a record with the given ID from the provided table and returns the number of rows affected by the delete + /// operation. /// - Task DeleteAsync(CancellationToken cancellationToken, object pocoOrPrimaryKey); + /// A cancellation token that can be used to cancel the operation. + /// The name of the table containing the record to delete. + /// The table's primary key column name. + /// The POCO instance representing the record to delete, or to use the provided . + /// The primary key value identifying the record to delete, used if is . + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// + Task DeleteAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco, object primaryKeyValue); /// - /// Async version of . + /// Asynchronously deletes a record and returns the number of rows affected by the update operation. /// - Task DeleteAsync(string sql, params object[] args); + /// The POCO type associated with the table to delete. + /// A cancellation token that can be used to cancel the operation. + /// The primary key value, or a POCO containing an assigned primary key value. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// + Task DeleteAsync(CancellationToken cancellationToken, object pocoOrPrimaryKeyValue); /// - /// Async version of . + /// Asynchronously executes an SQL delete and returns the number of rows affected by the delete operation. /// - Task DeleteAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// The POCO type associated with the table to delete. + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the condition portion of the WHERE clause identifying the row to delete + /// (everything after DELETE FROM tablename) and its parameters. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// + Task DeleteAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously executes an SQL delete and returns the number of rows affected by the delete operation. /// - Task DeleteAsync(Sql sql); + /// The POCO type associated with the table to delete. + /// A cancellation token that can be used to cancel the operation. + /// The SQL string representing the condition portion of the WHERE clause identifying the row to delete + /// (everything after DELETE FROM tablename). + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected by the delete operation. + /// + Task DeleteAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task DeleteAsync(CancellationToken cancellationToken, Sql sql); + #endregion - /// - /// Async version of . - /// + #region SaveAsync + + /// + Task SaveAsync(object poco); + + /// Task SaveAsync(string tableName, string primaryKeyName, object poco); - /// - /// Async version of . - /// - Task SaveAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); + /// + /// Performs an operation if the POCO is new (as determined by ), and an operation otherwise. + /// If an Insert operation is performed, and a mapped primary key column is auto-incrementing, the primary key property of the + /// POCO will be updated with the new record's auto-incremented ID. + /// + /// + Task SaveAsync(CancellationToken cancellationToken, object poco); /// - /// Async version of . - /// - Task SaveAsync(object poco); + /// Asynchronously saves the specified POCO to the database by performing either an insert or an update operation, as appropriate. + /// + /// + /// Performs an operation if the POCO is new (as determined by ), and an operation otherwise. + /// If an Insert operation is performed, and represents an auto-incrementing column, the + /// primary key property of the POCO will be updated with the new record's auto-incremented ID. + /// + /// A cancellation token that can be used to cancel the operation. + /// The name of the table where the record will be saved. + /// The table's primary key column name. + /// The POCO instance to save. + /// + /// A task that represents the asynchronous operation. + /// + Task SaveAsync(CancellationToken cancellationToken, string tableName, string primaryKeyName, object poco); - /// - /// Async version of . - /// - Task SaveAsync(CancellationToken cancellationToken, object poco); + #endregion } #endif } diff --git a/PetaPoco/IAsyncReader.cs b/PetaPoco/IAsyncReader.cs index 4082aec3..ad723779 100644 --- a/PetaPoco/IAsyncReader.cs +++ b/PetaPoco/IAsyncReader.cs @@ -3,10 +3,26 @@ namespace PetaPoco { + /// + /// Specifies a set of methods for asynchronously reading data as POCO objects from a data source. + /// + /// The POCO type representing a single result record. public interface IAsyncReader : IDisposable { + /// + /// Gets the current POCO object of type that the reader is positioned at. + /// T Poco { get; } + /// + /// Asynchronously reads the next row from the data source. + /// + /// + /// A task that represents the asynchronous operation. The task result contains if more records exist, + /// otherwise . + /// Task ReadAsync(); + + // TODO: Missing overload: `Task ReadAsync(CancellationToken)` } -} \ No newline at end of file +} diff --git a/PetaPoco/IBuildConfigurationSettings.cs b/PetaPoco/IBuildConfigurationSettings.cs index b7b34e7b..240cd791 100644 --- a/PetaPoco/IBuildConfigurationSettings.cs +++ b/PetaPoco/IBuildConfigurationSettings.cs @@ -1,26 +1,27 @@ -using System; +using System; namespace PetaPoco { /// - /// Represents the build configuration settings contract. + /// Represents the build configuration settings contract. /// public interface IBuildConfigurationSettings { /// - /// Sets the setting against the specified key. + /// Sets a setting with a specified key that can be used for future retrieval of the setting's value. /// /// The setting's key. /// The setting's value. void SetSetting(string key, object value); /// - /// Tries to get the setting and calls the to set the value if found. + /// Attempts to locate a setting of type using the specified , and invokes with the setting's value if found. /// - /// The setting type. + /// The type of the setting's value object. /// The setting's key. - /// The get setting callback. - /// The on fail callback, called when no setting can be gotten. - void TryGetSetting(string key, Action getSetting, Action onFail = null); + /// The action to invoke with the setting's value when the setting is found. + /// An optional action to invoke if the setting cannot be found. + void TryGetSetting(string key, Action onGetAction, Action onFailAction = null); } -} \ No newline at end of file +} diff --git a/PetaPoco/IConnection.cs b/PetaPoco/IConnection.cs index 96459c27..b96754ec 100644 --- a/PetaPoco/IConnection.cs +++ b/PetaPoco/IConnection.cs @@ -1,57 +1,58 @@ -using System.Data; +using System.Data; using System.Threading.Tasks; namespace PetaPoco { + /// + /// Defines methods and properties for managing database connections. This includes opening and closing shared connections, and + /// accessing the currently open connection. + /// public interface IConnection { - /// - /// When set to true the first opened connection is kept alive until - /// or is called. - /// - /// - bool KeepConnectionAlive { get; set; } + /// + /// Gets or sets the connection reuse policy for the shared connection or instance. + /// + /// + /// When set to the first opened connection is kept alive until is called + /// or the is disposed. + /// + bool KeepConnectionAlive { get; set; } /// - /// Provides access to the currently open shared connection. + /// Gets the currently open shared connection, or if there is no open connection. /// - /// - /// The currently open connection, or Null. - /// - /// - /// - /// IDbConnection Connection { get; } /// - /// Opens a connection that will be used for all subsequent queries. + /// Opens a connection that will be used for all subsequent queries. /// /// - /// Calls to / are reference - /// counted and should be balanced + /// Calls to and are reference counted and must be balanced. /// - /// - /// - /// void OpenSharedConnection(); -#if !NET40 +#if ASYNC /// - /// The async version of . + /// Asynchronously opens a connection that will be used for all subsequent queries. /// + /// + /// Calls to and are reference counted and must be + /// balanced. + /// + /// + /// A task that represents the asynchronous operation. + /// Task OpenSharedConnectionAsync(); + + // TODO: Missing overload: `Task OpenSharedConnectionAsync(CancellationToken)` #endif /// - /// Releases the shared connection. + /// Releases the shared connection. /// /// - /// Calls to / are reference - /// counted and should be balanced + /// Calls to and are reference counted and must be balanced. /// - /// - /// - /// void CloseSharedConnection(); } } diff --git a/PetaPoco/IDatabase.cs b/PetaPoco/IDatabase.cs index fb6db559..2fe65448 100644 --- a/PetaPoco/IDatabase.cs +++ b/PetaPoco/IDatabase.cs @@ -7,7 +7,7 @@ namespace PetaPoco { /// - /// Represents the core functionality of PetaPoco. + /// Represents the core functionality of PetaPoco. /// public interface IDatabase : IDisposable, IQuery, IAlterPoco, IExecute, ITransactionAccessor, IStoredProc, IConnection #if ASYNC @@ -15,191 +15,147 @@ public interface IDatabase : IDisposable, IQuery, IAlterPoco, IExecute, ITransac #endif { /// - /// Gets the default mapper. (Default is ) + /// Gets the default mapper. /// - /// - /// The default mapper. - /// IMapper DefaultMapper { get; } /// - /// Gets the SQL of the last executed statement + /// Gets the SQL of the last executed command. /// - /// - /// The last executed SQL. - /// string LastSQL { get; } /// - /// Gets the arguments to the last execute statement + /// Gets an array containing the arguments of the last executed command. /// - /// - /// The last executed SQL arguments. - /// object[] LastArgs { get; } /// - /// Gets a formatted string describing the last executed SQL statement and its argument values + /// Gets a formatted string describing the last executed command and its argument values. /// - /// - /// The formatted string. - /// string LastCommand { get; } /// - /// Gets or sets the enable auto select. (Default is True) + /// Gets or sets a value indicating whether automatic generation of the SELECT and WHERE parts of an SQL statement is + /// enabled when not explicitly provided by the caller. /// - /// - /// When set to true, PetaPoco will automatically create the "SELECT columns" section of the query for any query which - /// is found to require them. - /// - /// - /// True, if auto select is enabled; else, false. - /// bool EnableAutoSelect { get; set; } /// - /// Gets the flag for whether named params are enabled. (Default is True) + /// Gets or sets a value indicating whether named parameters are enabled. /// - /// - /// When set to true, parameters can be named ?myparam and populated from properties of the passed-in argument values. - /// - /// - /// True, if named parameters are enabled; else, false. - /// bool EnableNamedParams { get; set; } /// - /// Sets the timeout value, in seconds, which PetaPoco applies to all . - /// (Default is 0) + /// Gets or sets the wait time (in seconds) before terminating the attempt to execute a command. /// - /// - /// If the current value is zero PetaPoco will not set the command timeout, and therefore the .NET default (30 seconds) - /// will be in effect. - /// - /// - /// The current command timeout. - /// int CommandTimeout { get; set; } /// - /// Sets the timeout value for the next (and only next) SQL statement. + /// Gets or sets a one-time timeout value to temporarily override for the next command execution. /// - /// - /// This is a one-time settings, which after use, will return the setting. - /// - /// - /// The one time command timeout. - /// int OneTimeCommandTimeout { get; set; } /// - /// Gets the current . + /// Gets the underlying database Provider. /// - /// - /// The current database provider. - /// IProvider Provider { get; } /// - /// Gets the connection string. + /// Gets the connection string. /// - /// - /// The connection string. - /// string ConnectionString { get; } + #region Transaction Interface + /// - /// Gets or sets the transaction isolation level. + /// Gets or sets the transaction isolation level. /// - /// - /// When value is null, the underlying provider's default isolation level is used. - /// IsolationLevel? IsolationLevel { get; set; } /// - /// Starts or continues a transaction. - /// - /// An ITransaction reference that must be Completed or disposed - /// - /// This method makes management of calls to Begin/End/CompleteTransaction easier. - /// The usage pattern for this should be: - /// using (var tx = db.GetTransaction()) - /// { - /// // Do stuff - /// db.Update(...); - /// // Mark the transaction as complete - /// tx.Complete(); - /// } - /// Transactions can be nested but they must all be completed otherwise the entire - /// transaction is aborted. - /// + /// Begins or continues a transaction. + /// ITransaction GetTransaction(); /// - /// Starts a transaction scope, see GetTransaction() for recommended usage + /// Begins a transaction scope. /// void BeginTransaction(); +#if ASYNC + /// + /// Asynchronously begins a transaction scope. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task BeginTransactionAsync(); + + /// + /// Asynchronously begins a transaction scope. + /// + /// A cancellation token that can be used to cancel the operation. + /// + /// A task that represents the asynchronous operation. + /// + Task BeginTransactionAsync(CancellationToken cancellationToken); +#endif + /// - /// Aborts the entire outermost transaction scope + /// Aborts the entire outermost transaction scope. /// - /// - /// Called automatically by Transaction.Dispose() - /// if the transaction wasn't completed. - /// void AbortTransaction(); +#if ASYNC + // TODO: Missing: `AbortTransactionAsync()` +#endif + /// - /// Marks the current transaction scope as complete. + /// Marks the current transaction scope as complete. /// void CompleteTransaction(); +#if ASYNC + // TODO: Missing: `CompleteTransactionAsync()` +#endif + /// - /// Occurs when a new transaction has started. + /// Occurs when a new transaction has started. /// event EventHandler TransactionStarted; /// - /// Occurs when a transaction is about to be rolled back or committed. + /// Occurs when a transaction is about to be rolled back or committed. /// event EventHandler TransactionEnding; + #endregion + /// - /// Occurs when a database command is about to be executed. + /// Occurs when a database command is about to be executed. /// event EventHandler CommandExecuting; /// - /// Occurs when a database command has been executed. + /// Occurs when a database command has been executed. /// event EventHandler CommandExecuted; - /// - /// Occurs when a database connection is about to be closed. - /// - event EventHandler ConnectionClosing; + // TODO: Missing: `event EventHandler ConnectionOpening` /// - /// Occurs when a database connection has been opened. + /// Occurs when a database connection has been opened. /// event EventHandler ConnectionOpened; /// - /// Occurs when a database exception has been thrown. - /// - event EventHandler ExceptionThrown; - -#if ASYNC - /// - /// Async version of . + /// Occurs when a database connection is about to be closed. /// - Task BeginTransactionAsync(); + event EventHandler ConnectionClosing; /// - /// Async version of . + /// Occurs when a database exception has been thrown. /// - Task BeginTransactionAsync(CancellationToken cancellationToken); -#endif + event EventHandler ExceptionThrown; } -} \ No newline at end of file +} diff --git a/PetaPoco/IDatabaseBuildConfiguration.cs b/PetaPoco/IDatabaseBuildConfiguration.cs index 0c371f36..3ce7cf0b 100644 --- a/PetaPoco/IDatabaseBuildConfiguration.cs +++ b/PetaPoco/IDatabaseBuildConfiguration.cs @@ -1,9 +1,9 @@ namespace PetaPoco { /// - /// A helper interface which enables fluent configuration extension methods. + /// A helper interface which enables fluent configuration extension methods. /// public interface IDatabaseBuildConfiguration { } -} \ No newline at end of file +} diff --git a/PetaPoco/IExecute.cs b/PetaPoco/IExecute.cs index f81729fd..7ee07091 100644 --- a/PetaPoco/IExecute.cs +++ b/PetaPoco/IExecute.cs @@ -1,37 +1,40 @@ namespace PetaPoco { + /// + /// Specifies a set of methods for executing SQL non-query commands and scalar queries. + /// public interface IExecute { /// - /// Executes a non-query command + /// Executes a non-query command and returns the number of rows affected. /// - /// The SQL statement to execute - /// Arguments to any embedded parameters in the SQL - /// The number of rows affected - int Execute(string sql, params object[] args); + /// An SQL builder instance representing the SQL statement and its parameters. + /// The number of rows affected. + int Execute(Sql sql); /// - /// Executes a non-query command + /// Executes a non-query command and returns the number of rows affected. /// - /// An SQL builder object representing the query and its arguments - /// The number of rows affected - int Execute(Sql sql); + /// The SQL string. + /// The parameters to embed in the SQL string. + /// The number of rows affected. + int Execute(string sql, params object[] args); /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a scalar command and returns the first column of the first row in the result set. /// - /// The type that the result value should be cast to - /// The SQL query to execute - /// Arguments to any embedded parameters in the SQL - /// The scalar value cast to T - T ExecuteScalar(string sql, params object[] args); + /// The type of the result value. + /// An SQL builder instance representing the SQL statement and its parameters. + /// The scalar result value of type . + T ExecuteScalar(Sql sql); /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a scalar command and returns the first column of the first row in the result set. /// - /// The type that the result value should be cast to - /// An SQL builder object representing the query and its arguments - /// The scalar value cast to T - T ExecuteScalar(Sql sql); + /// The type of the result value. + /// The SQL query string. + /// The parameters to embed in the SQL query string. + /// The scalar result value of type . + T ExecuteScalar(string sql, params object[] args); } -} \ No newline at end of file +} diff --git a/PetaPoco/IExecuteAsync.cs b/PetaPoco/IExecuteAsync.cs index 4ea291a2..380f71f2 100644 --- a/PetaPoco/IExecuteAsync.cs +++ b/PetaPoco/IExecuteAsync.cs @@ -4,47 +4,66 @@ namespace PetaPoco { #if ASYNC + /// + /// Specifies a set of methods for asynchronously executing SQL non-query commands and scalar queries. + /// public interface IExecuteAsync { - /// - /// Async version of . - /// + /// + Task ExecuteAsync(Sql sql); + + /// Task ExecuteAsync(string sql, params object[] args); - /// - /// Async version of . - /// - Task ExecuteAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// + Task ExecuteScalarAsync(Sql sql); - /// - /// Async version of . - /// - Task ExecuteAsync(Sql sql); + /// + Task ExecuteScalarAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes a non-query command and returns the number of rows affected by the operation. /// + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL statement and its parameters. + /// + /// A task that represents the asynchronous operation. The task result is the number of rows affected by the operation. + /// Task ExecuteAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . - /// - Task ExecuteScalarAsync(string sql, params object[] args); - - /// - /// Async version of . + /// Asynchronously executes a non-query command and returns the number of rows affected by the operation. /// - Task ExecuteScalarAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// A cancellation token that can be used to cancel the operation. + /// The SQL string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result is the number of rows affected by the operation. + /// + Task ExecuteAsync(CancellationToken cancellationToken, string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes a scalar command and returns the first column of the first row in the result set. /// - Task ExecuteScalarAsync(Sql sql); + /// The type of the result value. + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL statement and its parameters. + /// + /// A task that represents the asynchronous operation. The task result is the scalar result value of type . + /// + Task ExecuteScalarAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously executes a scalar command and returns the first column of the first row in the result set. /// - Task ExecuteScalarAsync(CancellationToken cancellationToken, Sql sql); + /// The type of the result value. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL query string. + /// + /// A task that represents the asynchronous operation. The task result is the scalar result value of type . + /// + Task ExecuteScalarAsync(CancellationToken cancellationToken, string sql, params object[] args); } #endif -} \ No newline at end of file +} diff --git a/PetaPoco/IHideObjectMethods.cs b/PetaPoco/IHideObjectMethods.cs index 85fc3f15..37092bbe 100644 --- a/PetaPoco/IHideObjectMethods.cs +++ b/PetaPoco/IHideObjectMethods.cs @@ -4,53 +4,35 @@ namespace PetaPoco { /// - /// An interface used to hide the 4 System.Object instance methods from the API in Visual Studio intellisense. + /// Hides the compiler-generated public instance methods from the list of intellisense code completion suggestions. /// /// - /// Reference Project: MircoLite ORM (https://github.com/TrevorPilley/MicroLite) - /// Author: Trevor Pilley - /// Source: https://github.com/TrevorPilley/MicroLite/blob/develop/MicroLite/IHideObjectMethods.cs + /// Reference Project: MicroLite ORM () + ///
Author: Trevor Pilley + ///
Source: ///
[EditorBrowsable(EditorBrowsableState.Never)] public interface IHideObjectMethods { - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// + /// [EditorBrowsable(EditorBrowsableState.Never)] - bool Equals(object other); + bool Equals(object obj); - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// + /// [EditorBrowsable(EditorBrowsableState.Never)] int GetHashCode(); - /// - /// Gets the type. - /// - /// The type of the object. + /// [EditorBrowsable(EditorBrowsableState.Never)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = - "The method is defined on System.Object, this interface is just to hide it from intelisense in Visual Studio")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "GetType", - Justification = "The method is defined on System.Object, this interface is just to hide it from intelisense in Visual Studio")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "The method is defined on System.Object, this interface is just to hide it from intellisense")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", + Justification = "The method is defined on System.Object, this interface is just to hide it from intellisense", + MessageId = "GetType")] Type GetType(); - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// + /// [EditorBrowsable(EditorBrowsableState.Never)] string ToString(); } -} \ No newline at end of file +} diff --git a/PetaPoco/IQuery.cs b/PetaPoco/IQuery.cs index ee7575e6..f9807cbb 100644 --- a/PetaPoco/IQuery.cs +++ b/PetaPoco/IQuery.cs @@ -1,757 +1,620 @@ -using System; +using System; using System.Collections.Generic; namespace PetaPoco { + /// + /// Specifies a set of methods for executing SQL queries and returning the result set as lists, enumerables, single POCOs, multi-POCOs, + /// or paged results. + /// public interface IQuery { + #region Query : Single-POCO + /// - /// Streams the result of a select all query (SELECT *). + /// Executes an auto-select query (SELECT *) and returns the results as a sequence of type . /// - /// The POCO type. - /// An enumerable collection of POCOs /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch, which - /// returns the results as a List rather than streaming as an IEnumerable. + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// /// + /// The POCO type representing a single result record. + /// An enumerable sequence of POCOs of type . IEnumerable Query(); - /// - /// Runs an SQL query, returning the results as an IEnumerable collection - /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// An enumerable collection of POCOs - /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch, which - /// returns the results as a List rather than an IEnumerable. - /// - IEnumerable Query(string sql, params object[] args); + /// An SQL builder instance representing the SQL query and its parameters. + /// + IEnumerable Query(Sql sql); /// - /// Runs an SQL query, returning the results as an IEnumerable collection + /// Executes a query and returns the results as a sequence of type . /// - /// The Type representing a row in the result set - /// An SQL builder object representing the base SQL query and its arguments - /// An enumerable collection of POCOs /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch, which - /// returns the results as a List rather than an IEnumerable. + /// Because this method streams the results from the database, care should be taken to not start a new query before finishing with + /// and disposing of the previous one to prevent encountering database locks. In cases where contention could be an issue, consider + /// using the equivalent Fetch method. /// - IEnumerable Query(Sql sql); - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, string sql, params object[] args); + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// An enumerable sequence of POCOs of type . + IEnumerable Query(string sql, params object[] args); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, string sql, params object[] args); + #endregion - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, string sql, params object[] args); + #region Query with Default Mapping : Multi-POCO - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, string sql, params object[] args); - - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, Sql sql); + /// + IEnumerable Query(Sql sql); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, Sql sql); + /// + IEnumerable Query(Sql sql); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, Sql sql); + /// + IEnumerable Query(Sql sql); /// - /// Perform a multi-poco query + /// Executes a multi-poco query and projects the result sequence into a new form of type using a default + /// mapping function. /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The type of objects in the returned IEnumerable - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Func cb, Sql sql); + /// + /// PetaPoco will automatically attempt to determine the split points and auto-map each additional POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// An SQL builder instance representing the SQL query and its parameters. + /// An enumerable sequence of POCOs of type . + IEnumerable Query(Sql sql); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable + /// IEnumerable Query(string sql, params object[] args); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable + /// IEnumerable Query(string sql, params object[] args); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable + /// IEnumerable Query(string sql, params object[] args); /// - /// Perform a multi-poco query + /// Executes a multi-poco query and projects the result sequence into a new form of type using a default + /// mapping function. /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable + /// + /// PetaPoco will automatically attempt to determine the split points and auto-map each additional POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// An enumerable sequence of POCOs of type . IEnumerable Query(string sql, params object[] args); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Sql sql); + #endregion - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Sql sql); + #region Query with Custom Mapping : Multi-POCO - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Sql sql); + /// + IEnumerable Query(Func projector, Sql sql); - /// - /// Perform a multi-poco query - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POC's as an IEnumerable - IEnumerable Query(Sql sql); + /// + IEnumerable Query(Func projector, Sql sql); - /// - /// Performs a multi-poco query - /// - /// The type of objects in the returned IEnumerable - /// An array of Types representing the POCO types of the returned result set. - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as an IEnumerable - IEnumerable Query(Type[] types, object cb, string sql, params object[] args); + /// + IEnumerable Query(Func projector, Sql sql); /// - /// Runs a SELECT * query and returns the result set as a typed list + /// Executes a multi-poco query and projects the result sequence into a new form of type using the + /// provided mapping function. /// - /// The Type representing a row in the result set - /// A List holding the results of the query - List Fetch(); + /// + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The projected POCO type representing a single result record. + /// A function that transforms each of the given types into a , or to use a default mapping function. + /// An SQL builder instance representing the SQL query and its parameters. + /// An enumerable sequence of POCOs of type . + IEnumerable Query(Func projector, Sql sql); - /// - /// Runs a query and returns the result set as a typed list - /// - /// The Type representing a row in the result set - /// The SQL query to execute - /// Arguments to any embedded parameters in the SQL - /// A List holding the results of the query - List Fetch(string sql, params object[] args); + /// + IEnumerable Query(Func projector, string sql, params object[] args); - /// - /// Runs a query and returns the result set as a typed list - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and its arguments - /// A List holding the results of the query - List Fetch(Sql sql); + /// + IEnumerable Query(Func projector, string sql, params object[] args); - /// - /// Retrieves a page of records (without the total count) - /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// A List of results - /// - /// PetaPoco will automatically modify a default SELECT * statement to only retrieve the - /// records for the specified page. - /// - List Fetch(long page, long itemsPerPage); + /// + IEnumerable Query(Func projector, string sql, params object[] args); /// - /// Retrieves a page of records (without the total count) + /// Executes a multi-poco query and projects the result sequence into a new form of type using the + /// provided mapping function. /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . /// - List Fetch(long page, long itemsPerPage, string sql, params object[] args); - - /// - /// Retrieves a page of records (without the total count) + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The projected POCO type representing a single result record. + /// A function that transforms each of the given types into a , or to use a default mapping function. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// An enumerable sequence of POCOs of type . + IEnumerable Query(Func projector, string sql, params object[] args); + + /// + /// Executes a multi-poco query and projects the result sequence into a new form of type using the + /// provided mapping function. /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the base SQL query and its arguments - /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . /// - List Fetch(long page, long itemsPerPage, Sql sql); + /// The projected POCO type representing a single result record. + /// An array of POCO types representing the types referenced in composite type . + /// A function that transforms each of the given types into a , or to use a default mapping function. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// An enumerable sequence of POCOs of type . + IEnumerable Query(Type[] types, object projector, string sql, params object[] args); + + #endregion + + #region QueryMultiple using IGridReader : Multi-POCO Result Set + + /// An SQL builder instance representing the SQL query and its parameters. + /// + IGridReader QueryMultiple(Sql sql); /// - /// Perform a multi-poco fetch + /// Executes a multi-result set query. /// - /// The first POCO type - /// The second POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List - List Fetch(Func cb, string sql, params object[] args); + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A GridReader for reading the sequence of results. + IGridReader QueryMultiple(string sql, params object[] args); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List - List Fetch(Func cb, string sql, params object[] args); + #endregion - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List - List Fetch(Func cb, string sql, params object[] args); + #region Fetch : Single-POCO /// - /// Perform a multi-poco fetch + /// Executes an auto-select query (SELECT *) and returns the results as a list of type . /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List - List Fetch(Func cb, string sql, params object[] args); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A list of POCOs of type . + List Fetch(); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Func cb, Sql sql); + /// An SQL builder instance representing the SQL query and its parameters. + /// + List Fetch(Sql sql); /// - /// Perform a multi-poco fetch + /// Executes a query and returns the results as a list of type . /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Func cb, Sql sql); + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A list of POCOs of type . + List Fetch(string sql, params object[] args); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Func cb, Sql sql); + #endregion - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The returned list POCO type - /// A callback function to connect the POCO instances, or null to automatically guess the relationships - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Func cb, Sql sql); + #region Fetch with Default Mapping : Multi-POCO + + /// + List Fetch(Sql sql); + + /// + List Fetch(Sql sql); + + /// + List Fetch(Sql sql); /// - /// Perform a multi-poco fetch + /// Executes a multi-poco query and projects the result sequence into a new form of type using a default + /// mapping function. /// - /// The first POCO type - /// The second POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List + /// + /// PetaPoco will automatically attempt to determine the split points and auto-map each additional POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// An SQL builder instance representing the SQL query and its parameters. + /// A list of POCOs of type . + List Fetch(Sql sql); + + /// List Fetch(string sql, params object[] args); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List + /// List Fetch(string sql, params object[] args); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List + /// List Fetch(string sql, params object[] args); /// - /// Perform a multi-poco fetch + /// Executes a multi-poco query and projects the result sequence into a new form of type using a default + /// mapping function. /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fifth POCO type - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A collection of POCOs as a List + /// + /// PetaPoco will automatically attempt to determine the split points and auto-map each additional POCO type into . + /// + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A list of POCOs of type . List Fetch(string sql, params object[] args); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Sql sql); + #endregion - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Sql sql); + #region Fetch with Custom Mapping : Multi-POCO - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Sql sql); + /// + List Fetch(Func projector, Sql sql); - /// - /// Perform a multi-poco fetch - /// - /// The first POCO type - /// The second POCO type - /// The third POCO type - /// The fourth POCO type - /// The fourth POCO type - /// An SQL builder object representing the query and its arguments - /// A collection of POCOs as a List - List Fetch(Sql sql); + /// + List Fetch(Func projector, Sql sql); + + /// + List Fetch(Func projector, Sql sql); /// - /// Retrieves a page of records and the total number of available records + /// Executes a multi-poco query and projects the result sequence into a new form of type using the + /// provided mapping function. /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// The SQL to retrieve the total number of records - /// Arguments to any embedded parameters in the sqlCount statement - /// The SQL to retrieve a single page of results - /// Arguments to any embedded parameters in the sqlPage statement - /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page - /// object. + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . /// - Page Page(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The projected POCO type representing a single result record. + /// A function that transforms each of the given types into a , or to use a default mapping function. + /// An SQL builder instance representing the SQL query and its parameters. + /// A list of POCOs of type . + List Fetch(Func projector, Sql sql); + + /// + List Fetch(Func projector, string sql, params object[] args); + + /// + List Fetch(Func projector, string sql, params object[] args); + + /// + List Fetch(Func projector, string sql, params object[] args); /// - /// Retrieves a page of records and the total number of available records + /// Executes a multi-poco query and projects the result sequence into a new form of type using the + /// provided mapping function. /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// A Page of results /// - /// PetaPoco will automatically modify a default SELECT * statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// If is , PetaPoco will automatically attempt to determine the split points and + /// auto-map each POCO type into . /// - Page Page(long page, long itemsPerPage); + /// The first POCO type. + /// The second POCO type. + /// The third POCO type. + /// The fourth POCO type. + /// The fifth POCO type. + /// The projected POCO type representing a single result record. + /// A function that transforms each of the given types into a , or to use a default mapping function. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A list of POCOs of type . + List Fetch(Func projector, string sql, params object[] args); + + #endregion + + #region Fetch : Paged SkipTake /// - /// Retrieves a page of records and the total number of available records + /// Executes an auto-select query (SELECT *) for a subset of records based on the specified parameters, and returns the + /// results as a list of type . /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// This method performs essentially the same operation as . Determining the number of + /// records to skip, and how many to take, however, are calculated automatically based on the specified + /// index and values. /// - Page Page(long page, long itemsPerPage, string sql, params object[] args); + /// + List Fetch(long page, long maxItemsPerPage); - /// - /// Retrieves a page of records and the total number of available records - /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the base SQL query and its arguments - /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// This method performs essentially the same operation as . Determining the number of + /// records to skip, and how many to take, however, are calculated automatically based on the specified + /// index and values. /// - Page Page(long page, long itemsPerPage, Sql sql); + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL query and its parameters. + /// + List Fetch(long page, long maxItemsPerPage, Sql sql); /// - /// Retrieves a page of records and the total number of available records + /// Executes a query for a subset of records based on the specified parameters, and returns the results as a list of type + /// . /// - /// The Type representing a row in the result set - /// The 1-based page number to retrieve - /// The number of records per page - /// An SQL builder object representing the SQL to retrieve the total number of records - /// An SQL builder object representing the SQL to retrieve a single page of results - /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page - /// object. + /// This method performs essentially the same operation as . Determining the + /// number of records to skip, and how many to take, however, are calculated automatically based on the specified index and values. /// - Page Page(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); + /// The POCO type representing a single result record. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A list containing at most POCOs of type . + List Fetch(long page, long maxItemsPerPage, string sql, params object[] args); + + #endregion + + #region Page /// - /// Retrieves a range of records from result set + /// Executes an auto-select query (SELECT *) for a subset of records based on the specified parameters, and returns the + /// results as a Page of type . /// - /// The Type representing a row in the result set - /// The number of rows at the start of the result set to skip over - /// The number of rows to retrieve - /// A List of results /// - /// PetaPoco will automatically modify a default SELECT * statement to only retrieve the - /// records for the specified range. + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// PetaPoco will automatically modify a default SELECT * statement to only retrieve the records for the specified + /// page. It will also execute a second query to retrieve the total number of records in the result set. /// - List SkipTake(long skip, long take); + /// The POCO type representing a single result record. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// An initialized containing a list of POCOs. + Page Page(long page, long maxItemsPerPage); + + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Page Page(long page, long maxItemsPerPage, Sql sql); /// - /// Retrieves a range of records from result set + /// Executes a query for a subset of records based on the specified parameters, and returns the results as a Page of type + /// . /// - /// The Type representing a row in the result set - /// The number of rows at the start of the result set to skip over - /// The number of rows to retrieve - /// The base SQL query - /// Arguments to any embedded parameters in the SQL statement - /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the records for the specified page. It + /// will also execute a second query to retrieve the total number of records in the result set. /// - List SkipTake(long skip, long take, string sql, params object[] args); - - /// - /// Retrieves a range of records from result set + /// The POCO type representing a single result record. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// An initialized containing a list of POCOs. + Page Page(long page, long maxItemsPerPage, string sql, params object[] args); + + /// The one-based page number for this page. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL statement and its parameters, used to query the total number + /// of records. + /// An SQL builder instance representing the SQL statement and its parameters, used to retrieve a single page + /// of results. + /// + Page Page(long page, long maxItemsPerPage, Sql countSql, Sql pageSql); + + /// + /// Executes a query for a subset of records based on the specified parameters, and returns the results as a Page of type + /// . /// - /// The Type representing a row in the result set - /// The number of rows at the start of the result set to skip over - /// The number of rows to retrieve - /// An SQL builder object representing the base SQL query and its arguments - /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// This method accepts two separate SQL statements that will be used explicitly for both parts of the page query. The and parameters are used to populate the returned Page object. /// - List SkipTake(long skip, long take, Sql sql); + /// The POCO type representing a single result record. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// The SQL statement used to query the total number of records. + /// The parameters to embed in . + /// The SQL statement used to retrieve a single page of results. + /// The parameters to embed in the string. + /// An initialized containing a list of POCOs. + Page Page(long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs); - /// - /// Checks for the existence of a row with the specified primary key value. - /// - /// The Type representing the table being queried - /// The primary key value to look for - /// True if a record with the specified primary key value exists. - bool Exists(object primaryKey); + #endregion - /// - /// Checks for the existence of a row matching the specified condition - /// - /// The Type representing the table being queried - /// The SQL expression to be tested for (ie: the WHERE expression) - /// Arguments to any embedded parameters in the SQL statement - /// True if a record matching the condition is found. - bool Exists(string sqlCondition, params object[] args); + #region SkipTake /// - /// Returns the record with the specified primary key value + /// Executes an auto-select query (SELECT *) and returns a subset of the results as a list of type . /// - /// The Type representing a row in the result set - /// The primary key value of the record to fetch - /// The single record matching the specified primary key value /// - /// Throws an exception if there is not exactly one record with the specified primary key value. + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// /// - T Single(object primaryKey); + /// The POCO type representing a single result record. + /// The number of records to skip. + /// The number of records to take. + /// A list of POCOs of type . + List SkipTake(long skip, long take); + + /// The number of records to skip. + /// The number of records to take. + /// An SQL builder instance representing the SQL query and its parameters. + /// + List SkipTake(long skip, long take, Sql sql); /// - /// Runs a query that should always return a single row. + /// Executes a query and returns a subset of the results as a list of type . /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The single record matching the specified SQL query /// - /// Throws an exception if there is not exactly one record + /// The provided SQL query will be modified to limit the starting offset and number of returned records based on the specified + /// parameters. /// - T Single(string sql, params object[] args); + /// The POCO type representing a single result record. + /// The number of records to skip. + /// The number of records to take. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// A list of POCOs of type . + List SkipTake(long skip, long take, string sql, params object[] args); + + #endregion + + #region Exists /// - /// Runs a query that should always return a single row. + /// Determines whether a record exists with the specified primary key value. /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and its arguments - /// The single record matching the specified SQL query /// - /// Throws an exception if there is not exactly one matching record + /// If provided a POCO instance as the parameter, PetaPoco will extract the value from the + /// POCO's mapped primary key property, and perform the same query as if the primary key value was provided directly. /// - T Single(Sql sql); + /// The POCO type representing a single result record. + /// The primary key value, or a POCO containing an assigned primary key value. + /// if one or more records exist with the specified primary key value; otherwise, . + bool Exists(object pocoOrPrimaryKeyValue); + + // TODO: Missing overload: `bool IQuery.Exists(Sql)` /// - /// Runs a query that should always return either a single row, or no rows + /// Determines whether a record exists that matches the conditions defined by the specified query. /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and its arguments - /// The single record matching the specified primary key value, or default(T) if no matching rows - T SingleOrDefault(Sql sql); + /// The POCO type representing a single result record. + /// The SQL string representing the condition portion of the WHERE clause. + /// The parameters to embed in the SQL string. + /// if one or more records exist that satisfy the conditions defined in the specified query; + /// otherwise, . + bool Exists(string sql, params object[] args); + + #endregion + + #region Single /// - /// Returns the record with the specified primary key value, or the default value if not found + /// Returns the only record that matches the specified primary key value, and throws an exception if there is not exactly one + /// matching record. /// - /// The Type representing a row in the result set - /// The primary key value of the record to fetch - /// The single record matching the specified primary key value - /// - /// If there are no records with the specified primary key value, default(T) (typically null) is returned. - /// - T SingleOrDefault(object primaryKey); + /// The POCO type representing a single result record. + /// The primary key value. + /// The single result returned by the query. + /// The result set is empty, or the result set contains more than one + /// record. + T Single(object primaryKey); + + /// An SQL builder instance representing the SQL query and its parameters. + /// + T Single(Sql sql); /// - /// Runs a query that should always return either a single row, or no rows + /// Returns the only record that matches the specified query, and throws an exception if there is not exactly one matching record. /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The single record matching the specified primary key value, or default(T) if no matching rows - T SingleOrDefault(string sql, params object[] args); + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// The single result returned by the query. + /// The result set is empty, or the result set contains more than one + /// record. + T Single(string sql, params object[] args); + + #endregion + + #region SingleOrDefault /// - /// Runs a query that should always return at least one record + /// Returns the only record that matches the specified primary key value, or a default value if the result set is empty; this method + /// throws an exception if there is more than one matching record. /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The first record in the result set - T First(string sql, params object[] args); + /// The POCO type representing a single result record. + /// The primary key value. + /// default(T) if no record is found; otherwise, the single result returned by the query. + /// The result set contains more than one record. + T SingleOrDefault(object primaryKey); + + /// An SQL builder instance representing the SQL query and its parameters. + /// + T SingleOrDefault(Sql sql); /// - /// Runs a query that should always return at least one record + /// Returns the only record that matches the specified query, or a default value if the result set is empty; this method throws an + /// exception if there is more than one matching record. /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and its arguments - /// The first record in the result set + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// default(T) if no record is found; otherwise, the single result returned by the query. + /// The result set contains more than one record. + T SingleOrDefault(string sql, params object[] args); + + #endregion + + #region First + + /// An SQL builder instance representing the SQL query and its parameters. + /// T First(Sql sql); /// - /// Runs a query and returns the first record, or the default value if no matching records + /// Returns the first record that matches the specified query, and throws an exception if the result set is empty. /// - /// The Type representing a row in the result set - /// The SQL query - /// Arguments to any embedded parameters in the SQL statement - /// The first record in the result set, or default(T) if no matching rows - T FirstOrDefault(string sql, params object[] args); + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// The first result record returned by the specified query. + /// The result set is empty. + T First(string sql, params object[] args); - /// - /// Runs a query and returns the first record, or the default value if no matching records - /// - /// The Type representing a row in the result set - /// An SQL builder object representing the query and its arguments - /// The first record in the result set, or default(T) if no matching rows + #endregion + + #region FirstOrDefault + + /// An SQL builder instance representing the SQL query and its parameters. + /// T FirstOrDefault(Sql sql); /// - /// Perform a multi-results set query + /// Returns the first record that matches the specified query, or a default value if the result set is empty. /// - /// An SQL builder object representing the query and its arguments - /// A GridReader to be queried - IGridReader QueryMultiple(Sql sql); + /// The POCO type representing a single result record. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// default(T) if the result set is empty; otherwise, the first record that matches the specified query. + T FirstOrDefault(string sql, params object[] args); - /// - /// Perform a multi-results set query - /// - /// The SQL query to be executed - /// Arguments to any embedded parameters in the SQL - /// A GridReader to be queried - IGridReader QueryMultiple(string sql, params object[] args); + #endregion } -} \ No newline at end of file +} diff --git a/PetaPoco/IQueryAsync.cs b/PetaPoco/IQueryAsync.cs index ac463ce0..cef87434 100644 --- a/PetaPoco/IQueryAsync.cs +++ b/PetaPoco/IQueryAsync.cs @@ -7,417 +7,741 @@ namespace PetaPoco { #if ASYNC + /// + /// Specifies a set of methods for asynchronously executing SQL queries and returning the result set as lists, enumerables, single + /// POCOs, multi-POCOs, or paged results. + /// public interface IQueryAsync { - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback); + #region QueryAsync : Single-POCO - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CommandType commandType); + /// + Task> QueryAsync(); - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken); + /// + Task> QueryAsync(Sql sql); - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType); + /// + Task> QueryAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) and returns an async reader. /// - Task QueryAsync(Action receivePocoCallback, string sql, params object[] args); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken); /// - /// Async version of . + /// Asynchronously executes a query and returns an async reader. /// - Task QueryAsync(Action receivePocoCallback, CommandType commandType, string sql, params object[] args); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL query and its parameters. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, string sql, params object[] args); + /// Asynchronously executes a query and returns an async reader. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); + #endregion - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, Sql sql); + #region QueryAsync : Single-POCO as CommandType - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CommandType commandType, Sql sql); + /// + Task> QueryAsync(CommandType commandType); - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, Sql sql); + /// + Task> QueryAsync(CommandType commandType, Sql sql); - /// - /// Async version of . - /// - Task QueryAsync(Action receivePocoCallback, CancellationToken cancellationToken, CommandType commandType, Sql sql); + /// + Task> QueryAsync(CommandType commandType, string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) for the specified command type and returns an async reader. /// - Task> QueryAsync(); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType); /// - /// Async version of . + /// Asynchronously executes a query for the specified command type and returns an async reader. /// - Task> QueryAsync(CommandType commandType); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// An SQL builder instance representing the SQL query and its parameters. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query for the specified command type and returns an async reader. /// - Task> QueryAsync(CancellationToken cancellationToken); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); - /// - /// Async version of . - /// - Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType); + #endregion - /// - /// Async version of . - /// - Task> QueryAsync(string sql, params object[] args); + #region QueryAsync with Action : Single-POCO - /// - /// Async version of . - /// - Task> QueryAsync(CommandType commandType, string sql, params object[] args); + /// + Task QueryAsync(Action action); - /// - /// Async version of . - /// - Task> QueryAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// + Task QueryAsync(Action action, Sql sql); - /// - /// Async version of . - /// - Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); + /// + Task QueryAsync(Action action, string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) and invokes the specified action on each result read from the + /// underlying data reader. /// - Task> QueryAsync(Sql sql); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// An action to perform on each POCO in the result set. + /// + /// A task that represents the asynchronous operation. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken); - /// - /// Async version of . - /// - Task> QueryAsync(CommandType commandType, Sql sql); + /// A cancellation token that can be used to cancel the operation. + /// An action to perform on each POCO in the result set. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query and invokes the specified action on each result read from the underlying data reader. /// - Task> QueryAsync(CancellationToken cancellationToken, Sql sql); + /// The POCO type representing a single result record. + /// An action to perform on each POCO in the result set. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task> QueryAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql); + #endregion - /// - /// Async version of . - /// - Task> FetchAsync(); + #region QueryAsync with Action : Single-POCO as CommandType - /// - /// Async version of . - /// - Task> FetchAsync(CommandType commandType); + /// + Task QueryAsync(Action action, CommandType commandType); - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken); + /// + Task QueryAsync(Action action, CommandType commandType, Sql sql); - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType); + /// + Task QueryAsync(Action action, CommandType commandType, string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) for the specified command type and invokes the specified action + /// on each result read from the underlying data reader. /// - Task> FetchAsync(string sql, params object[] args); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// An action to perform on each POCO in the result set. + /// The type of command to execute. + /// + /// A task that represents the asynchronous operation. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType); - /// - /// Async version of . - /// - Task> FetchAsync(CommandType commandType, string sql, params object[] args); + /// A cancellation token that can be used to cancel the operation. + /// An action to perform on each POCO in the result set. + /// The type of command to execute. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query for the specified command type and invokes the specified action on each result read from the + /// underlying data reader. /// - Task> FetchAsync(CancellationToken cancellationToken, string sql, params object[] args); + /// The POCO type representing a single result record. + /// An action to perform on each POCO in the result set. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. + /// + Task QueryAsync(Action action, CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); + #endregion - /// - /// Async version of . - /// - Task> FetchAsync(Sql sql); + #region QueryAsync : Multi-POCO - /// - /// Async version of . - /// - Task> FetchAsync(CommandType commandType, Sql sql); + //... - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, Sql sql); + #endregion - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql); + #region QueryMultipleAsync : Multi-POCO Result Set - /// - /// Async version of . - /// - Task> FetchAsync(long page, long itemsPerPage); + //... - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage); + #endregion - /// - /// Async version of . - /// - Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); + #region FetchAsync : Single-POCO - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sql, params object[] args); + /// + Task> FetchAsync(); - /// - /// Async version of . - /// - Task> FetchAsync(long page, long itemsPerPage, Sql sql); + /// + Task> FetchAsync(Sql sql); - /// - /// Async version of . - /// - Task> FetchAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sql); + /// + Task> FetchAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) and returns the results as a list of type . /// - Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken); - /// - /// Async version of . - /// - Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task> FetchAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query and returns the results as a list of type . /// - Task> PageAsync(long page, long itemsPerPage); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage); + #endregion - /// - /// Async version of . - /// - Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); + #region FetchAsync : Single-POCO as CommandType - /// - /// Async version of . - /// - Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, string sql, params object[] args); + /// + Task> FetchAsync(CommandType commandType); - /// - /// Async version of . - /// - Task> PageAsync(long page, long itemsPerPage, Sql sql); + /// + Task> FetchAsync(CommandType commandType, Sql sql); - /// - /// Async version of . - /// - Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sql); + /// + Task> FetchAsync(CommandType commandType, string sql, params object[] args); /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) for the specified command type and returns the results as a list + /// of type . /// - Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType); - /// - /// Async version of . - /// - Task> PageAsync(CancellationToken cancellationToken, long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query for the specified command type and returns the results as a list of type . /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The type of command to execute. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken, CommandType commandType, string sql, params object[] args); + + #endregion + + #region FetchAsync : Multi-POCO + + //... + + #endregion + + #region FetchAsync : Paged SkipTake + + /// + Task> FetchAsync(long page, long maxItemsPerPage); + + /// + Task> FetchAsync(long page, long maxItemsPerPage, Sql sql); + + /// + Task> FetchAsync(long page, long maxItemsPerPage, string sql, params object[] args); + + /// + /// Asynchronously executes an auto-select query (SELECT *) for a subset of records based on the specified parameters, and + /// returns the results as a list of type . + /// + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// This method performs essentially the same operation as . + /// Determining the number of records to skip, and how many to take, however, are calculated automatically based on the specified + /// index and values. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage); + + /// + /// This method performs essentially the same operation as . + /// Determining the number of records to skip, and how many to take, however, are calculated automatically based on the specified + /// index and values. + /// + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql sql); + + /// + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a list of + /// type . + /// + /// + /// This method performs essentially the same operation as . Determining the number of records to skip, and how many to take, however, are calculated automatically based on + /// the specified index and values. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number used to calculate the number of records to skip. + /// The maximum number of records per page. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string sql, params object[] args); + + #endregion + + #region PageAsync + + /// + Task> PageAsync(long page, long maxItemsPerPage); + + /// + Task> PageAsync(long page, long maxItemsPerPage, Sql sql); + + /// + Task> PageAsync(long page, long maxItemsPerPage, string sql, params object[] args); + + /// + Task> PageAsync(long page, long maxItemsPerPage, Sql countSql, Sql pageSql); + + /// + Task> PageAsync(long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs); + + /// + /// Asynchronously executes an auto-select query (SELECT *) for a subset of records based on the specified parameters, and + /// returns the results as a Page of type . + /// + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// + /// A task that represents the asynchronous operation. The task result contains an initialized containing a + /// list of POCOs. + /// + Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage); + + /// + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a Page of + /// type . + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL query and its parameters. + /// + /// A task that represents the asynchronous operation. The task result contains an initialized containing a + /// list of POCOs. + /// + Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql sql); + + /// + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a Page of + /// type . + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains an initialized containing a + /// list of POCOs. + /// + Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string sql, params object[] args); + + /// + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a Page of + /// type . + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// An SQL builder instance representing the SQL statement and its parameters, used to query the total number + /// of records. + /// An SQL builder instance representing the SQL statement and its parameters, used to retrieve a single page + /// of results. + /// + /// A task that represents the asynchronous operation. The task result contains an initialized containing a + /// list of POCOs. + /// + Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, Sql countSql, Sql pageSql); + + /// + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a Page of + /// type . + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The one-based page number for this page. + /// The maximum number of records per page. + /// The SQL statement used to query the total number of records. + /// The parameters to embed in . + /// The SQL statement used to retrieve a single page of results. + /// The parameters to embed in the string. + /// + /// A task that represents the asynchronous operation. The task result contains an initialized containing a + /// list of POCOs. + /// + Task> PageAsync(CancellationToken cancellationToken, long page, long maxItemsPerPage, string countSql, object[] countArgs, string pageSql, object[] pageArgs); + + #endregion + + #region SkipTakeAsync + + /// Task> SkipTakeAsync(long skip, long take); + /// + Task> SkipTakeAsync(long skip, long take, Sql sql); + + /// + Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); + /// - /// Async version of . + /// Asynchronously executes an auto-select query (SELECT *) for a subset of records based on the specified parameters, and + /// returns the results as a list of type . /// + /// + /// must be enabled in order to generate the auto-select portion of the SQL statement. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The number of records to skip. + /// The number of records to take. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take); - /// - /// Async version of . - /// - Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); + /// A cancellation token that can be used to cancel the operation. + /// The number of records to skip. + /// The number of records to take. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, Sql sql); /// - /// Async version of . + /// Asynchronously executes a query for a subset of records based on the specified parameters, and returns the results as a list of + /// type . /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The number of records to skip. + /// The number of records to take. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, string sql, params object[] args); - /// - /// Async version of . - /// - Task> SkipTakeAsync(long skip, long take, Sql sql); + #endregion - /// - /// Async version of . - /// - Task> SkipTakeAsync(CancellationToken cancellationToken, long skip, long take, Sql sql); + #region ExistsAsync - /// - /// Async version of . - /// - Task ExistsAsync(object primaryKey); + /// + Task ExistsAsync(object pocoOrPrimaryKeyValue); - /// - /// Async version of . - /// - Task ExistsAsync(CancellationToken cancellationToken, object primaryKey); + // TODO: Missing overload: `IQueryAsync.ExistsAsync(Sql)` - /// - /// Async version of . - /// - Task ExistsAsync(string sqlCondition, params object[] args); + /// + Task ExistsAsync(string sql, params object[] args); /// - /// Async version of . + /// Asynchronously determines whether a record exists with the specified primary key value. /// - Task ExistsAsync(CancellationToken cancellationToken, string sqlCondition, params object[] args); + /// + /// If provided a POCO object as the parameter, PetaPoco will extract the value from the + /// POCO's mapped primary key property, and perform the same query as if the primary key value was provided directly. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The primary key value, or a POCO containing an assigned primary key value. + /// + /// A task that represents the asynchronous operation. The task result contains if one or more records exist + /// with the specified primary key value; otherwise, . + /// + Task ExistsAsync(CancellationToken cancellationToken, object pocoOrPrimaryKeyValue); - /// - /// Async version of . - /// - Task SingleAsync(object primaryKey); + // TODO: Missing overload: `IQueryAsync.ExistsAsync(CancellationToken, Sql)` /// - /// Async version of . + /// Asynchronously determines whether a record exists that matches the conditions defined by the specified query. /// - Task SingleAsync(CancellationToken cancellationToken, object primaryKey); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL string representing the condition portion of the WHERE clause. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains if one or more records exist + /// that satisfy the conditions defined in the specified query; otherwise, . + /// + Task ExistsAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task SingleAsync(string sql, params object[] args); + #endregion - /// - /// Async version of . - /// - Task SingleAsync(CancellationToken cancellationToken, string sql, params object[] args); + #region SingleAsync - /// - /// Async version of . - /// + /// + Task SingleAsync(object primaryKey); + + /// Task SingleAsync(Sql sql); + /// + Task SingleAsync(string sql, params object[] args); + /// - /// Async version of . + /// Asynchronously returns the only record that matches the specified primary key value, and throws an exception if there is not + /// exactly one matching record. /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The primary key value. + /// + /// A task that represents the asynchronous operation. The task result contains the single result returned by the query. + /// + /// The result set is empty, or the result set contains more than one + /// record. + Task SingleAsync(CancellationToken cancellationToken, object primaryKey); + + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL query and its parameters. + /// Task SingleAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously returns the only record that matches the specified query, and throws an exception if there is not exactly one + /// matching record. /// - Task SingleOrDefaultAsync(Sql sql); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains the single result returned by the query. + /// + /// The result set is empty, or the result set contains more than one + /// record. + Task SingleAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task SingleOrDefaultAsync(CancellationToken cancellationToken, Sql sql); + #endregion - /// - /// Async version of . - /// + #region SingleOrDefaultAsync + + /// Task SingleOrDefaultAsync(object primaryKey); + /// + Task SingleOrDefaultAsync(Sql sql); + + /// + Task SingleOrDefaultAsync(string sql, params object[] args); + /// - /// Async version of . + /// Asynchronously returns the only record that matches the specified primary key value, or a default value if the result set is + /// empty; this method throws an exception if there is more than one matching record. /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The primary key value. + /// + /// A task that represents the asynchronous operation. The task result contains default(T) if no record is found; otherwise, the + /// single result returned by the specified query. + /// + /// The result set contains more than one record. Task SingleOrDefaultAsync(CancellationToken cancellationToken, object primaryKey); - /// - /// Async version of . - /// - Task SingleOrDefaultAsync(string sql, params object[] args); + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task SingleOrDefaultAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously returns the only record that matches the specified query, or a default value if the result set is empty; this + /// method throws an exception if there is more than one matching record. /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains default(T) if no record is found; otherwise, the + /// single result returned by the specified query. + /// + /// The result set contains more than one record. Task SingleOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task FirstAsync(string sql, params object[] args); + #endregion - /// - /// Async version of . - /// - Task FirstAsync(CancellationToken cancellationToken, string sql, params object[] args); + #region FirstAsync - /// - /// Async version of . - /// + /// Task FirstAsync(Sql sql); - /// - /// Async version of . - /// + /// + Task FirstAsync(string sql, params object[] args); + + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// Task FirstAsync(CancellationToken cancellationToken, Sql sql); /// - /// Async version of . + /// Asynchronously returns the first result returned by the specified query, and throws an exception if the result set is empty. /// - Task FirstOrDefaultAsync(string sql, params object[] args); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains the first result returned by the specified query. + /// + /// The result set is empty. + Task FirstAsync(CancellationToken cancellationToken, string sql, params object[] args); - /// - /// Async version of . - /// - Task FirstOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args); + #endregion - /// - /// Async version of . - /// + #region FirstOrDefaultAsync + + /// Task FirstOrDefaultAsync(Sql sql); + /// + Task FirstOrDefaultAsync(string sql, params object[] args); + + /// A cancellation token that can be used to cancel the operation. + /// An SQL builder instance representing the SQL query and its parameters. + /// + Task FirstOrDefaultAsync(CancellationToken cancellationToken, Sql sql); + /// - /// Async version of . + /// Asynchronously returns the first result returned by the specified query, or a default value if the result set is empty. /// - Task FirstOrDefaultAsync(CancellationToken cancellationToken, Sql sql); + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The SQL query string. + /// The parameters to embed in the SQL string. + /// + /// A task that represents the asynchronous operation. The task result contains default(T) if the result set is empty; otherwise, + /// the first result returned by the specified query. + /// + Task FirstOrDefaultAsync(CancellationToken cancellationToken, string sql, params object[] args); + + #endregion } #endif } diff --git a/PetaPoco/IStoredProc.cs b/PetaPoco/IStoredProc.cs index dada0024..331c3b59 100644 --- a/PetaPoco/IStoredProc.cs +++ b/PetaPoco/IStoredProc.cs @@ -2,61 +2,60 @@ namespace PetaPoco { + /// + /// Specifies a set of methods for executing stored procedures. + /// public interface IStoredProc { /// - /// Runs a stored procedure, returning the results as an IEnumerable collection + /// Executes a non-query stored procedure and returns the number of rows affected. /// - /// The Type representing a row in the result set - /// The name of the stored procedure to run - /// Arguments for the stored procedure - /// An enumerable collection of result records /// - /// For any arguments which are POCOs, each readable property will be turned into a named parameter - /// for the stored procedure. Arguments which are IDbDataParameters will be passed through. Any other - /// argument types will throw an exception. + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. /// - IEnumerable QueryProc(string storedProcedureName, params object[] args); + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// The number of rows affected. + int ExecuteNonQueryProc(string storedProcedureName, params object[] args); /// - /// Runs a stored procedure, returning the results as typed list + /// Executes a scalar stored procedure and returns the first column of the first row in the result set. /// - /// The Type representing a row in the result set - /// The name of the stored procedure to run - /// Arguments for the stored procedure - /// A List holding the results of the query /// - /// For any arguments which are POCOs, each readable property will be turned into a named parameter - /// for the stored procedure. Arguments which are IDbDataParameters will be passed through. Any other - /// argument types will throw an exception. + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. /// - List FetchProc(string storedProcedureName, params object[] args); + /// The POCO type representing a single result record. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// The scalar result value of type . + T ExecuteScalarProc(string storedProcedureName, params object[] args); /// - /// Executes a stored procedure and returns the first column of the first row in the result set. + /// Executes a query stored procedure and returns the results as a sequence of type . /// - /// The type that the result value should be cast to - /// The name of the stored procedure to run - /// Arguments for the stored procedure - /// The scalar value cast to T /// - /// For any arguments which are POCOs, each readable property will be turned into a named parameter - /// for the stored procedure. Arguments which are IDbDataParameters will be passed through. Any other - /// argument types will throw an exception. + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. /// - T ExecuteScalarProc(string storedProcedureName, params object[] args); + /// The POCO type representing a single result record. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// An sequence of results. + IEnumerable QueryProc(string storedProcedureName, params object[] args); /// - /// Executes a non-query stored procedure + /// Executes a query stored procedure and returns the results as a list of type . /// - /// The name of the stored procedure to run - /// Arguments for the stored procedure - /// The number of rows affected /// - /// For any arguments which are POCOs, each readable property will be turned into a named parameter - /// for the stored procedure. Arguments which are IDbDataParameters will be passed through. Any other - /// argument types will throw an exception. + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. /// - int ExecuteNonQueryProc(string storedProcedureName, params object[] args); + /// The POCO type representing a single result record. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// A containing the results. + List FetchProc(string storedProcedureName, params object[] args); } -} \ No newline at end of file +} diff --git a/PetaPoco/IStoredProcAsync.cs b/PetaPoco/IStoredProcAsync.cs index 84c79e25..083e305d 100644 --- a/PetaPoco/IStoredProcAsync.cs +++ b/PetaPoco/IStoredProcAsync.cs @@ -6,57 +6,107 @@ namespace PetaPoco { #if ASYNC + /// + /// Specifies a set of methods for asynchronously executing stored procedures. + /// public interface IStoredProcAsync { - /// - /// Async version of . - /// - Task QueryProcAsync(Action receivePocoCallback, string storedProcedureName, params object[] args); + /// + Task ExecuteNonQueryProcAsync(string storedProcedureName, params object[] args); - /// - /// Async version of . - /// - Task QueryProcAsync(Action receivePocoCallback, CancellationToken cancellationToken, string storedProcedureName, params object[] args); + /// + Task ExecuteScalarProcAsync(string storedProcedureName, params object[] args); - /// - /// Async version of . - /// + /// Task> QueryProcAsync(string storedProcedureName, params object[] args); - /// - /// Async version of . - /// - Task> QueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); + /// + Task QueryProcAsync(Action action, string storedProcedureName, params object[] args); - /// - /// Async version of . - /// + /// Task> FetchProcAsync(string storedProcedureName, params object[] args); /// - /// Async version of . + /// Asynchronously executes a non-query stored procedure and returns the number of rows affected. /// - Task> FetchProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); + /// + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. + /// + /// A cancellation token that can be used to cancel the operation. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// + /// A task that represents the asynchronous operation. The task result contains the number of rows affected. + /// + Task ExecuteNonQueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); /// - /// Async version of . + /// Asynchronously executes a scalar stored procedure and returns the first column of the first row in the result set. /// - Task ExecuteScalarProcAsync(string storedProcedureName, params object[] args); + /// + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// + /// A task that represents the asynchronous operation. The task result contains the scalar result value of type . + /// + Task ExecuteScalarProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); /// - /// Async version of . + /// Asynchronously executes a query stored procedure and returns an IAsyncReader of type . /// - Task ExecuteScalarProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); + /// + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// + /// A task that represents the asynchronous operation. The task result contains an for reading the + /// result set. + /// + Task> QueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); /// - /// Async version of . + /// Asynchronously executes a query stored procedure and invokes the specified action on each POCO in the result set. /// - Task ExecuteNonQueryProcAsync(string storedProcedureName, params object[] args); + /// + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. + /// + /// The POCO type representing a single result record. + /// An action to perform on each POCO in the result set. + /// A cancellation token that can be used to cancel the operation. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// + /// A task that represents the asynchronous operation. + /// + Task QueryProcAsync(Action action, CancellationToken cancellationToken, string storedProcedureName, params object[] args); /// - /// Async version of . + /// Asynchronously executes a query stored procedure and returns a list of type . /// - Task ExecuteNonQueryProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); + /// + /// For any arguments which are POCOs, each readable property will be turned into a named parameter for the stored procedure. + /// Arguments which are IDbDataParameters will be passed through. Any other argument types will throw an exception. + /// + /// The POCO type representing a single result record. + /// A cancellation token that can be used to cancel the operation. + /// The name of the stored procedure to execute. + /// The arguments to pass to the stored procedure. + /// + /// A task that represents the asynchronous operation. The task result contains a list of POCOs of type . + /// + Task> FetchProcAsync(CancellationToken cancellationToken, string storedProcedureName, params object[] args); } #endif } diff --git a/PetaPoco/ITransactionAccessor.cs b/PetaPoco/ITransactionAccessor.cs index 42b0ea3e..8b2416d4 100644 --- a/PetaPoco/ITransactionAccessor.cs +++ b/PetaPoco/ITransactionAccessor.cs @@ -3,16 +3,14 @@ namespace PetaPoco { /// - /// Represents a contract which exposes the current instance. + /// Represents a contract which exposes the current instance. /// public interface ITransactionAccessor { /// - /// Gets the current transaction instance. + /// Gets the current transaction instance. /// - /// - /// The current transaction instance; else, null if not transaction is in progress. - /// + /// The current transaction instance. Returns if no transaction is in progress. IDbTransaction Transaction { get; } } -} \ No newline at end of file +} diff --git a/PetaPoco/OracleProvider.cs b/PetaPoco/OracleProvider.cs index ac047f87..9988e87f 100644 --- a/PetaPoco/OracleProvider.cs +++ b/PetaPoco/OracleProvider.cs @@ -1,44 +1,43 @@ -using System; +using System; using System.Data.Common; using System.Reflection; namespace PetaPoco { - /* - Thanks to Adam Schroder (@schotime) for this. - - This extra file provides an implementation of DbProviderFactory for early versions of the Oracle - drivers that don't include include it. For later versions of Oracle, the standard OracleProviderFactory - class should work fine - - Uses reflection to load Oracle.DataAccess assembly and in-turn create connections and commands - - Currently untested. - - Usage: - - new PetaPoco.Database("", new PetaPoco.OracleProvider()) - - Or in your app/web config (be sure to change ASSEMBLYNAME to the name of your - assembly containing OracleProvider.cs) - - - - - - - - - - - - */ - + /// + /// Pprovides an implementation of for early versions of the Oracle drivers that don't include it. + /// + /// + /// For later versions of Oracle, the standard OracleProviderFactory class should work fine. + /// Uses reflection to load Oracle.DataAccess assembly and in-turn create connections and commands. + /// Thanks to Adam Schroder (@schotime) for this. + /// Currently untested. + /// + /// + /// + /// + /// + /// Or in your app/web config (be sure to change ASSEMBLY_NAME to the name of your assembly containing OracleProvider.cs): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + /// public class OracleProvider : DbProviderFactory { private const string _assemblyName = "Oracle.DataAccess"; @@ -47,9 +46,15 @@ public class OracleProvider : DbProviderFactory private static Type _connectionType; private static Type _commandType; - // Required for DbProviderFactories.GetFactory() to work. + /// + /// Singleton instance of OracleProvider. Required for DbProviderFactories.GetFactory() to work. + /// public static OracleProvider Instance = new OracleProvider(); + /// + /// Initializes a new instance of the class. + /// + /// Unable to find the connection type from the assembly. public OracleProvider() { _connectionType = TypeFromAssembly(_connectionTypeName, _assemblyName); @@ -58,11 +63,19 @@ public OracleProvider() throw new InvalidOperationException("Can't find Connection type: " + _connectionTypeName); } + /// + /// Creates a new instance of an OracleConnection. + /// + /// A new . public override DbConnection CreateConnection() { return (DbConnection) Activator.CreateInstance(_connectionType); } + /// + /// Creates a new instance of an OracleCommand. + /// + /// A new . public override DbCommand CreateCommand() { DbCommand command = (DbCommand) Activator.CreateInstance(_commandType); @@ -73,6 +86,14 @@ public override DbCommand CreateCommand() return command; } + /// + /// Returns the Type for the specified from the provided . + /// + /// The name of the type to get. + /// The name of the assembly to get the type from. + /// The Type, or if unable to locate it. + /// Unable to load . + /// Unable to find the . public static Type TypeFromAssembly(string typeName, string assemblyName) { try @@ -96,7 +117,7 @@ public static Type TypeFromAssembly(string typeName, string assemblyName) if (assembly == null) { - throw new InvalidOperationException("Can't find assembly: " + assemblyName); + throw new InvalidOperationException("Cannot find assembly: " + assemblyName); } type = assembly.GetType(typeName); @@ -114,4 +135,4 @@ public static Type TypeFromAssembly(string typeName, string assemblyName) } } } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/FirebirdDatabaseProvider.cs b/PetaPoco/Providers/FirebirdDatabaseProvider.cs index 0648b6dc..51d9e6bc 100644 --- a/PetaPoco/Providers/FirebirdDatabaseProvider.cs +++ b/PetaPoco/Providers/FirebirdDatabaseProvider.cs @@ -11,11 +11,16 @@ namespace PetaPoco.Providers { // TODO: It irks the ¢®@₱ out of me that the Firebird DBProvider class name is the only one that improperly appends "Db" after the provider name. - public class FirebirdDbDatabaseProvider : DatabaseProvider + /// + /// Provides a specific implementation of the class for Firebird. + /// + public class FirebirdDbDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient"); + /// public override string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) { var sql = $"{parts.Sql}\nROWS @{args.Length} TO @{args.Length + 1}"; @@ -23,17 +28,19 @@ public override string BuildPageQuery(long skip, long take, SQLParts parts, ref return sql; } - public override object ExecuteInsert(Database database, IDbCommand cmd, string primaryKeyName) + /// + public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { PrepareInsert(cmd, primaryKeyName); - return ExecuteScalarHelper(database, cmd); + return ExecuteScalarHelper(db, cmd); } #if ASYNC - public override Task ExecuteInsertAsync(CancellationToken cancellationToken, Database database, IDbCommand cmd, string primaryKeyName) + /// + public override Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { PrepareInsert(cmd, primaryKeyName); - return ExecuteScalarHelperAsync(cancellationToken, database, cmd); + return ExecuteScalarHelperAsync(cancellationToken, db, cmd); } #endif @@ -47,7 +54,7 @@ private void PrepareInsert(IDbCommand cmd, string primaryKeyName) cmd.CommandText += " RETURNING " + EscapeSqlIdentifier(primaryKeyName) + ";"; } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"\"{sqlIdentifier}\""; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"\"{sqlIdentifier}\""; } } diff --git a/PetaPoco/Providers/MariaDbDatabaseProvider.cs b/PetaPoco/Providers/MariaDbDatabaseProvider.cs index d9b9ce3e..dc50aad5 100644 --- a/PetaPoco/Providers/MariaDbDatabaseProvider.cs +++ b/PetaPoco/Providers/MariaDbDatabaseProvider.cs @@ -4,14 +4,22 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for MariaDB. + /// + /// + /// This class uses the provider. + /// public class MariaDbDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() { // MariaDb currently uses the MySql data provider return GetFactory("MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Culture=neutral, PublicKeyToken=c5687fc88969c44d"); } + /// public override string GetParameterPrefix(string connectionString) { if (connectionString != null && connectionString.IndexOf("Allow User Variables=true", StringComparison.Ordinal) >= 0) @@ -19,10 +27,10 @@ public override string GetParameterPrefix(string connectionString) return "@"; } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"`{sqlIdentifier}`"; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"`{sqlIdentifier}`"; - public override string GetExistsSql() - => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; + /// + public override string GetExistsSql() => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/MsAccessDbDatabaseProvider.cs b/PetaPoco/Providers/MsAccessDbDatabaseProvider.cs index 3e5e68e7..5f4c89c0 100644 --- a/PetaPoco/Providers/MsAccessDbDatabaseProvider.cs +++ b/PetaPoco/Providers/MsAccessDbDatabaseProvider.cs @@ -10,27 +10,39 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for Microsoft Access. + /// public class MsAccessDbDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("System.Data.OleDb.OleDbFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); - public override object ExecuteInsert(Database database, IDbCommand cmd, string primaryKeyName) + /// + public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { - ExecuteNonQueryHelper(database, cmd); + ExecuteNonQueryHelper(db, cmd); cmd.CommandText = "SELECT @@IDENTITY AS NewID;"; - return ExecuteScalarHelper(database, cmd); + return ExecuteScalarHelper(db, cmd); } #if ASYNC - public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database database, IDbCommand cmd, string primaryKeyName) + /// + public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { - await ExecuteNonQueryHelperAsync(cancellationToken, database, cmd).ConfigureAwait(false); + await ExecuteNonQueryHelperAsync(cancellationToken, db, cmd).ConfigureAwait(false); cmd.CommandText = "SELECT @@IDENTITY AS NewID;"; - return await ExecuteScalarHelperAsync(cancellationToken, database, cmd).ConfigureAwait(false); + return await ExecuteScalarHelperAsync(cancellationToken, db, cmd).ConfigureAwait(false); } #endif + /// + /// Page queries are not supported by MsAccess database. + /// + /// This method always throws a . + /// The MsAccess provider does not support paging. + /// public override string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) => throw new NotSupportedException("The MS Access provider does not support paging."); } diff --git a/PetaPoco/Providers/MySqlConnectorDatabaseProvider.cs b/PetaPoco/Providers/MySqlConnectorDatabaseProvider.cs index 53473a9f..a7bb7d1f 100644 --- a/PetaPoco/Providers/MySqlConnectorDatabaseProvider.cs +++ b/PetaPoco/Providers/MySqlConnectorDatabaseProvider.cs @@ -4,11 +4,15 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for MySQL using the MySqlConnector library. + /// public class MySqlConnectorDatabaseProvider : DatabaseProvider { - public override DbProviderFactory GetFactory() - => GetFactory("MySqlConnector.MySqlConnectorFactory, MySqlConnector"); + /// + public override DbProviderFactory GetFactory() => GetFactory("MySqlConnector.MySqlConnectorFactory, MySqlConnector"); + /// public override string GetParameterPrefix(string connectionString) { if (connectionString != null && connectionString.IndexOf("Allow User Variables=true", StringComparison.Ordinal) >= 0) @@ -16,10 +20,10 @@ public override string GetParameterPrefix(string connectionString) return "@"; } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"`{sqlIdentifier}`"; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"`{sqlIdentifier}`"; - public override string GetExistsSql() - => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; + /// + public override string GetExistsSql() => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/MySqlDatabaseProvider.cs b/PetaPoco/Providers/MySqlDatabaseProvider.cs index 8fad02d1..69a180fe 100644 --- a/PetaPoco/Providers/MySqlDatabaseProvider.cs +++ b/PetaPoco/Providers/MySqlDatabaseProvider.cs @@ -4,11 +4,16 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for MySQL. + /// public class MySqlDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Culture=neutral, PublicKeyToken=c5687fc88969c44d"); + /// public override string GetParameterPrefix(string connectionString) { if (connectionString != null && connectionString.IndexOf("Allow User Variables=true", StringComparison.Ordinal) >= 0) @@ -16,10 +21,10 @@ public override string GetParameterPrefix(string connectionString) return "@"; } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"`{sqlIdentifier}`"; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"`{sqlIdentifier}`"; - public override string GetExistsSql() - => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; + /// + public override string GetExistsSql() => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/OracleDatabaseProvider.cs b/PetaPoco/Providers/OracleDatabaseProvider.cs index dcc697fa..cfd5dc62 100644 --- a/PetaPoco/Providers/OracleDatabaseProvider.cs +++ b/PetaPoco/Providers/OracleDatabaseProvider.cs @@ -11,17 +11,23 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for Oracle. + /// public class OracleDatabaseProvider : DatabaseProvider { - public override string GetParameterPrefix(string connectionString) - => ":"; + /// + public override string GetParameterPrefix(string connectionString) => ":"; + /// public override void PreExecute(IDbCommand cmd) { cmd.GetType().GetProperty("BindByName")?.SetValue(cmd, true, null); cmd.GetType().GetProperty("InitialLONGFetchSize")?.SetValue(cmd, -1, null); } + /// + /// A paged query does not alias '*' public override string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) { if (parts.SqlSelectRemoved.StartsWith("*")) @@ -31,6 +37,7 @@ public override string BuildPageQuery(long skip, long take, SQLParts parts, ref return Singleton.Instance.BuildPageQuery(skip, take, parts, ref args); } + /// public override DbProviderFactory GetFactory() { // "Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess" is for Oracle.ManagedDataAccess.dll @@ -39,12 +46,13 @@ public override DbProviderFactory GetFactory() "Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess"); } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"\"{sqlIdentifier.ToUpperInvariant()}\""; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"\"{sqlIdentifier.ToUpperInvariant()}\""; - public override string GetAutoIncrementExpression(TableInfo ti) - => !string.IsNullOrEmpty(ti.SequenceName) ? $"{ti.SequenceName}.nextval" : null; + /// + public override string GetAutoIncrementExpression(TableInfo ti) => !string.IsNullOrEmpty(ti.SequenceName) ? $"{ti.SequenceName}.nextval" : null; + /// public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -58,18 +66,8 @@ public override object ExecuteInsert(Database db, IDbCommand cmd, string primary return -1; } - private IDbDataParameter PrepareInsert(IDbCommand cmd, string primaryKeyName) - { - cmd.CommandText += $" returning {EscapeSqlIdentifier(primaryKeyName)} into :newid"; - var param = cmd.CreateParameter(); - param.ParameterName = ":newid"; - param.Value = DBNull.Value; - param.Direction = ParameterDirection.ReturnValue; - param.DbType = DbType.Int64; - cmd.Parameters.Add(param); - return param; - } #if ASYNC + /// public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -83,5 +81,17 @@ public override async Task ExecuteInsertAsync(CancellationToken cancella return -1; } #endif + + private IDbDataParameter PrepareInsert(IDbCommand cmd, string primaryKeyName) + { + cmd.CommandText += $" returning {EscapeSqlIdentifier(primaryKeyName)} into :newid"; + var param = cmd.CreateParameter(); + param.ParameterName = ":newid"; + param.Value = DBNull.Value; + param.Direction = ParameterDirection.ReturnValue; + param.DbType = DbType.Int64; + cmd.Parameters.Add(param); + return param; + } } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/PostgreSQLDatabaseProvider.cs b/PetaPoco/Providers/PostgreSQLDatabaseProvider.cs index 699874fd..0531b925 100644 --- a/PetaPoco/Providers/PostgreSQLDatabaseProvider.cs +++ b/PetaPoco/Providers/PostgreSQLDatabaseProvider.cs @@ -8,16 +8,22 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for PostgreSQL. + /// public class PostgreSQLDatabaseProvider : DatabaseProvider { + /// public override bool HasNativeGuidSupport => true; + /// public override DbProviderFactory GetFactory() => GetFactory("Npgsql.NpgsqlFactory, Npgsql, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7"); - public override string GetExistsSql() - => "SELECT CASE WHEN EXISTS(SELECT 1 FROM {0} WHERE {1}) THEN 1 ELSE 0 END"; + /// + public override string GetExistsSql() => "SELECT CASE WHEN EXISTS(SELECT 1 FROM {0} WHERE {1}) THEN 1 ELSE 0 END"; + /// public override object MapParameterValue(object value) { // Don't map bools to ints in PostgreSQL @@ -27,9 +33,10 @@ public override object MapParameterValue(object value) return base.MapParameterValue(value); } - public override string EscapeSqlIdentifier(string sqlIdentifier) - => $"\"{sqlIdentifier}\""; + /// + public override string EscapeSqlIdentifier(string sqlIdentifier) => $"\"{sqlIdentifier}\""; + /// public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -41,7 +48,9 @@ public override object ExecuteInsert(Database db, IDbCommand cmd, string primary ExecuteNonQueryHelper(db, cmd); return -1; } + #if ASYNC + /// public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -55,4 +64,4 @@ public override async Task ExecuteInsertAsync(CancellationToken cancella } #endif } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/SQLiteDatabaseProvider.cs b/PetaPoco/Providers/SQLiteDatabaseProvider.cs index b1783f4b..b4b3d204 100644 --- a/PetaPoco/Providers/SQLiteDatabaseProvider.cs +++ b/PetaPoco/Providers/SQLiteDatabaseProvider.cs @@ -6,11 +6,16 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for SQLite. + /// public class SQLiteDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("System.Data.SQLite.SQLiteFactory, System.Data.SQLite", "Microsoft.Data.Sqlite.SqliteFactory, Microsoft.Data.Sqlite"); + /// public override object MapParameterValue(object value) { if (value is uint u) @@ -19,6 +24,7 @@ public override object MapParameterValue(object value) return base.MapParameterValue(value); } + /// public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -32,7 +38,7 @@ public override object ExecuteInsert(Database db, IDbCommand cmd, string primary } #if ASYNC - + /// public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { if (primaryKeyName != null) @@ -44,10 +50,9 @@ public override async Task ExecuteInsertAsync(CancellationToken cancella await ExecuteNonQueryHelperAsync(cancellationToken, db, cmd); return -1; } - #endif - public override string GetExistsSql() - => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; + /// + public override string GetExistsSql() => "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/SqlServerCEDatabaseProviders.cs b/PetaPoco/Providers/SqlServerCEDatabaseProviders.cs index 2f07fd25..8f320c96 100644 --- a/PetaPoco/Providers/SqlServerCEDatabaseProviders.cs +++ b/PetaPoco/Providers/SqlServerCEDatabaseProviders.cs @@ -9,11 +9,16 @@ namespace PetaPoco.Providers { // TODO: Plural class name? There may be multiple providers this handles, but each instance is still only one. + /// + /// Provides a specific implementation of the class for SQL Server CE. + /// public class SqlServerCEDatabaseProviders : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Culture=neutral, PublicKeyToken=89845dcd8080cc91"); + /// public override string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) { if (string.IsNullOrEmpty(parts.SqlOrderBy)) @@ -23,6 +28,7 @@ public override string BuildPageQuery(long skip, long take, SQLParts parts, ref return sqlPage; } + /// public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) { ExecuteNonQueryHelper(db, cmd); @@ -30,6 +36,7 @@ public override object ExecuteInsert(Database db, IDbCommand cmd, string primary } #if ASYNC + /// public override async Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) { await ExecuteNonQueryHelperAsync(cancellationToken, db, cmd); diff --git a/PetaPoco/Providers/SqlServerDatabaseProvider.cs b/PetaPoco/Providers/SqlServerDatabaseProvider.cs index 6eef6baa..fb7b0167 100644 --- a/PetaPoco/Providers/SqlServerDatabaseProvider.cs +++ b/PetaPoco/Providers/SqlServerDatabaseProvider.cs @@ -8,11 +8,16 @@ namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for SQL Server. + /// public class SqlServerDatabaseProvider : DatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("System.Data.SqlClient.SqlClientFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + /// public override string BuildPageQuery(long skip, long take, SQLParts parts, ref object[] args) { var helper = (PagingHelper) PagingUtility; @@ -36,18 +41,22 @@ public override string BuildPageQuery(long skip, long take, SQLParts parts, ref return sqlPage; } + /// public override object ExecuteInsert(Database db, IDbCommand cmd, string primaryKeyName) => ExecuteScalarHelper(db, cmd); +#if ASYNC + /// + public override Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) + => ExecuteScalarHelperAsync(cancellationToken, db, cmd); +#endif + + /// public override string GetExistsSql() => "IF EXISTS (SELECT 1 FROM {0} WHERE {1}) SELECT 1 ELSE SELECT 0"; + /// public override string GetInsertOutputClause(string primaryKeyName) => $" OUTPUT INSERTED.[{primaryKeyName}]"; - -#if ASYNC - public override Task ExecuteInsertAsync(CancellationToken cancellationToken, Database db, IDbCommand cmd, string primaryKeyName) - => ExecuteScalarHelperAsync(cancellationToken, db, cmd); -#endif } -} \ No newline at end of file +} diff --git a/PetaPoco/Providers/SqlServerMsDataDatabaseProvider.cs b/PetaPoco/Providers/SqlServerMsDataDatabaseProvider.cs index 67553b14..c1e3404c 100644 --- a/PetaPoco/Providers/SqlServerMsDataDatabaseProvider.cs +++ b/PetaPoco/Providers/SqlServerMsDataDatabaseProvider.cs @@ -1,10 +1,18 @@ using System.Data.Common; +using PetaPoco.Core; namespace PetaPoco.Providers { + /// + /// Provides a specific implementation of the class for Microsoft.Data.SqlClient. + /// + /// + /// This class serves as a wrapper for the class. + /// public class SqlServerMsDataDatabaseProvider : SqlServerDatabaseProvider { + /// public override DbProviderFactory GetFactory() => GetFactory("Microsoft.Data.SqlClient.SqlClientFactory, Microsoft.Data.SqlClient"); } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/AsyncReader.cs b/PetaPoco/Utilities/AsyncReader.cs index c57b840c..4899a5b3 100644 --- a/PetaPoco/Utilities/AsyncReader.cs +++ b/PetaPoco/Utilities/AsyncReader.cs @@ -6,31 +6,44 @@ namespace PetaPoco.Utilities { #if ASYNC + /// + /// Represents an asynchronous reader that reads a sequence of rows from a data source. + /// + /// The POCO type representing a single result record. public class AsyncReader : IAsyncReader { private readonly bool _isAsync; private readonly Func _pocoFactory; - private IDbCommand _cmd; private IDatabase _db; + private IDbCommand _cmd; private IDataReader _reader; - private DbDataReader Reader => (DbDataReader) _reader; + private DbDataReader Reader => (DbDataReader)_reader; private AsyncReader() { } - public AsyncReader(IDatabase db, IDbCommand cmd, IDataReader reader, Func pocoFactory) + /// + /// Initializes a new instance of the AsyncReader class. + /// + /// The database instance this AsyncReader is associated with. + /// The database query command to execute. + /// The underlying data reader for reading the result sets. + /// The factory function to use for creating POCOs of type . + public AsyncReader(IDatabase database, IDbCommand command, IDataReader reader, Func pocoFactory) { - _db = db; - _cmd = cmd; + _db = database; + _cmd = command; _reader = reader; _pocoFactory = pocoFactory; _isAsync = reader is DbDataReader; } + /// public T Poco { get; private set; } - public async Task ReadAsync() + /// + public async Task ReadAsync() { if (_reader == null) return false; @@ -45,6 +58,12 @@ public async Task ReadAsync() return hasRecords; } + // TODO: Not implemented: `ReadAsync(CancellationToken)` + + /// + /// Disposes the AsyncReader, closing and releasing the underlying , , and shared + /// . + /// public void Dispose() { _reader?.Dispose(); @@ -57,8 +76,11 @@ public void Dispose() _db = null; } - public static AsyncReader Empty() - => new AsyncReader(); + /// + /// Returns an empty AsyncReader. + /// + /// An empty, uninitialized AsyncReader of type . + public static AsyncReader Empty() => new AsyncReader(); } #endif -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/AutoSelectHelper.cs b/PetaPoco/Utilities/AutoSelectHelper.cs index 7a18f63d..1d85257d 100644 --- a/PetaPoco/Utilities/AutoSelectHelper.cs +++ b/PetaPoco/Utilities/AutoSelectHelper.cs @@ -9,7 +9,8 @@ internal static class AutoSelectHelper private static Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL|WITH|SET|DECLARE)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - private static Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); + private static Regex rxFrom = new Regex(@"\A\s*FROM\s", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); public static string AddSelectClause(IProvider provider, string sql, IMapper defaultMapper) { @@ -30,4 +31,4 @@ public static string AddSelectClause(IProvider provider, string sql, IMapper return sql; } } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/Cache.cs b/PetaPoco/Utilities/Cache.cs index 1572e36b..0dfe8943 100644 --- a/PetaPoco/Utilities/Cache.cs +++ b/PetaPoco/Utilities/Cache.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; namespace PetaPoco.Internal { @@ -19,4 +17,4 @@ public TValue GetOrAdd(TKey key, Func factory) public void Flush() => _map.Clear(); } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/IPagingHelper.cs b/PetaPoco/Utilities/IPagingHelper.cs index 068f0e62..077c9887 100644 --- a/PetaPoco/Utilities/IPagingHelper.cs +++ b/PetaPoco/Utilities/IPagingHelper.cs @@ -1,16 +1,18 @@ namespace PetaPoco.Utilities { /// - /// Represents the contract for a paging helper. + /// Represents the contract for a paging helper. /// public interface IPagingHelper { /// - /// Splits the given into ; + /// Splits the given SQL statement into parts, and initializes an instance containing the resulting + /// substrings at . /// - /// The SQL to split. - /// The SQL parts. - /// True if the SQL could be split; else, False. + /// The SQL string to be parsed. This value must not be . + /// When this method returns, the parsed SQL statement split into it's constituent parts, if the SQL statement + /// could be split; otherwise, . This parameter is passed uninitialized. + /// if the SQL statement could be parsed; otherwise, . bool SplitSQL(string sql, out SQLParts parts); } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/PagingHelper.cs b/PetaPoco/Utilities/PagingHelper.cs index 3dd11a70..f90fb545 100644 --- a/PetaPoco/Utilities/PagingHelper.cs +++ b/PetaPoco/Utilities/PagingHelper.cs @@ -2,43 +2,101 @@ namespace PetaPoco.Utilities { + /// + /// Provides utility methods for splitting SQL queries into parts and handling paging. + /// public class PagingHelper : IPagingHelper { - public Regex RegexColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(? + /// Gets the regular expression used for matching the SELECT clause. + /// + /// + /// Beginning at the start of the string, this expression matches the keyword SELECT (inclusive), followed by one or + /// more spaces, capturing everything in front of the word FROM (exclusive), accounting for special handling of nested + /// parentheses, aggregate functions, etc. + /// SELECT column1, column2 FROM tbl;
+ /// SELECT SUM(column1) AS sum_col1, column2 FROM tbl; + ///
+ public Regex RegexColumns { get; } = new Regex( + @"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", - RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + /// + /// Gets the regular expression used for matching the DISTINCT keyword. + /// + /// + /// Starting at the beginning of the string, this expression performs a simple match for the DISTINCT keyword in an SQL + /// statement, followed by a space. Everything after is excluded. + /// SELECT DISTINCT * FROM tbl;
+ /// SELECT DISTINCT column1 FROM tbl; + ///
+ public Regex RegexDistinct { get; } = new Regex(@"\ADISTINCT\s", + RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public Regex SimpleRegexOrderBy = new Regex(@"\bORDER\s+BY\s+", + /// + /// Gets the regular expression used for matching the ORDER BY clause. + /// + /// + /// This expression matches the ORDER BY clause, followed by one or more column names as well as the accompanying + /// optional sort order modifier keywords, ASC and DESC, for each (inclusive). AS alias declarations are + /// excluded from the match. + /// SELECT * FROM tbl ORDER BY column1, column2 DESC;
+ /// SELECT column1, column2 AS col2 ORDER BY SUM(column1) ASC, col2;
+ ///
+ public Regex RegexOrderBy { get; } = new Regex( + @"\bORDER\s+BY\s+(?!.*?(?:\)|\s+)AS\s)(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public Regex RegexGroupBy = new Regex(@"\bGROUP\s+BY\s+(?!.*?(?:\)|\s+)AS\s)(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+?)*", - RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + /// + /// Gets the regular expression used for matching the ORDER BY keyword. + /// + /// + /// This expression performs a simple match for the ORDER BY keyword in a SQL statement, followed by a space. + /// Everything after is excluded. + /// SELECT * FROM tbl ORDER BY column1, column2 DESC;
+ /// SELECT column1, column2 AS col2 ORDER BY SUM(column1) ASC, col2; + ///
+ public Regex SimpleRegexOrderBy { get; } = new Regex(@"\bORDER\s+BY\s+", + RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public Regex SimpleRegexGroupBy = new Regex(@"\bGROUP\s+BY\s+", - RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + /// + /// Gets the regular expression used for matching the GROUP BY clause. + /// + /// + /// Matches the GROUP BY clause, followed by one or more column names along with any aggregate functions or + /// expressions. AS alias declarations are excluded from the match. + /// SELECT * FROM tbl GROUP BY column1, column2;
+ /// SELECT column1, column2 GROUP BY SUM(column1), column2;
+ /// SELECT column1 AS col1 FROM tbl GROUP BY col1 ORDER BY col1; + ///
+ public Regex RegexGroupBy { get; } = new Regex( + @"\bGROUP\s+BY\s+(?!.*?(?:\)|\s+)AS\s)(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\[\]`""\w\(\)\.])+?)*", + RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + /// + /// Gets the regular expression used for matching the GROUP BY keyword. + /// + /// + /// This expression performs a simple match for the GROUP BY keyword in a SQL statement, followed by a space. + /// Everything after is excluded. + /// SELECT * FROM tbl GROUP BY column1, column2;
+ /// SELECT column1, COUNT(column2) AS count_col2 FROM tbl GROUP BY column1 ORDER BY column1; + ///
+ public Regex SimpleRegexGroupBy { get; } = new Regex(@"\bGROUP\s+BY\s+", + RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + /// + /// Gets the singleton instance of the class. + /// public static IPagingHelper Instance { get; private set; } - static PagingHelper() - { - Instance = new PagingHelper(); - } + static PagingHelper() => Instance = new PagingHelper(); - /// - /// Splits the given into ; - /// - /// The SQL to split. - /// The SQL parts. - /// True if the SQL could be split; else, False. + /// public bool SplitSQL(string sql, out SQLParts parts) { + // TODO: add String.IsNullOrWhiteSpace() check for sql param + // TODO: initialize parts in this method parts.Sql = sql; parts.SqlSelectRemoved = null; parts.SqlCount = sql; @@ -73,4 +131,4 @@ public bool SplitSQL(string sql, out SQLParts parts) return true; } } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/ParametersHelper.cs b/PetaPoco/Utilities/ParametersHelper.cs index 2d02e68f..a6336ba0 100644 --- a/PetaPoco/Utilities/ParametersHelper.cs +++ b/PetaPoco/Utilities/ParametersHelper.cs @@ -1,39 +1,70 @@ -using PetaPoco.Core; +using PetaPoco.Core; using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Reflection; using System.Text; using System.Text.RegularExpressions; namespace PetaPoco.Internal { + /// + /// Provides static utility methods and extensions for handling SQL parameters. + /// + /// + /// This class includes extensions for validating and replacing parameter prefixes, as well as static methods used for processing + /// parameters for queries and stored procedures. + /// internal static class ParametersHelper { private static Regex ParamPrefixRegex = new Regex(@"(? paramPrefix + m.Value.Substring(1)); - } - - public static string EnsureParamPrefix(this int input, string paramPrefix) - => $"{paramPrefix}{input}"; - - public static string EnsureParamPrefix(this string input, string paramPrefix) - { - if (input.StartsWith(paramPrefix)) - return input; - else - return NonWordStartRegex.Replace(input, paramPrefix); - } - - // Helper to handle named parameters from object properties - public static string ProcessQueryParams(string sql, object[] args_src, List args_dest) + /// + /// Replaces all parameter prefixes in the provided SQL statement with the specified replacement string. + /// + /// The SQL statement. + /// The replacement parameter prefix. + /// The SQL statement with the parameter prefixes replaced. + public static string ReplaceParamPrefix(this string sql, string replacementPrefix) + => ParamPrefixRegex.Replace(sql, m => replacementPrefix + m.Value.Substring(1)); + + /// + /// Ensures that the provided SQL parameter number is prefixed with the specified prefix string. + /// + /// The parameter number. + /// The prefix string. + /// The parameter number, converted to a string and appended to the specified prefix. + public static string EnsureParamPrefix(this int value, string paramPrefix) + => $"{paramPrefix}{value}"; + + /// + /// Ensures that the provided SQL parameter string is prefixed with the specified prefix string. + /// + /// The parameter name. + /// The prefix string. + /// The parameter name appended to the specified prefix string. + public static string EnsureParamPrefix(this string value, string paramPrefix) + => value.StartsWith(paramPrefix) ? value : NonWordStartRegex.Replace(value, paramPrefix); + + /// + /// Processes the parameters for an SQL statement. + /// + /// + /// Helper method for processing named parameters from object properties. + /// + /// The SQL statement. + /// The source arguments to be processed. + /// The destination list to store the processed arguments. + /// The SQL statement with the parameters processed. + /// The number of parameters is less than the count of numbered parameters in the SQL + /// string. + /// None of the passed parameters have a property with the name used as a named + /// parameter. + public static string ProcessQueryParams(string sql, object[] srcArgs, List destArgs) { + // TODO: Use same collection type for srcArgs and destArgs (`object[]` vs `List`) return ParamPrefixRegex.Replace(sql, m => { string param = m.Value.Substring(1); @@ -44,17 +75,16 @@ public static string ProcessQueryParams(string sql, object[] args_src, List= args_src.Length) - throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, - sql)); - arg_val = args_src[paramIndex]; + if (paramIndex < 0 || paramIndex >= srcArgs.Length) + throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, srcArgs.Length, sql)); + arg_val = srcArgs[paramIndex]; } else { bool found = false; arg_val = null; - foreach (var o in args_src) + foreach (var o in srcArgs) { if (o is IDictionary dict) { @@ -64,7 +94,7 @@ public static string ProcessQueryParams(string sql, object[] args_src, List setParameterProperties) + /// + /// Processes the parameters for a stored procedure command. + /// + /// The SQL command representing the stored procedure. + /// The arguments to be processed. + /// An action used to set the database parameter properties. + /// An array of database parameters processed from the input arguments. + /// Value type or string passed as stored procedure argument. + public static object[] ProcessStoredProcParams(IDbCommand cmd, object[] args, Action setPropertiesAction) { // For a stored proc, we assume that we're only getting POCOs or parameters var result = new List(); @@ -138,13 +170,14 @@ void ProcessArg(object arg) else { var type = arg.GetType(); + // TODO: Include second param `paramName: nameof(args)` in thrown ArgumentException if (type.IsValueType || type == typeof(string)) throw new ArgumentException($"Value type or string passed as stored procedure argument: {arg}"); var readableProps = type.GetProperties().Where(p => p.CanRead); foreach (var prop in readableProps) { AddParameter(prop.Name, prop.GetValue(arg, null)); - } + } } } @@ -152,7 +185,7 @@ void AddParameter(string name, object value) { var param = cmd.CreateParameter(); param.ParameterName = name; - setParameterProperties(param, value, null); + setPropertiesAction(param, value, null); result.Add(param); } @@ -163,5 +196,8 @@ void AddParameter(string name, object value) return result.ToArray(); } + + private static bool IsEnumerable(this object value) + => (value as IEnumerable) != null && (value as string) == null && (value as byte[]) == null; } -} \ No newline at end of file +} diff --git a/PetaPoco/Utilities/SQLParts.cs b/PetaPoco/Utilities/SQLParts.cs index 8386e919..f90e07d5 100644 --- a/PetaPoco/Utilities/SQLParts.cs +++ b/PetaPoco/Utilities/SQLParts.cs @@ -1,28 +1,28 @@ -namespace PetaPoco.Utilities +namespace PetaPoco.Utilities { /// - /// Presents the SQL parts. + /// Represents a parsed SQL statement, providing access to its constituent parts for convenient modification and rebuilding. /// public struct SQLParts { /// - /// The SQL. + /// Gets or sets the complete SQL statement. /// public string Sql; /// - /// The SQL count. + /// Gets or sets the COUNT clause of the SQL statement, used for operations such as Exists and paged requests. /// public string SqlCount; /// - /// The SQL Select + /// Gets or sets the SQL statement with the SELECT clause removed, for generating auto-select queries. /// public string SqlSelectRemoved; /// - /// The SQL Order By + /// Gets or sets the ORDER BY clause of the SQL statement, used for sorting the records. /// public string SqlOrderBy; } -} \ No newline at end of file +}