Merge pull request #28 from NoiTheCat/dev/dotnet8

Upgrade to .NET 8
This commit is contained in:
Noi 2024-05-10 00:07:34 -07:00 committed by GitHub
commit 71b8b4a274
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 120 additions and 85 deletions

12
.config/dotnet-tools.json Normal file
View file

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.4",
"commands": [
"dotnet-ef"
]
}
}
}

View file

@ -13,13 +13,13 @@ tab_width = 4
# New line preferences # New line preferences
end_of_line = crlf end_of_line = crlf
insert_final_newline = false insert_final_newline = true
#### .NET Coding Conventions #### #### .NET Coding Conventions ####
# Organize usings # Organize usings
dotnet_separate_import_directive_groups = false dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false dotnet_sort_system_directives_first = true
file_header_template = unset file_header_template = unset
# this. and Me. preferences # this. and Me. preferences
@ -63,7 +63,7 @@ dotnet_style_prefer_simplified_interpolation = true
dotnet_style_readonly_field = true dotnet_style_readonly_field = true
# Parameter preferences # Parameter preferences
dotnet_code_quality_unused_parameters = all dotnet_code_quality_unused_parameters = true
# Suppression preferences # Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none dotnet_remove_unnecessary_suppression_exclusions = none
@ -75,59 +75,58 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions #### #### C# Coding Conventions ####
# var preferences # var preferences
csharp_style_var_elsewhere = false csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = false csharp_style_var_when_type_is_apparent = true:suggestion
# Expression-bodied members # Expression-bodied members
csharp_style_expression_bodied_accessors = true csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = true csharp_style_expression_bodied_constructors = true:silent
csharp_style_expression_bodied_indexers = true csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = true csharp_style_expression_bodied_local_functions = true:silent
csharp_style_expression_bodied_methods = true csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_operators = true csharp_style_expression_bodied_operators = true:silent
csharp_style_expression_bodied_properties = true csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences # Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_not_pattern = true csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:suggestion csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences # Null-checking preferences
csharp_style_conditional_delegate_call = true csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences # Modifier preferences
csharp_prefer_static_local_function = true csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
# Code-block preferences # Code-block preferences
csharp_prefer_braces = when_multiline csharp_prefer_braces = when_multiline:silent
csharp_prefer_simple_using_statement = true csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = file_scoped csharp_style_namespace_declarations = file_scoped:suggestion
# Expression-level preferences # Expression-level preferences
csharp_prefer_simple_default_expression = true csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true csharp_style_pattern_local_over_anonymous_function = true
csharp_style_prefer_index_operator = true csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_null_check_over_type_check = true csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = true csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences # 'using' directive preferences
csharp_using_directive_placement = outside_namespace csharp_using_directive_placement = outside_namespace:silent
# New line preferences # New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
#### C# Formatting Rules #### #### C# Formatting Rules ####
@ -217,3 +216,8 @@ dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.begins_with_i.capitalization = pascal_case
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_prefer_primary_constructors = true:suggestion

4
.vscode/launch.json vendored
View file

@ -10,8 +10,8 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net6.0/WorldTime.dll", "program": "${workspaceFolder}/bin/Debug/net8.0/WorldTime.dll",
"args": [ "-c", "${workspaceFolder}/bin/Debug/net6.0/settings.json" ], "args": [ "-c", "${workspaceFolder}/bin/Debug/settings.json" ],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole", "console": "internalConsole",

View file

