Merge pull request #58 from Kataane/add-postgresql-serializer
Add PostgreSQL serializer
This commit is contained in:
commit
b8125e9aed
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using TelegramBotBase.Builder;
|
||||
using TelegramBotBase.Builder.Interfaces;
|
||||
|
||||
namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring the use of PostgreSQL Server Database for session serialization.
|
||||
/// </summary>
|
||||
public static class BotBaseBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses an PostgreSQL Server Database to save and restore sessions.
|
||||
/// </summary>
|
||||
/// <param name="builder">The session serialization stage builder.</param>
|
||||
/// <param name="connectionString">The connection string to the PostgreSQL database.</param>
|
||||
/// <param name="fallbackForm">The fallback form type.</param>
|
||||
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
|
||||
/// <returns>The language selection stage builder.</returns>
|
||||
public static ILanguageSelectionStage UsePostgreSqlDatabase(
|
||||
this ISessionSerializationStage builder,
|
||||
string connectionString, Type fallbackForm = null,
|
||||
string tablePrefix = "tgb_")
|
||||
{
|
||||
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
|
||||
|
||||
builder.UseSerialization(serializer);
|
||||
|
||||
return builder as BotBaseBuilder;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Uses an PostgreSQL Server Database to save and restore sessions.
|
||||
/// </summary>
|
||||
/// <param name="builder">The session serialization stage builder.</param>
|
||||
/// <param name="hostOrIp">The host or IP address of the PostgreSQL server.</param>
|
||||
/// <param name="port">The port number for the PostgreSQL server.</param>
|
||||
/// <param name="databaseName">The name of the PostgreSQL database.</param>
|
||||
/// <param name="userId">The user ID for connecting to the PostgreSQL server.</param>
|
||||
/// <param name="password">The password for connecting to the PostgreSQL server.</param>
|
||||
/// <param name="fallbackForm">The fallback form type.</param>
|
||||
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
|
||||
/// <returns>The language selection stage builder.</returns>
|
||||
public static ILanguageSelectionStage UsePostgreSqlDatabase(
|
||||
this ISessionSerializationStage builder,
|
||||
string hostOrIp, string port,
|
||||
string databaseName, string userId,
|
||||
string password, Type fallbackForm = null,
|
||||
string tablePrefix = "tgb_")
|
||||
{
|
||||
var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Username={userId};Password={password}";
|
||||
|
||||
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
|
||||
|
||||
builder.UseSerialization(serializer);
|
||||
|
||||
return builder as BotBaseBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses an PostgreSQL Server Database with Windows Authentication to save and restore sessions.
|
||||
/// </summary>
|
||||
/// <param name="builder">The session serialization stage builder.</param>
|
||||
/// <param name="hostOrIp">The host or IP address of the PostgreSQL server.</param>
|
||||
/// <param name="port">The port number for the PostgreSQL server.</param>
|
||||
/// <param name="databaseName">The name of the PostgreSQL database.</param>
|
||||
/// <param name="integratedSecurity">A flag indicating whether to use Windows Authentication (true) or not (false).</param>
|
||||
/// <param name="fallbackForm">The fallback form type.</param>
|
||||
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
|
||||
/// <returns>The language selection stage builder.</returns>
|
||||
public static ILanguageSelectionStage UsePostgreSqlDatabase(
|
||||
this ISessionSerializationStage builder,
|
||||
string hostOrIp, string port,
|
||||
string databaseName, bool integratedSecurity = true,
|
||||
Type fallbackForm = null, string tablePrefix = "tgb_")
|
||||
{
|
||||
if (!integratedSecurity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Integrated Security=true;";
|
||||
|
||||
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
|
||||
|
||||
builder.UseSerialization(serializer);
|
||||
|
||||
return builder as BotBaseBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,252 @@
|
||||
using Npgsql;
|
||||
using System;
|
||||
using System.Data;
|
||||
using NpgsqlTypes;
|
||||
using TelegramBotBase.Args;
|
||||
using TelegramBotBase.Base;
|
||||
using TelegramBotBase.Form;
|
||||
using TelegramBotBase.Interfaces;
|
||||
|
||||
namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a PostgreSQL implementation of the <see cref="IStateMachine"/> for saving and loading form states.
|
||||
/// </summary>
|
||||
public class PostgreSqlSerializer : IStateMachine
|
||||
{
|
||||
private readonly string insertIntoSessionSql;
|
||||
private readonly string insertIntoSessionsDataSql;
|
||||
private readonly string selectAllDevicesSessionsSql;
|
||||
private readonly string selectAllDevicesSessionsDataSql;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PostgreSqlSerializer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The connection string to the PostgreSQL database.</param>
|
||||
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
|
||||
/// <param name="fallbackStateForm">The fallback state form type.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="connectionString"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="fallbackStateForm"/> is not a subclass of <see cref="FormBase"/>.</exception>
|
||||
public PostgreSqlSerializer(string connectionString, string tablePrefix = "tgb_", Type fallbackStateForm = null)
|
||||
{
|
||||
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
|
||||
TablePrefix = tablePrefix;
|
||||
|
||||
FallbackStateForm = fallbackStateForm;
|
||||
|
||||
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
|
||||
}
|
||||
|
||||
insertIntoSessionSql = "INSERT INTO " + TablePrefix +
|
||||
"devices_sessions (deviceId, deviceTitle, \"FormUri\", \"QualifiedName\") VALUES (@deviceId, @deviceTitle, @FormUri, @QualifiedName)";
|
||||
insertIntoSessionsDataSql = "INSERT INTO " + TablePrefix + "devices_sessions_data (deviceId, key, value, type) VALUES (@deviceId, @key, @value, @type)";
|
||||
|
||||
selectAllDevicesSessionsSql = "SELECT * FROM " + TablePrefix + "devices_sessions";
|
||||
selectAllDevicesSessionsDataSql = "SELECT * FROM " + TablePrefix + "devices_sessions_data WHERE deviceId = @deviceId";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection string to the PostgreSQL database.
|
||||
/// </summary>
|
||||
public string ConnectionString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the table name prefix for database tables.
|
||||
/// </summary>
|
||||
public string TablePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the fallback state form type.
|
||||
/// </summary>
|
||||
public Type FallbackStateForm { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Saves form states to the PostgreSQL database.
|
||||
/// </summary>
|
||||
/// <param name="e">The <see cref="SaveStatesEventArgs"/> containing the states to be saved.</param>
|
||||
public void SaveFormStates(SaveStatesEventArgs e)
|
||||
{
|
||||
var container = e.States;
|
||||
|
||||
//Cleanup old Session data
|
||||
Cleanup();
|
||||
|
||||
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
//Store session data in database
|
||||
foreach (var state in container.States)
|
||||
{
|
||||
using (var sessionCommand = connection.CreateCommand())
|
||||
{
|
||||
sessionCommand.CommandText = insertIntoSessionSql;
|
||||
|
||||
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint){Value = state.DeviceId });
|
||||
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceTitle", DbType.StringFixedLength){Value = state.ChatTitle ?? string.Empty});
|
||||
sessionCommand.Parameters.Add(new NpgsqlParameter("@FormUri", DbType.StringFixedLength) {Value = state.FormUri});
|
||||
sessionCommand.Parameters.Add(new NpgsqlParameter("@QualifiedName", DbType.StringFixedLength){Value = state.QualifiedName });
|
||||
|
||||
sessionCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
foreach (var state in container.States)
|
||||
{
|
||||
SaveSessionsData(state, connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Loads form states from the PostgreSQL database.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="StateContainer"/> containing the loaded form states.</returns>
|
||||
public StateContainer LoadFormStates()
|
||||
{
|
||||
var stateContainer = new StateContainer();
|
||||
|
||||
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using (var sessionCommand = connection.CreateCommand())
|
||||
{
|
||||
sessionCommand.CommandText = selectAllDevicesSessionsSql;
|
||||
|
||||
var sessionTable = new DataTable();
|
||||
using (var dataAdapter = new NpgsqlDataAdapter(sessionCommand))
|
||||
{
|
||||
dataAdapter.Fill(sessionTable);
|
||||
|
||||
foreach (DataRow row in sessionTable.Rows)
|
||||
{
|
||||
var stateEntry = new StateEntry
|
||||
{
|
||||
DeviceId = (long)row["deviceId"],
|
||||
ChatTitle = row["deviceTitle"].ToString(),
|
||||
FormUri = row["FormUri"].ToString(),
|
||||
QualifiedName = row["QualifiedName"].ToString()
|
||||
};
|
||||
|
||||
stateContainer.States.Add(stateEntry);
|
||||
|
||||
if (stateEntry.DeviceId > 0)
|
||||
{
|
||||
stateContainer.ChatIds.Add(stateEntry.DeviceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateContainer.GroupIds.Add(stateEntry.DeviceId);
|
||||
}
|
||||
|
||||
LoadDataTable(connection, row, stateEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stateContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up old session data in the PostgreSQL database.
|
||||
/// </summary>
|
||||
private void Cleanup()
|
||||
{
|
||||
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using (var clearCommand = connection.CreateCommand())
|
||||
{
|
||||
clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions_data";
|
||||
clearCommand.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var clearCommand = connection.CreateCommand())
|
||||
{
|
||||
clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions";
|
||||
clearCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves session data to the PostgreSQL database.
|
||||
/// </summary>
|
||||
/// <param name="state">The state entry containing session data to be saved.</param>
|
||||
/// <param name="connection">The NpgsqlConnection used for the database interaction.</param>
|
||||
private void SaveSessionsData(StateEntry state, NpgsqlConnection connection)
|
||||
{
|
||||
foreach (var data in state.Values)
|
||||
{
|
||||
using (var dataCommand = connection.CreateCommand())
|
||||
{
|
||||
dataCommand.CommandText = insertIntoSessionsDataSql;
|
||||
|
||||
dataCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint) { Value = state.DeviceId });
|
||||
dataCommand.Parameters.Add(new NpgsqlParameter("@key", DbType.StringFixedLength) { Value = data.Key });
|
||||
|
||||
var type = data.Value.GetType();
|
||||
|
||||
if (type.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = data.Value });
|
||||
}
|
||||
else
|
||||
{
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(data.Value);
|
||||
dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = json });
|
||||
}
|
||||
|
||||
dataCommand.Parameters.Add(new NpgsqlParameter("@type", DbType.StringFixedLength) { Value = type.AssemblyQualifiedName });
|
||||
|
||||
dataCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads session data from the PostgreSQL database.
|
||||
/// </summary>
|
||||
/// <param name="connection">The NpgsqlConnection used for the database interaction.</param>
|
||||
/// <param name="row">The DataRow representing a session entry in the main sessions table.</param>
|
||||
/// <param name="stateEntry">The StateEntry object to which session data will be loaded.</param>
|
||||
private void LoadDataTable(NpgsqlConnection connection, DataRow row, StateEntry stateEntry)
|
||||
{
|
||||
using (var sessionCommand = connection.CreateCommand())
|
||||
{
|
||||
sessionCommand.CommandText = selectAllDevicesSessionsDataSql;
|
||||
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", row["deviceId"]));
|
||||
|
||||
var dataCommandTable = new DataTable();
|
||||
using (var npgSqlDataAdapter = new NpgsqlDataAdapter(sessionCommand))
|
||||
{
|
||||
npgSqlDataAdapter.Fill(dataCommandTable);
|
||||
|
||||
foreach (DataRow dataRow in dataCommandTable.Rows)
|
||||
{
|
||||
var key = dataRow["key"].ToString();
|
||||
var type = Type.GetType(dataRow["type"].ToString());
|
||||
|
||||
var value = System.Text.Json.JsonSerializer.Deserialize(dataRow["value"].ToString(), type);
|
||||
|
||||
stateEntry.Values.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
# TelegramBotBase.Extensions.Serializer.Database.PostgreSQL
|
||||
|
||||
[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL/)
|
||||
[](https://www.t.me/tgbotbase)
|
||||
|
||||
[](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
|
||||
[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL)
|
||||
|
||||
## How to use
|
||||
|
||||
```csharp
|
||||
using TelegramBotBase.Extensions.Serializer.Database.PostgreSQL;
|
||||
|
||||
|
||||
var bot = BotBaseBuilder
|
||||
.Create()
|
||||
.WithAPIKey(APIKey)
|
||||
.DefaultMessageLoop()
|
||||
.WithStartForm<Start>()
|
||||
.NoProxy()
|
||||
.OnlyStart()
|
||||
.UsePostgreSqlDatabase("localhost", "8181", "telegram_bot")
|
||||
.UseEnglish()
|
||||
.Build();
|
||||
|
||||
bot.Start();
|
||||
```
|
||||
@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net8</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
|
||||
<Copyright>MIT</Copyright>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<AssemblyVersion>1.0.1</AssemblyVersion>
|
||||
<FileVersion>1.0.1</FileVersion>
|
||||
<Version>1.0.1</Version>
|
||||
<Description>
|
||||
A session serializer for PostgreSQL Server.
|
||||
</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.3" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,25 @@
|
||||
-- Enable uuid-ossp extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Create table tgb_devices_sessions
|
||||
CREATE TABLE tgb_devices_sessions (
|
||||
deviceId bigint NOT NULL,
|
||||
deviceTitle character varying(512) NOT NULL,
|
||||
"FormUri" character varying(512) NOT NULL,
|
||||
"QualifiedName" character varying(512) NOT NULL,
|
||||
CONSTRAINT PK_tgb_devices_sessions_1 PRIMARY KEY (deviceId)
|
||||
);
|
||||
|
||||
-- Create table tgb_devices_sessions_data
|
||||
CREATE TABLE tgb_devices_sessions_data (
|
||||
Id uuid DEFAULT uuid_generate_v4() NOT NULL,
|
||||
deviceId bigint NOT NULL,
|
||||
key character varying(512) NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"type" character varying(512) NOT NULL,
|
||||
CONSTRAINT PK_tgb_devices_session_data PRIMARY KEY (Id)
|
||||
);
|
||||
|
||||
-- Add default constraint for Id column in tgb_devices_sessions_data
|
||||
ALTER TABLE tgb_devices_sessions_data
|
||||
ALTER COLUMN Id SET DEFAULT uuid_generate_v4();
|
||||
@ -1,36 +0,0 @@
|
||||
namespace TelegramBotBase.Localizations;
|
||||
|
||||
public sealed class Russian : Localization
|
||||
{
|
||||
public Russian()
|
||||
{
|
||||
Values["Language"] = "Русский (Russian)";
|
||||
Values["ButtonGrid_Title"] = "Меню";
|
||||
Values["ButtonGrid_NoItems"] = "Нет доступных элементов.";
|
||||
Values["ButtonGrid_PreviousPage"] = "◀️";
|
||||
Values["ButtonGrid_NextPage"] = "▶️";
|
||||
Values["ButtonGrid_CurrentPage"] = "Страница {0} из {1}";
|
||||
Values["ButtonGrid_SearchFeature"] = "💡 Отправьте сообщение, чтобы отфильтровать список. Нажмите на 🔍, чтобы сбросить фильтр.";
|
||||
Values["ButtonGrid_Back"] = "Назада";
|
||||
Values["ButtonGrid_CheckAll"] = "Выделить все";
|
||||
Values["ButtonGrid_UncheckAll"] = "Отменить выбор";
|
||||
Values["CalendarPicker_Title"] = "Календарь / Выберите дату";
|
||||
Values["CalendarPicker_PreviousPage"] = "◀️";
|
||||
Values["CalendarPicker_NextPage"] = "▶️";
|
||||
Values["TreeView_Title"] = "Выберите пункт";
|
||||
Values["TreeView_LevelUp"] = "🔼 Обратно";
|
||||
Values["ToggleButton_On"] = "Вкл";
|
||||
Values["ToggleButton_Off"] = "Выкл";
|
||||
Values["ToggleButton_OnIcon"] = "⚫";
|
||||
Values["ToggleButton_OffIcon"] = "⚪";
|
||||
Values["ToggleButton_Title"] = "Переключить";
|
||||
Values["ToggleButton_Changed"] = "Выбрано";
|
||||
Values["MultiToggleButton_SelectedIcon"] = "✅";
|
||||
Values["MultiToggleButton_Title"] = "Множественный выбор";
|
||||
Values["MultiToggleButton_Changed"] = "Выбрано";
|
||||
Values["PromptDialog_Back"] = "Назад";
|
||||
Values["ToggleButton_Changed"] = "Настройки изменены";
|
||||
Values["ButtonGrid_SearchIcon"] = "🔍";
|
||||
Values["ButtonGrid_TagIcon"] = "📁";
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Exa
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.Extensions.Serializer.Database.PostgreSql", "TelegramBotBase.Extensions.Serializer.Database.PostgreSql\TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj", "{7C55D9FF-7DC1-41D0-809C-469EBFA20992}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -84,6 +86,10 @@ Global
|
||||
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -98,6 +104,7 @@ Global
|
||||
{52EA3201-02E8-46F5-87C4-B4752C8A815C} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
|
||||
{067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
|
||||
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
|
||||
{7C55D9FF-7DC1-41D0-809C-469EBFA20992} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user