diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..85da627
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,191 @@
+###############################
+# PandaTech Editor Config #
+###############################
+
+
+################################
+# ReSharper Generated Settings #
+################################
+
+[*]
+charset = utf-8-bom
+end_of_line = crlf
+trim_trailing_whitespace = false
+insert_final_newline = false
+indent_style = space
+indent_size = 4
+
+# Microsoft .NET properties
+csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True
+dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field
+dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
+dotnet_naming_rule.unity_serialized_field_rule.severity = warning
+dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
+dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
+dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
+dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
+dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
+dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+# ReSharper properties
+resharper_align_linq_query = true
+resharper_align_multiline_calls_chain = true
+resharper_apply_auto_detected_rules = false
+resharper_braces_for_for = required
+resharper_braces_for_foreach = required
+resharper_braces_for_ifelse = required
+resharper_braces_for_while = required
+resharper_cpp_insert_final_newline = true
+resharper_csharp_indent_size = 3
+resharper_csharp_max_enum_members_on_line = 0
+resharper_csharp_tab_width = 3
+resharper_csharp_wrap_arguments_style = chop_if_long
+resharper_csharp_wrap_parameters_style = chop_if_long
+resharper_formatter_off_tag = @formatter:off
+resharper_formatter_on_tag = @formatter:on
+resharper_formatter_tags_enabled = true
+resharper_keep_existing_declaration_parens_arrangement = false
+resharper_keep_existing_expr_member_arrangement = false
+resharper_keep_existing_initializer_arrangement = false
+resharper_keep_existing_switch_expression_arrangement = false
+resharper_max_array_initializer_elements_on_line = 0
+resharper_max_initializer_elements_on_line = 0
+resharper_place_accessorholder_attribute_on_same_line = false
+resharper_place_accessor_attribute_on_same_line = false
+resharper_place_field_attribute_on_same_line = false
+resharper_place_simple_anonymousmethod_on_single_line = false
+resharper_place_simple_embedded_statement_on_same_line = false
+resharper_place_simple_initializer_on_single_line = false
+resharper_place_simple_property_pattern_on_single_line = false
+resharper_use_indent_from_vs = false
+resharper_wrap_after_property_in_chained_method_calls = true
+resharper_wrap_array_initializer_style = chop_if_long
+resharper_wrap_chained_method_calls = chop_always
+resharper_wrap_linq_expressions = chop_always
+resharper_wrap_list_pattern = chop_if_long
+resharper_wrap_object_and_collection_initializer_style = chop_always
+resharper_wrap_property_pattern = chop_always
+
+# ReSharper inspection severities
+resharper_arrange_redundant_parentheses_highlighting = hint
+resharper_arrange_this_qualifier_highlighting = hint
+resharper_arrange_type_member_modifiers_highlighting = hint
+resharper_arrange_type_modifiers_highlighting = hint
+resharper_built_in_type_reference_style_for_member_access_highlighting = hint
+resharper_built_in_type_reference_style_highlighting = hint
+resharper_enforce_foreach_statement_braces_highlighting = warning
+resharper_enforce_for_statement_braces_highlighting = warning
+resharper_enforce_if_statement_braces_highlighting = warning
+resharper_enforce_lock_statement_braces_highlighting = warning
+resharper_enforce_using_statement_braces_highlighting = warning
+resharper_enforce_while_statement_braces_highlighting = warning
+resharper_mvc_action_not_resolved_highlighting = warning
+resharper_mvc_area_not_resolved_highlighting = warning
+resharper_mvc_controller_not_resolved_highlighting = warning
+resharper_mvc_masterpage_not_resolved_highlighting = warning
+resharper_mvc_partial_view_not_resolved_highlighting = warning
+resharper_mvc_template_not_resolved_highlighting = warning
+resharper_mvc_view_component_not_resolved_highlighting = warning
+resharper_mvc_view_component_view_not_resolved_highlighting = warning
+resharper_mvc_view_not_resolved_highlighting = warning
+resharper_razor_assembly_not_resolved_highlighting = warning
+resharper_redundant_base_qualifier_highlighting = warning
+resharper_web_config_module_not_resolved_highlighting = warning
+resharper_web_config_type_not_resolved_highlighting = warning
+resharper_web_config_wrong_module_highlighting = warning
+
+[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,bowerrc,jest.config}]
+indent_style = space
+indent_size = 2
+
+[{*.yaml,*.yml}]
+indent_style = space
+indent_size = 2
+
+[*.cs]
+indent_style = space
+indent_size = 3
+tab_width = 3
+
+[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,c++m,cc,ccm,cginc,compute,cp,cpp,cppm,cshtml,cu,cuh,cxx,cxxm,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,mxx,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,uxml,vb,xaml,xamlx,xoml,xsd}]
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+#########################
+# Custom added settings #
+#########################
+[*.cs]
+dotnet_diagnostic.cs8600.severity = error #Converting null literal or possible null value to non-nullable type.
+dotnet_diagnostic.cs8601.severity = error #Possible null reference assignment.
+dotnet_diagnostic.cs8602.severity = error #possible dereference of a null reference
+dotnet_diagnostic.cs8603.severity = error #possible null reference return
+dotnet_diagnostic.cs8604.severity = error #possible null reference argument for parameter
+dotnet_diagnostic.cs8605.severity = error #Unboxing a possibly null value
+dotnet_diagnostic.cs8618.severity = error # Non-nullable field is uninitialized. Consider declaring as nullable.
+dotnet_diagnostic.cs8625.severity = error # Cannot convert null literal to non-nullable reference type.
+dotnet_diagnostic.cs8762.severity = error # Nullability of reference types in type doesn't match overridden member.
+dotnet_diagnostic.cs1717.severity = error #variable is assigned to itself
+dotnet_diagnostic.cs1718.severity = error #comparison made to same variable
+dotnet_diagnostic.cs0659.severity = error #overriding object.Equals but not overriding object.GetHashCode
+dotnet_diagnostic.cs0251.severity = error #Indexing an array with a negative index (array indices always start at zero)
+dotnet_diagnostic.s3363.severity = none #Never set DateTime as PrimaryKey. Ignored as we never do it but have warnings for cache entities.
+dotnet_diagnostic.ca2016.severity = error #Forwarding cancellation tokens
+csharp_style_namespace_declarations = file_scoped:error
+resharper_entity_framework_model_validation_unlimited_string_length_highlighting = none
+
+
+#########################
+# VS added settings #
+#########################
+
+[*.cs]
+csharp_style_namespace_declarations = file_scoped:error
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+
+[{*.yaml,*.yml}]
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 3
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+
+[*.csv]
+indent_style = tab
+tab_width = 4
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b4fbd90..6c1dd43 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,10 +16,10 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup .NET Core
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
diff --git a/EFCore.PostgresExtensions.sln b/EFCore.PostgresExtensions.sln
index 4ac374d..060316c 100644
--- a/EFCore.PostgresExtensions.sln
+++ b/EFCore.PostgresExtensions.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitignore = .gitignore
Readme.md = Readme.md
global.json = global.json
+ .editorconfig = .editorconfig
EndProjectSection
EndProject
Global
diff --git a/Readme.md b/Readme.md
index 98a3e44..6237ffe 100644
--- a/Readme.md
+++ b/Readme.md
@@ -116,10 +116,12 @@ public partial class PgFunction : Migration
}
}
```
+
#### Additional notes
-- The random incrementing sequence feature ensures the generated IDs are unique, non-sequential, and non-predictable, enhancing security.
-- The feature supports only `long` data type (`bigint` in PostgreSQL).
+- The random incrementing sequence feature ensures the generated IDs are unique, non-sequential, and non-predictable,
+ enhancing security.
+- The feature supports only `long` data type (`bigint` in PostgreSQL).
### Npgsql COPY Integration (Obsolete: Use EFCore.BulkExtensions.PostgreSql instead)
diff --git a/global.json b/global.json
index c96d045..2bc13e8 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.200",
- "rollForward": "latestMajor"
+ "version": "9.0.100",
+ "rollForward": "latestMinor"
}
}
diff --git a/src/EFCore.PostgresExtensions/EFCore.PostgresExtensions.csproj b/src/EFCore.PostgresExtensions/EFCore.PostgresExtensions.csproj
index 7bcd323..af36456 100644
--- a/src/EFCore.PostgresExtensions/EFCore.PostgresExtensions.csproj
+++ b/src/EFCore.PostgresExtensions/EFCore.PostgresExtensions.csproj
@@ -1,20 +1,20 @@
- net8.0
+ net9.0
enable
enable
pandatech.png
Readme.md
Pandatech
MIT
- 3.0.0
+ 4.0.0
Pandatech.EFCore.PostgresExtensions
Pandatech.EFCore.PostgresExtensions
Pandatech, library, EntityFrameworkCore, PostgreSQL, For Update, Lock, LockingSyntax, Bulk insert, BinaryCopy
The Pandatech.EFCore.PostgresExtensions library enriches Entity Framework Core applications with advanced PostgreSQL functionalities, starting with the ForUpdate locking syntax and BulkInsert function. Designed for seamless integration, this NuGet package aims to enhance the efficiency and capabilities of EF Core models when working with PostgreSQL, with the potential for further PostgreSQL-specific extensions.
https://github.com/PandaTechAM/be-lib-efcore-postgres-extensions
- Random Incrementing Sequence Generation Feature
+ .net 9 upgrade
@@ -23,7 +23,7 @@
-
+
diff --git a/src/EFCore.PostgresExtensions/Enums/LockBehavior.cs b/src/EFCore.PostgresExtensions/Enums/LockBehavior.cs
index 286889c..d4c964c 100644
--- a/src/EFCore.PostgresExtensions/Enums/LockBehavior.cs
+++ b/src/EFCore.PostgresExtensions/Enums/LockBehavior.cs
@@ -1,31 +1,33 @@
-namespace EFCore.PostgresExtensions.Enums
-{
- public enum LockBehavior
- {
- ///
- /// Using this behavior forces transaction to wait until row is unlocked.
- ///
- Default = 0,
+namespace EFCore.PostgresExtensions.Enums;
- ///
- /// Using this behavior will skip rows that are locked by another transaction.
- ///
- SkipLocked = 1,
+public enum LockBehavior
+{
+ ///
+ /// Using this behavior forces transaction to wait until row is unlocked.
+ ///
+ Default = 0,
- ///
- /// Using this behavior will throw an exception if requested rows are locked by another transaction.
- ///
- NoWait = 2,
- }
+ ///
+ /// Using this behavior will skip rows that are locked by another transaction.
+ ///
+ SkipLocked = 1,
- public static class LockBehaviorExtensions
- {
- public static string GetSqlKeyword(this LockBehavior lockBehavior) => lockBehavior switch
- {
- LockBehavior.Default => string.Empty,
- LockBehavior.SkipLocked => "skip locked",
- LockBehavior.NoWait => "nowait",
- _ => string.Empty,
- };
- }
+ ///
+ /// Using this behavior will throw an exception if requested rows are locked by another transaction.
+ ///
+ NoWait = 2
}
+
+public static class LockBehaviorExtensions
+{
+ public static string GetSqlKeyword(this LockBehavior lockBehavior)
+ {
+ return lockBehavior switch
+ {
+ LockBehavior.Default => string.Empty,
+ LockBehavior.SkipLocked => "skip locked",
+ LockBehavior.NoWait => "nowait",
+ _ => string.Empty
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/EFCore.PostgresExtensions/Extensions/BulkInsertExtension/BulkInsertExtensionSync.cs b/src/EFCore.PostgresExtensions/Extensions/BulkInsertExtension/BulkInsertExtensionSync.cs
index d62f61d..4f42c9a 100644
--- a/src/EFCore.PostgresExtensions/Extensions/BulkInsertExtension/BulkInsertExtensionSync.cs
+++ b/src/EFCore.PostgresExtensions/Extensions/BulkInsertExtension/BulkInsertExtensionSync.cs
@@ -119,13 +119,19 @@ private static void ConvertEnumValue(int columnCount,
if (!propertyTypes[i].IsGenericType || propertyTypes[i]
.GetGenericTypeDefinition() != typeof(List<>) ||
!propertyTypes[i]
- .GetGenericArguments()[0].IsEnum) continue;
+ .GetGenericArguments()[0].IsEnum)
+ {
+ continue;
+ }
var enumMapping = properties[i]
.FindTypeMapping();
// Only proceed if the mapping is for an array type, as expected for lists
- if (enumMapping is not NpgsqlArrayTypeMapping) continue;
+ if (enumMapping is not NpgsqlArrayTypeMapping)
+ {
+ continue;
+ }
var list = (IList)values[i]!;
var underlyingType = Enum.GetUnderlyingType(propertyTypes[i]
@@ -153,9 +159,14 @@ private static DbContext PrepareBulkInsertOperation(DbSet dbSet,
if (entities == null || entities.Count == 0)
+ {
throw new ArgumentException("The model list cannot be null or empty.");
+ }
- if (context == null) throw new ArgumentNullException(nameof(context), "The DbContext instance cannot be null.");
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context), "The DbContext instance cannot be null.");
+ }
var entityType = context.Model.FindEntityType(typeof(T))! ??
@@ -168,14 +179,18 @@ private static DbContext PrepareBulkInsertOperation(DbSet dbSet,
.ToList();
if (pkGeneratedByDb)
+ {
properties = properties.Where(x => !x.IsKey())
.ToList();
+ }
var columnNames = properties.Select(x => $"\"{x.GetColumnName()}\"")
.ToList();
if (columnNames.Count == 0)
+ {
throw new InvalidOperationException("Column names are null or empty.");
+ }
columnCount = columnNames.Count;
diff --git a/src/EFCore.PostgresExtensions/Extensions/DbSetExtensions.cs b/src/EFCore.PostgresExtensions/Extensions/DbSetExtensions.cs
index ecd9398..591eff9 100644
--- a/src/EFCore.PostgresExtensions/Extensions/DbSetExtensions.cs
+++ b/src/EFCore.PostgresExtensions/Extensions/DbSetExtensions.cs
@@ -5,11 +5,11 @@ namespace EFCore.PostgresExtensions.Extensions;
public static class DbSetExtensions
{
- public static DbContext GetDbContext(this DbSet dbSet) where T : class
- {
- var infrastructure = dbSet as IInfrastructure;
- var serviceProvider = infrastructure.Instance;
- var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext;
- return currentDbContext.Context;
- }
+ public static DbContext GetDbContext(this DbSet dbSet) where T : class
+ {
+ var infrastructure = dbSet as IInfrastructure;
+ var serviceProvider = infrastructure.Instance;
+ var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext;
+ return currentDbContext!.Context;
+ }
}
\ No newline at end of file
diff --git a/src/EFCore.PostgresExtensions/Extensions/OptionsBuilderExtensions.cs b/src/EFCore.PostgresExtensions/Extensions/OptionsBuilderExtensions.cs
index 9e836be..f41b497 100644
--- a/src/EFCore.PostgresExtensions/Extensions/OptionsBuilderExtensions.cs
+++ b/src/EFCore.PostgresExtensions/Extensions/OptionsBuilderExtensions.cs
@@ -1,15 +1,14 @@
using EFCore.PostgresExtensions.Interceptors;
using Microsoft.EntityFrameworkCore;
-namespace EFCore.PostgresExtensions.Extensions
+namespace EFCore.PostgresExtensions.Extensions;
+
+public static class OptionsBuilderExtensions
{
- public static class OptionsBuilderExtensions
- {
- public static DbContextOptionsBuilder UseQueryLocks(this DbContextOptionsBuilder builder)
- {
- builder.AddInterceptors(new TaggedQueryCommandInterceptor());
+ public static DbContextOptionsBuilder UseQueryLocks(this DbContextOptionsBuilder builder)
+ {
+ builder.AddInterceptors(new TaggedQueryCommandInterceptor());
- return builder;
- }
- }
-}
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/EFCore.PostgresExtensions/Extensions/QueryableExtensions.cs b/src/EFCore.PostgresExtensions/Extensions/QueryableExtensions.cs
index 8c79b12..73af680 100644
--- a/src/EFCore.PostgresExtensions/Extensions/QueryableExtensions.cs
+++ b/src/EFCore.PostgresExtensions/Extensions/QueryableExtensions.cs
@@ -1,26 +1,28 @@
using EFCore.PostgresExtensions.Enums;
using Microsoft.EntityFrameworkCore;
-namespace EFCore.PostgresExtensions.Extensions
+namespace EFCore.PostgresExtensions.Extensions;
+
+public static class QueryableExtensions
{
- public static class QueryableExtensions
- {
- internal const string ForUpdateKey = "for update ";
+ internal const string ForUpdateKey = "for update ";
- ///
- /// Use this method for selecting data with locking.
- /// Attention! Be aware that this method works only inside the transaction scope(dbContext.BeginTransaction) and you need to register it in startup.
- ///
- ///
- /// Query to lock.
- /// Behavior organizes the way data should be locked, for more information check enum values.
- /// The same query with locking behavior added.
- public static IQueryable ForUpdate(this IQueryable query,
- LockBehavior lockBehavior = LockBehavior.Default)
- {
- query = query.TagWith(ForUpdateKey + lockBehavior.GetSqlKeyword());
+ ///
+ /// Use this method for selecting data with locking.
+ ///
+ /// Attention! Be aware that this method works only inside the transaction scope(dbContext.BeginTransaction) and
+ /// you need to register it in startup.
+ ///
+ ///
+ ///
+ /// Query to lock.
+ /// Behavior organizes the way data should be locked, for more information check enum values.
+ /// The same query with locking behavior added.
+ public static IQueryable ForUpdate(this IQueryable query,
+ LockBehavior lockBehavior = LockBehavior.Default)
+ {
+ query = query.TagWith(ForUpdateKey + lockBehavior.GetSqlKeyword());
- return query.AsQueryable();
- }
- }
+ return query.AsQueryable();
+ }
}
\ No newline at end of file
diff --git a/src/EFCore.PostgresExtensions/Helpers/PgFunctionHelpers.cs b/src/EFCore.PostgresExtensions/Helpers/PgFunctionHelpers.cs
index 53b49f0..451ebc4 100644
--- a/src/EFCore.PostgresExtensions/Helpers/PgFunctionHelpers.cs
+++ b/src/EFCore.PostgresExtensions/Helpers/PgFunctionHelpers.cs
@@ -2,7 +2,7 @@
public static class PgFunctionHelpers
{
- public static string GetPgFunction(string tableName,
+ public static string GetPgFunction(string tableName,
string pkName,
long startValue,
int minRandIncrementValue,
@@ -12,47 +12,54 @@ public static string GetPgFunction(string tableName,
var pgFunctionName = GetPgFunctionName(tableName);
return $"""
- -- Create sequence if not exists
- DO $$
- BEGIN
- IF NOT EXISTS (SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = '{sequenceName}') THEN
- CREATE SEQUENCE {sequenceName} START WITH {startValue};
- END IF;
- END
- $$;
-
- -- Create or replace the function
- CREATE OR REPLACE FUNCTION {pgFunctionName}
- RETURNS bigint AS $$
- DECLARE
- current_value bigint;
- increment_value integer;
- new_value bigint;
- BEGIN
- -- Acquire an advisory lock
- PERFORM pg_advisory_lock(1);
-
- -- Get the next value of the sequence atomically
- current_value := nextval('{sequenceName}'); -- name of the sequence
-
- -- Generate a random increment between {minRandIncrementValue} and {maxRandIncrementValue}
- increment_value := floor(random() * ({maxRandIncrementValue} - {minRandIncrementValue} + 1) + {minRandIncrementValue})::integer;
-
- -- Set the new value with the random increment
- new_value := current_value + increment_value;
-
- -- Update the sequence to the new value
- PERFORM setval('{sequenceName}', new_value, true); -- name of the sequence
-
- -- Release the advisory lock
- PERFORM pg_advisory_unlock(1);
-
- RETURN new_value;
- END;
- $$ LANGUAGE plpgsql;
- """;
+ -- Create sequence if not exists
+ DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = '{sequenceName}') THEN
+ CREATE SEQUENCE {sequenceName} START WITH {startValue};
+ END IF;
+ END
+ $$;
+
+ -- Create or replace the function
+ CREATE OR REPLACE FUNCTION {pgFunctionName}
+ RETURNS bigint AS $$
+ DECLARE
+ current_value bigint;
+ increment_value integer;
+ new_value bigint;
+ BEGIN
+ -- Acquire an advisory lock
+ PERFORM pg_advisory_lock(1);
+
+ -- Get the next value of the sequence atomically
+ current_value := nextval('{sequenceName}'); -- name of the sequence
+
+ -- Generate a random increment between {minRandIncrementValue} and {maxRandIncrementValue}
+ increment_value := floor(random() * ({maxRandIncrementValue} - {minRandIncrementValue} + 1) + {minRandIncrementValue})::integer;
+
+ -- Set the new value with the random increment
+ new_value := current_value + increment_value;
+
+ -- Update the sequence to the new value
+ PERFORM setval('{sequenceName}', new_value, true); -- name of the sequence
+
+ -- Release the advisory lock
+ PERFORM pg_advisory_unlock(1);
+
+ RETURN new_value;
+ END;
+ $$ LANGUAGE plpgsql;
+ """;
}
- private static string GetSequenceName(string tableName, string pkName) => $"{tableName}_{pkName}_seq";
- public static string GetPgFunctionName(string tableName) => $"{tableName}_random_id_generator()";
+ private static string GetSequenceName(string tableName, string pkName)
+ {
+ return $"{tableName}_{pkName}_seq";
+ }
+
+ public static string GetPgFunctionName(string tableName)
+ {
+ return $"{tableName}_random_id_generator()";
+ }
}
\ No newline at end of file
diff --git a/src/EFCore.PostgresExtensions/Interceptors/TaggedQueryCommandInterceptor.cs b/src/EFCore.PostgresExtensions/Interceptors/TaggedQueryCommandInterceptor.cs
index d1296ef..cc8f4d3 100644
--- a/src/EFCore.PostgresExtensions/Interceptors/TaggedQueryCommandInterceptor.cs
+++ b/src/EFCore.PostgresExtensions/Interceptors/TaggedQueryCommandInterceptor.cs
@@ -1,40 +1,37 @@
-using EFCore.PostgresExtensions.Extensions;
+using System.Data.Common;
+using EFCore.PostgresExtensions.Extensions;
using Microsoft.EntityFrameworkCore.Diagnostics;
-using System.Data.Common;
-namespace EFCore.PostgresExtensions.Interceptors
+namespace EFCore.PostgresExtensions.Interceptors;
+
+public class TaggedQueryCommandInterceptor : DbCommandInterceptor
{
- public class TaggedQueryCommandInterceptor : DbCommandInterceptor
- {
- public override InterceptionResult ReaderExecuting(
- DbCommand command,
- CommandEventData eventData,
- InterceptionResult result)
- {
- ManipulateCommand(command);
+ public override InterceptionResult ReaderExecuting(DbCommand command,
+ CommandEventData eventData,
+ InterceptionResult result)
+ {
+ ManipulateCommand(command);
- return result;
- }
+ return result;
+ }
- public override ValueTask> ReaderExecutingAsync(
- DbCommand command,
- CommandEventData eventData,
- InterceptionResult result,
- CancellationToken cancellationToken = default)
- {
- ManipulateCommand(command);
+ public override ValueTask> ReaderExecutingAsync(DbCommand command,
+ CommandEventData eventData,
+ InterceptionResult result,
+ CancellationToken cancellationToken = default)
+ {
+ ManipulateCommand(command);
- return new ValueTask>(result);
- }
+ return new ValueTask>(result);
+ }
- private static void ManipulateCommand(DbCommand command)
- {
- if (command.CommandText.StartsWith($"-- {QueryableExtensions.ForUpdateKey}", StringComparison.Ordinal))
- {
- var tagEndIndex = command.CommandText.IndexOf('\n');
+ private static void ManipulateCommand(DbCommand command)
+ {
+ if (command.CommandText.StartsWith($"-- {QueryableExtensions.ForUpdateKey}", StringComparison.Ordinal))
+ {
+ var tagEndIndex = command.CommandText.IndexOf('\n');
- command.CommandText += command.CommandText[2..tagEndIndex];
- }
- }
- }
-}
+ command.CommandText += command.CommandText[2..tagEndIndex];
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/PandaNuGet.Demo/PandaNuGet.Demo.csproj b/test/PandaNuGet.Demo/PandaNuGet.Demo.csproj
index fb3aa9b..862895c 100644
--- a/test/PandaNuGet.Demo/PandaNuGet.Demo.csproj
+++ b/test/PandaNuGet.Demo/PandaNuGet.Demo.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
enable
enable
true
@@ -9,11 +9,11 @@
-
-
-
+
+
+
-
+
diff --git a/test/PandaNuGet.Demo/Services/GetByFirstBytesService.cs b/test/PandaNuGet.Demo/Services/GetByFirstBytesService.cs
index 4309af4..f3c4b28 100644
--- a/test/PandaNuGet.Demo/Services/GetByFirstBytesService.cs
+++ b/test/PandaNuGet.Demo/Services/GetByFirstBytesService.cs
@@ -3,111 +3,116 @@
using PandaNuGet.Demo.Context;
using PandaNuGet.Demo.Entities;
using Pandatech.Crypto;
+using Pandatech.Crypto.Helpers;
using PostgresDbContext = PandaNuGet.Demo.Context.PostgresDbContext;
namespace PandaNuGet.Demo.Services;
public class GetByFirstBytesService(PostgresContext context)
{
- private static byte[] DocumentNumber => Sha3.Hash("1234567890");
-
- public async Task SeedUser()
- {
- var user = new UserEntity();
- var documentNumber = DocumentNumber;
- var randomBytes = new byte[10];
- documentNumber = documentNumber.Concat(randomBytes).ToArray();
- user.Document = documentNumber;
- context.Users.Add(user);
- await context.SaveChangesAsync();
- return user.Id;
- }
-
- public async Task GetByFirstBytes()
- {
- var userId = await SeedUser();
-
- var userByDocument = await context
- .Users
- .WhereStartWithBytes(x => x.Document, 1, 64, DocumentNumber)
- .FirstOrDefaultAsync();
-
- Console.WriteLine($"AAAAAA {userByDocument.Id}");
-
- var user = await context.Users.FindAsync(userId);
- if (user != null)
- {
- context.Users.Remove(user);
- await context.SaveChangesAsync();
- }
- }
-
- public async Task GetByFirstBytesDavit()
- {
- var userId = await SeedUser();
-
- var userByDocument = await context
- .Users
- .Where(u => PostgresDbContext.substr(u.Document, 1, 64).SequenceEqual(DocumentNumber))
- .FirstOrDefaultAsync();
-
- Console.WriteLine($"AAAAAA {userByDocument.Id}");
-
- var user = await context.Users.FindAsync(userId);
- if (user != null)
- {
- context.Users.Remove(user);
- await context.SaveChangesAsync();
- }
- }
+ private static byte[] DocumentNumber => Sha3.Hash("1234567890");
+
+ public async Task SeedUser()
+ {
+ var user = new UserEntity();
+ var documentNumber = DocumentNumber;
+ var randomBytes = new byte[10];
+ documentNumber = documentNumber.Concat(randomBytes)
+ .ToArray();
+ user.Document = documentNumber;
+ context.Users.Add(user);
+ await context.SaveChangesAsync();
+ return user.Id;
+ }
+
+ public async Task GetByFirstBytes()
+ {
+ var userId = await SeedUser();
+
+ var userByDocument = await context
+ .Users
+ .WhereStartWithBytes(x => x.Document!, 1, 64, DocumentNumber)
+ .FirstOrDefaultAsync();
+
+ Console.WriteLine($"AAAAAA {userByDocument!.Id}");
+
+ var user = await context.Users.FindAsync(userId);
+ if (user != null)
+ {
+ context.Users.Remove(user);
+ await context.SaveChangesAsync();
+ }
+ }
+
+ public async Task GetByFirstBytesDavit()
+ {
+ var userId = await SeedUser();
+
+ var userByDocument = await context
+ .Users
+ .Where(u => PostgresDbContext.substr(u.Document!, 1, 64)
+ .SequenceEqual(DocumentNumber))
+ .FirstOrDefaultAsync();
+
+ Console.WriteLine($"AAAAAA {userByDocument!.Id}");
+
+ var user = await context.Users.FindAsync(userId);
+ if (user != null)
+ {
+ context.Users.Remove(user);
+ await context.SaveChangesAsync();
+ }
+ }
}
public static class QueryableExtensions
{
- public static IQueryable WhereStartWithBytes(
- this IQueryable source,
- Expression> byteArraySelector,
- int start,
- int length,
- byte[] prefix) where T : class
- {
- // Parameter expression for the source element
- var parameter = Expression.Parameter(typeof(T), "x");
-
- // Expression to get the byte array from the element
- var member = Expression.Invoke(byteArraySelector, parameter);
-
- // MethodInfo for the substr method
- var methodInfo = typeof(PostgresDbContext).GetMethod(nameof(PostgresDbContext.substr), new[] { typeof(byte[]), typeof(int), typeof(int) });
-
- // Call to substr method
- var call = Expression.Call(
- methodInfo,
- member,
- Expression.Constant(start),
- Expression.Constant(length));
-
- // MethodInfo for the SequenceEqual method
- var sequenceEqualMethod = typeof(Enumerable).GetMethods()
- .First(m => m.Name == "SequenceEqual" && m.GetParameters().Length == 2)
- .MakeGenericMethod(typeof(byte));
-
- // Call to SequenceEqual method
- var sequenceEqualCall = Expression.Call(
- sequenceEqualMethod,
- call,
- Expression.Constant(prefix));
-
- // Lambda expression for the final predicate
- var lambda = Expression.Lambda>(sequenceEqualCall, parameter);
-
- // Apply the predicate to the source IQueryable with client-side evaluation
- return source.AsEnumerable().AsQueryable().Where(lambda);
- }
-}
-
-
-
-
-
-
+ public static IQueryable WhereStartWithBytes(this IQueryable source,
+ Expression> byteArraySelector,
+ int start,
+ int length,
+ byte[] prefix) where T : class
+ {
+ // Parameter expression for the source element
+ var parameter = Expression.Parameter(typeof(T), "x");
+
+ // Expression to get the byte array from the element
+ var member = Expression.Invoke(byteArraySelector, parameter);
+
+ // MethodInfo for the substr method
+ var methodInfo = typeof(PostgresDbContext).GetMethod(nameof(PostgresDbContext.substr),
+ new[]
+ {
+ typeof(byte[]),
+ typeof(int),
+ typeof(int)
+ });
+
+ // Call to substr method
+ var call = Expression.Call(
+ methodInfo!,
+ member,
+ Expression.Constant(start),
+ Expression.Constant(length));
+
+ // MethodInfo for the SequenceEqual method
+ var sequenceEqualMethod = typeof(Enumerable).GetMethods()
+ .First(m => m.Name == "SequenceEqual" && m.GetParameters()
+ .Length == 2)
+ .MakeGenericMethod(typeof(byte));
+
+ // Call to SequenceEqual method
+ var sequenceEqualCall = Expression.Call(
+ sequenceEqualMethod,
+ call,
+ Expression.Constant(prefix));
+
+ // Lambda expression for the final predicate
+ var lambda = Expression.Lambda>(sequenceEqualCall, parameter);
+
+ // Apply the predicate to the source IQueryable with client-side evaluation
+ return source.AsEnumerable()
+ .AsQueryable()
+ .Where(lambda);
+ }
+}
\ No newline at end of file
diff --git a/test/PandaNuGet.Tests/PandaNuGet.Tests.csproj b/test/PandaNuGet.Tests/PandaNuGet.Tests.csproj
index 7107c1c..26e505b 100644
--- a/test/PandaNuGet.Tests/PandaNuGet.Tests.csproj
+++ b/test/PandaNuGet.Tests/PandaNuGet.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
enable
enable
@@ -10,9 +10,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all