@ -7,10 +7,9 @@ using WorldTime.Data;
namespace WorldTime.Commands; namespace WorldTime.Commands;
public class CommandsBase : InteractionModuleBase<ShardedInteractionContext> { public class CommandsBase : InteractionModuleBase<ShardedInteractionContext> {
protected const string ErrInvalidZone = ":x: Not a valid zone name." protected const string ErrInvalidZone =
+ " To find your time zone, refer to: <https://kevinnovak.github.io/Time-Zone-Picker/>."; ":x: Not a valid zone name. To find your zone, you may refer to a site such as <https://zones.arilyn.cc/>.";
protected const string ErrNoUserCache = ":warning: Please try the command again."; protected const string ErrNoUserCache = ":warning: Oops, bot wasn't ready. Please try again in a moment.";
protected const string ErrNotAllowed = ":x: Only server moderators may use this command.";
private static readonly ReadOnlyDictionary<string, string> _tzNameMap; private static readonly ReadOnlyDictionary<string, string> _tzNameMap;

View file

@ -3,7 +3,7 @@ using Discord.Interactions;
namespace WorldTime.Commands; namespace WorldTime.Commands;
[Group("config", "Configuration commands for World Time.")] [Group("config", "Configuration commands for World Time.")]
[DefaultMemberPermissions(GuildPermission.ManageGuild)] [DefaultMemberPermissions(GuildPermission.ManageGuild)]
[EnabledInDm(false)] [CommandContextType(InteractionContextType.Guild)]
public class ConfigCommands : CommandsBase { public class ConfigCommands : CommandsBase {
internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings."; internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings.";
internal const string HelpSetFor = "Sets/updates time zone for a given user."; internal const string HelpSetFor = "Sets/updates time zone for a given user.";

View file

@ -3,17 +3,6 @@ using System.Text;
namespace WorldTime.Commands; namespace WorldTime.Commands;
public class UserCommands : CommandsBase { public class UserCommands : CommandsBase {
const string EmbedHelpField1 =
$"`/help` - {HelpHelp}\n"
+ $"`/list` - {HelpList}\n"
+ $"`/set` - {HelpSet}\n"
+ $"`/remove` - {HelpRemove}";
const string EmbedHelpField2 =
$"`/config use-12hour` - {ConfigCommands.HelpUse12}\n"
+ $"`/config private-confirms` - {ConfigCommands.HelpPrivateConfirms}\n"
+ $"`/set-for` - {ConfigCommands.HelpSetFor}\n"
+ $"`/remove-for` - {ConfigCommands.HelpRemoveFor}";
#region Help strings #region Help strings
const string HelpHelp = "Displays a list of available bot commands."; const string HelpHelp = "Displays a list of available bot commands.";
const string HelpList = "Shows the current time for all recently active known users."; const string HelpList = "Shows the current time for all recently active known users.";
@ -23,6 +12,7 @@ public class UserCommands : CommandsBase {
#endregion #endregion
[SlashCommand("help", HelpHelp)] [SlashCommand("help", HelpHelp)]
[CommandContextType(InteractionContextType.Guild, InteractionContextType.BotDm)]
public async Task CmdHelp() { public async Task CmdHelp() {
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version!.ToString(3); var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version!.ToString(3);
var guildct = ShardedClient.Guilds.Count; var guildct = ShardedClient.Guilds.Count;
@ -30,7 +20,8 @@ public class UserCommands : CommandsBase {
var uniquetz = db.GetDistinctZoneCount(); var uniquetz = db.GetDistinctZoneCount();
await RespondAsync(embed: new EmbedBuilder() { await RespondAsync(embed: new EmbedBuilder() {
Title = "Help & About", Title = "Help & About",
Description = $"World Time v{version} - Serving {guildct} communities across {uniquetz} time zones.\n\n" Description =
$"World Time v{version} - Serving {guildct} communities across {uniquetz} time zones.\n\n"
+ "This bot is provided for free, without any paywalled 'premium' features. " + "This bot is provided for free, without any paywalled 'premium' features. "
+ "If you've found this bot useful, please consider contributing via the " + "If you've found this bot useful, please consider contributing via the "
+ "bot author's page on Ko-fi: https://ko-fi.com/noithecat.", + "bot author's page on Ko-fi: https://ko-fi.com/noithecat.",
@ -38,16 +29,28 @@ public class UserCommands : CommandsBase {
IconUrl = Context.Client.CurrentUser.GetAvatarUrl(), IconUrl = Context.Client.CurrentUser.GetAvatarUrl(),
Text = "World Time" Text = "World Time"
} }
}.AddField(inline: false, name: "Commands", value: EmbedHelpField1 }.AddField(inline: false, name: "Commands", value:
).AddField(inline: false, name: "Admin commands", value: EmbedHelpField2 $"""
`/help` - {HelpHelp}
`/list` - {HelpList}
`/set` - {HelpSet}
`/remove` - {HelpRemove}
"""
).AddField(inline: false, name: "Admin commands", value:
$"""
`/config use-12hour` - {ConfigCommands.HelpUse12}
`/config private-confirms` - {ConfigCommands.HelpPrivateConfirms}
`/set-for` - {ConfigCommands.HelpSetFor}
`/remove-for` - {ConfigCommands.HelpRemoveFor}
"""
).AddField(inline: false, name: "Zones", value: ).AddField(inline: false, name: "Zones", value:
"This bot accepts zone names from the IANA Time Zone Database (a.k.a. Olson Database). " + "This bot accepts zone names from the IANA Time Zone Database (a.k.a. Olson Database). " +
"A useful tool to determine yours can be found at: https://kevinnovak.github.io/Time-Zone-Picker/" "A useful tool to determine yours can be found at: https://zones.arilyn.cc/"
).Build()); ).Build());
} }
[SlashCommand("list", HelpList)] [SlashCommand("list", HelpList)]
[EnabledInDm(false)] [CommandContextType(InteractionContextType.Guild)]
public async Task CmdList([Summary(description: "A specific user whose time to look up.")]SocketGuildUser? user = null) { public async Task CmdList([Summary(description: "A specific user whose time to look up.")]SocketGuildUser? user = null) {
if (!await AreUsersDownloadedAsync(Context.Guild)) { if (!await AreUsersDownloadedAsync(Context.Guild)) {
await RespondAsync(ErrNoUserCache, ephemeral: true); await RespondAsync(ErrNoUserCache, ephemeral: true);
@ -76,9 +79,9 @@ public class UserCommands : CommandsBase {
// Generate time and zone names to be displayed, group with associated user IDs // Generate time and zone names to be displayed, group with associated user IDs
var sortedlist = new SortedDictionary<string, List<ulong>>(); var sortedlist = new SortedDictionary<string, List<ulong>>();
var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false; var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false;
foreach ((string area, List<ulong> users) in userlist.OrderByDescending(o => o.Value.Count)) { foreach ((var area, List<ulong> users) in userlist.OrderByDescending(o => o.Value.Count)) {
var areaprint = TzPrint(area, ampm); var areaprint = TzPrint(area, ampm);
if (!sortedlist.ContainsKey(areaprint)) sortedlist.Add(areaprint, new List<ulong>()); if (!sortedlist.ContainsKey(areaprint)) sortedlist[areaprint] = [];
sortedlist[areaprint].AddRange(users); sortedlist[areaprint].AddRange(users);
} }
@ -87,10 +90,10 @@ public class UserCommands : CommandsBase {
// Build zone listings with users // Build zone listings with users
var outputlines = new List<string>(); var outputlines = new List<string>();
foreach ((string area, List<ulong> users) in sortedlist) { foreach ((var area, List<ulong> users) in sortedlist) {
var buffer = new StringBuilder(); var buffer = new StringBuilder();
buffer.Append(area[6..] + ": "); buffer.Append(area[6..] + ": ");
bool empty = true; var empty = true;
foreach (var userid in users) { foreach (var userid in users) {
var userinstance = Context.Guild.GetUser(userid); var userinstance = Context.Guild.GetUser(userid);
if (userinstance == null) continue; if (userinstance == null) continue;
@ -107,7 +110,7 @@ public class UserCommands : CommandsBase {
// Prepare for output - send buffers out if they become too large // Prepare for output - send buffers out if they become too large
outputlines.Sort(); outputlines.Sort();
bool hasOutputOneLine = false; var hasOutputOneLine = false;
// First output is shown as an interaction response, followed then as regular channel messages // First output is shown as an interaction response, followed then as regular channel messages
async Task doOutput(Embed msg) { async Task doOutput(Embed msg) {
if (!hasOutputOneLine) { if (!hasOutputOneLine) {
@ -137,7 +140,7 @@ public class UserCommands : CommandsBase {
using var db = DbContext; using var db = DbContext;
var result = db.GetUserZone(parameter); var result = db.GetUserZone(parameter);
if (result == null) { if (result == null) {
bool isself = Context.User.Id == parameter.Id; var isself = Context.User.Id == parameter.Id;
if (isself) await RespondAsync(":x: You do not have a time zone. Set it with `tz.set`.", ephemeral: true); if (isself) await RespondAsync(":x: You do not have a time zone. Set it with `tz.set`.", ephemeral: true);
else await RespondAsync(":x: The given user does not have a time zone set.", ephemeral: true); else await RespondAsync(":x: The given user does not have a time zone set.", ephemeral: true);
return; return;
@ -149,7 +152,7 @@ public class UserCommands : CommandsBase {
} }
[SlashCommand("set", HelpSet)] [SlashCommand("set", HelpSet)]
[EnabledInDm(false)] [CommandContextType(InteractionContextType.Guild)]
public async Task CmdSet([Summary(description: "The new time zone to set.")]string zone) { public async Task CmdSet([Summary(description: "The new time zone to set.")]string zone) {
var parsedzone = ParseTimeZone(zone); var parsedzone = ParseTimeZone(zone);
if (parsedzone == null) { if (parsedzone == null) {
@ -164,7 +167,7 @@ public class UserCommands : CommandsBase {
} }
[SlashCommand("remove", HelpRemove)] [SlashCommand("remove", HelpRemove)]
[EnabledInDm(false)] [CommandContextType(InteractionContextType.Guild)]
public async Task CmdRemove() { public async Task CmdRemove() {
using var db = DbContext; using var db = DbContext;
var success = db.DeleteUser((SocketGuildUser)Context.User); var success = db.DeleteUser((SocketGuildUser)Context.User);

View file

@ -102,7 +102,7 @@ public class BotDatabaseContext : DbContext {
select Tuple.Create(entry.TimeZone, (ulong)entry.UserId); select Tuple.Create(entry.TimeZone, (ulong)entry.UserId);
var resultSet = new Dictionary<string, List<ulong>>(); var resultSet = new Dictionary<string, List<ulong>>();
foreach (var (tz, user) in query) { foreach (var (tz, user) in query) {
if (!resultSet.ContainsKey(tz)) resultSet.Add(tz, new List<ulong>()); if (!resultSet.ContainsKey(tz)) resultSet[tz] = [];
resultSet[tz].Add(user); resultSet[tz].Add(user);
} }
return resultSet; return resultSet;

View file

@ -0,0 +1,4 @@
[*.cs]
generated_code = true
dotnet_analyzer_diagnostic.category-CodeQuality.severity = none
dotnet_diagnostic.CS1591.severity = none

View file

@ -9,9 +9,8 @@ A social time zone reference tool!
#### Running your own instance #### Running your own instance
You need: You need:
* .NET 6 (https://dotnet.microsoft.com/en-us/) * .NET 8 (https://dotnet.microsoft.com/en-us/)
* PostgreSQL (https://www.postgresql.org/) * PostgreSQL (https://www.postgresql.org/)
* EF Core tools (https://learn.microsoft.com/en-us/ef/core/get-started/overview/install#get-the-entity-framework-core-tools)
* A Discord bot token (https://discord.com/developers/applications) * A Discord bot token (https://discord.com/developers/applications)
Get your bot token and set up your database user and schema, then create a JSON file containing the following: Get your bot token and set up your database user and schema, then create a JSON file containing the following:
@ -28,10 +27,11 @@ Get your bot token and set up your database user and schema, then create a JSON
Then run the following commands: Then run the following commands:
```sh ```sh
$ dotnet restore $ dotnet restore
$ dotnet tool restore
$ dotnet ef database update -- -c path/to/config.json $ dotnet ef database update -- -c path/to/config.json
``` ```
And finally, to run the bot: And finally, to run the bot:
``` ```sh
$ dotnet run -c Release -- -c path/to/config.json $ dotnet run -c Release -- -c path/to/config.json
``` ```

View file

@ -104,7 +104,7 @@ internal class WorldTime : IDisposable {
// Discord Bots // Discord Bots
if (!string.IsNullOrEmpty(Config.DBotsToken)) { if (!string.IsNullOrEmpty(Config.DBotsToken)) {
try { try {
string dBotsApiUrl = $"https://discord.bots.gg/api/v1/bots/{ botId }/stats"; var dBotsApiUrl = $"https://discord.bots.gg/api/v1/bots/{ botId }/stats";
var body = $"{{ \"guildCount\": {guildCount} }}"; var body = $"{{ \"guildCount\": {guildCount} }}";
var uri = new Uri(string.Format(dBotsApiUrl)); var uri = new Uri(string.Format(dBotsApiUrl));

View file

@ -1,28 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Version>2.3.3</Version>
<Authors>NoiTheCat</Authors>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>2.3.2</Version> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<Authors>NoiTheCat</Authors> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<WarningLevel>0</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Discord.Net" Version="3.12.0" /> <PackageReference Include="Discord.Net" Version="3.14.1" />
<PackageReference Include="EFCore.NamingConventions" Version="7.0.2" /> <PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NodaTime" Version="3.1.9" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="Npgsql" Version="7.0.4" /> <PackageReference Include="Npgsql" Version="8.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
</ItemGroup> </ItemGroup>