Migration to scaffold framework for code-generation (#82)
* - initial project refactoring - migration to scaffold framework * move non-code files from project folder * ifdefs refactoring * code refactoring/cleanup (in progress) * refactoring * refactor settings UI * update dependencies, fix build * convert settings control tabs to usercontrol * fix configuration dialogs styling * implement #58, fixes, Access testing done * sqlite tested * - packages support - custom types rendering - firebird fixes * sqlce (v5) fixes, v7 is not ready yet * sqlce v7 fixes * - sybase support - fixes - remove strong name * clickhouse support * sql server support (partially) * sql server support * postgresql linqpad 5 fix * npgsql types rendering * mysql/mariadb support * oracle support * sap hana support * db2/informix support * cleanup * pre-built context support * update dependencies * code cleanup * finalize release notes * Update Build/README.md Co-authored-by: Stuart Turner <stuart@turner-isageek.com> * Update README.md Co-authored-by: Stuart Turner <stuart@turner-isageek.com> * Update release-notes.md Co-authored-by: Stuart Turner <stuart@turner-isageek.com> * add clickhouse provider information to UI * [clickhouse] add FixedString to string mapping option * add polysharp * sql logger: don't log RecordsAffected when it is not set * Database -> Database Type * dialect detection for sql server, oracle, pgsql --------- Co-authored-by: Stuart Turner <stuart@turner-isageek.com>
This commit is contained in:
Родитель
426207d95c
Коммит
d18f9a726d
|
@ -1,5 +1,3 @@
|
|||
; What is EditorConfig? http://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
; use tabs identation for all files
|
||||
|
@ -227,6 +225,9 @@ dotnet_diagnostic.IDE0079.severity = none # IDE0079: Remove unnecessary suppress
|
|||
dotnet_diagnostic.IDE0080.severity = none # IDE0080: Remove unnecessary suppression operator
|
||||
dotnet_diagnostic.IDE0081.severity = none # IDE0081: Remove ByVal
|
||||
dotnet_diagnostic.IDE0083.severity = none # IDE0083: Use pattern matching (not operator)
|
||||
dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure
|
||||
dotnet_diagnostic.IDE0160.severity = none # IDE0160: Use block-scoped namespace
|
||||
dotnet_diagnostic.IDE0161.severity = error # IDE0161: Use file-scoped namespace
|
||||
dotnet_diagnostic.IDE1006.severity = none # IDE1006: Naming rule violation
|
||||
|
||||
dotnet_diagnostic.CS1998.severity = error # CS1998: Async method lacks 'await' operators and will run synchronously
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#Ignore files build by Visual Studio
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
*.zip
|
||||
*.lpx
|
||||
*.lpx6
|
||||
*.csproj.user
|
||||
|
|
|
@ -37,7 +37,7 @@ if ($version) {
|
|||
$xml.package.metadata.AppendChild($child)
|
||||
|
||||
$child = $xml.CreateElement('copyright', $nsUri)
|
||||
$child.InnerText = 'Copyright © 2016-2020 ' + $authors
|
||||
$child.InnerText = 'Copyright © 2016-2023 ' + $authors
|
||||
$xml.package.metadata.AppendChild($child)
|
||||
|
||||
$child = $xml.CreateElement('authors', $nsUri)
|
||||
|
|
До Ширина: | Высота: | Размер: 679 B После Ширина: | Высота: | Размер: 679 B |
До Ширина: | Высота: | Размер: 452 B После Ширина: | Высота: | Размер: 452 B |
|
@ -0,0 +1,36 @@
|
|||
ECHO OFF
|
||||
ECHO Packing %2
|
||||
|
||||
DEL linq2db.LINQPad.%2
|
||||
DEL linq2db.LINQPad.%2.zip
|
||||
|
||||
REM LINQPad 5 driver archive generation
|
||||
IF %2 EQU lpx (
|
||||
REM remove resource satellite assemblies
|
||||
RD /S /Q %1\cs
|
||||
RD /S /Q %1\de
|
||||
RD /S /Q %1\es
|
||||
RD /S /Q %1\fr
|
||||
RD /S /Q %1\it
|
||||
RD /S /Q %1\ja
|
||||
RD /S /Q %1\ko
|
||||
RD /S /Q %1\pl
|
||||
RD /S /Q %1\pt
|
||||
RD /S /Q %1\pt-BR
|
||||
RD /S /Q %1\ru
|
||||
RD /S /Q %1\tr
|
||||
RD /S /Q %1\zh-Hans
|
||||
RD /S /Q %1\zh-Hant
|
||||
|
||||
REM remove not needed files
|
||||
DEL /Q %1\linq2db.*.xml
|
||||
DEL /Q %1\*.pdb
|
||||
|
||||
"C:\Program Files\7-Zip\7z.exe" -r a linq2db.LINQPad.%2.zip %1\*.* %1\..\..\..\..\Build\Connection.png %1\..\..\..\..\Build\FailedConnection.png %1\..\..\..\..\Build\header.xml
|
||||
)
|
||||
|
||||
REM LINQPad 7 driver archive generation
|
||||
IF %2 EQU lpx6 ("C:\Program Files\7-Zip\7z.exe" a linq2db.LINQPad.%2.zip %1\linq2db.LINQPad.dll %1\..\..\..\..\Build\Connection.png %1\..\..\..\..\Build\FailedConnection.png %1\linq2db.LINQPad.deps.json)
|
||||
|
||||
REN linq2db.LINQPad.%2.zip linq2db.LINQPad.%2
|
||||
|
|
@ -1,23 +1,25 @@
|
|||
## LINQ to DB LINQPad 6 and 7 Driver
|
||||
# LINQ to DB LINQPad 7 Driver
|
||||
|
||||
This nuget package is a driver for [LINQPad 6 and 7](http://www.linqpad.net).
|
||||
This nuget package is a driver for [LINQPad 7](http://www.linqpad.net). Support for LINQPad 6 is available via older 4.x drivers.
|
||||
|
||||
Following databases supported:
|
||||
|
||||
- **DB2** (LUW, z/OS) (only 64-bit version)
|
||||
- **ClickHouse**: using Binary, HTTP and MySQL interfaces
|
||||
- **DB2** (LUW, z/OS): x64-bit version of LINQPad only
|
||||
- **Firebird**
|
||||
- **Informix** (only 64-bit version)
|
||||
- **Microsoft Access** *(supports both OleDb and ODBC)*
|
||||
- **Microsoft Sql Server** 2005+ *(including **Microsoft Sql Azure**)*
|
||||
- **Microsoft Sql Server Compact (SqlCe)**
|
||||
- **MySql/MariaDB**
|
||||
- **Informix**: x64-bit version of LINQPad only
|
||||
- **Microsoft Access**: both OLE DB and ODBC drivers
|
||||
- **Microsoft SQL Server** 2005+ *(including **Microsoft SQL Azure**)*
|
||||
- **Microsoft SQL Server Compact (SQL CE)**
|
||||
- **MariaDB**
|
||||
- **MySql**
|
||||
- **Oracle**
|
||||
- **PostgreSQL**
|
||||
- **SQLite**
|
||||
- **SAP HANA** *(client software must be installed, supports both Native and ODBC providers)*
|
||||
- **SAP/Sybase ASE**
|
||||
- **SQLite**
|
||||
|
||||
### Installation
|
||||
## Installation
|
||||
|
||||
- Click "Add connection" in LINQPad.
|
||||
- In the "Choose Data Context" dialog, press the "View more drivers..." button.
|
||||
|
@ -26,4 +28,3 @@ Following databases supported:
|
|||
- In the "Choose Data Context" dialog, select the "LINQ to DB" driver and click the "Next" button.
|
||||
- In the "LINQ to DB connection" dialog, supply your connection information.
|
||||
- You're done.
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
variables:
|
||||
solution: 'linq2db.LINQPad.sln'
|
||||
build_configuration: 'Release'
|
||||
assemblyVersion: 4.2.0.0
|
||||
nugetVersion: 4.2.0
|
||||
nugetDevVersion: 4.2.1
|
||||
nugetPRVersion: 4.2.1
|
||||
assemblyVersion: 5.0.0.0
|
||||
nugetVersion: 5.0.0
|
||||
nugetDevVersion: 5.0.1
|
||||
nugetPRVersion: 5.0.1
|
||||
artifact_lpx: 'lpx'
|
||||
artifact_lpx6: 'lpx6'
|
||||
artifact_nuget: 'nuget'
|
||||
|
@ -98,7 +98,6 @@ stages:
|
|||
displayName: Publish to Azure Artifacts feed
|
||||
condition: eq(variables['Build.SourceBranchName'], 'master')
|
||||
|
||||
# apikey exires Oct/2020
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'push'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<DataContextDriver>
|
||||
<MainAssembly>linq2db.LINQPad.dll</MainAssembly>
|
||||
<SupportUri>https://github.com/linq2db/linq2db.LINQPad</SupportUri>
|
||||
</DataContextDriver>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<DataContextDriver>
|
||||
<MainAssembly>linq2db.LINQPad.dll</MainAssembly>
|
||||
<SupportUri>https://github.com/linq2db/linq2db.LINQPad</SupportUri>
|
||||
</DataContextDriver>
|
|
@ -2,48 +2,51 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>linq2db.LINQPad</id>
|
||||
<title>LINQ to DB driver for LINQPad 6+</title>
|
||||
<description>Supported databases: IBM DB2 LUW/zOS, Firebird, IBM Informix, Microsoft Access, Microsoft Sql Server (+Azure), Microsoft Sql Server Compact, MySql, MariaDB, Oracle, PostgreSQL, SQLite, SAP HANA, SAP/Sybase ASE.</description>
|
||||
<title>LINQ to DB driver for LINQPad 7</title>
|
||||
<description>Supported databases: IBM DB2 LUW/zOS, Firebird, IBM Informix, Microsoft Access, Microsoft Sql Server (+Azure), Microsoft Sql Server Compact, MySql, MariaDB, Oracle, PostgreSQL, SQLite, SAP HANA, SAP/Sybase ASE, ClickHouse.</description>
|
||||
<summary />
|
||||
<tags>
|
||||
linqpaddriver linqpad linq2db linqtodb access msaccess db2 odbc oledb azure firebird informix mysql mariadb oracle postgres postgresql saphana sqlce sqlserverce sqlserver sybase ase sap sqlite database
|
||||
linqpaddriver linqpad linq2db linqtodb access msaccess db2 odbc oledb azure firebird informix mysql mariadb oracle postgres postgresql saphana sqlce sqlserverce sqlserver sybase ase sap sqlite database clickhouse iseries
|
||||
</tags>
|
||||
<readme>README.md</readme>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETCoreApp3.1">
|
||||
<dependency id="LINQPad.Reference" version="1.3.0" />
|
||||
<dependency id="linq2db" version="4.2.0" />
|
||||
<group targetFramework="net6.0-windows">
|
||||
<dependency id="linq2db" version="5.1.1" />
|
||||
<dependency id="linq2db.Tools" version="5.1.1" />
|
||||
|
||||
<dependency id="Humanizer.Core" version="2.14.1" />
|
||||
<dependency id="CodeJam" version="4.1.0" />
|
||||
<dependency id="System.Configuration.ConfigurationManager" version="6.0.0" />
|
||||
<dependency id="Microsoft.CodeAnalysis.CSharp" version="4.3.0" />
|
||||
<dependency id="System.Text.Json" version="6.0.5" />
|
||||
<dependency id="dotMorten.Microsoft.SqlServer.Types" version="1.5.0" />
|
||||
<dependency id="LINQPad.Reference" version="1.3.0" />
|
||||
|
||||
<dependency id="FirebirdSql.Data.FirebirdClient" version="9.0.2" />
|
||||
<dependency id="MySqlConnector" version="2.1.13" />
|
||||
<dependency id="AdoNetCore.AseClient" version="0.19.2" />
|
||||
<dependency id="System.Data.SQLite.Core" version="1.0.116" />
|
||||
<dependency id="System.Data.SqlClient" version="4.8.3" />
|
||||
<dependency id="System.Data.Odbc" version="5.0.0" />
|
||||
<dependency id="System.Data.OleDb" version="5.0.0" />
|
||||
<dependency id="Npgsql" version="6.0.6" />
|
||||
<dependency id="Oracle.ManagedDataAccess.Core" version="3.21.70" />
|
||||
<dependency id="IBM.Data.DB2.Core" version="3.1.0.600" />
|
||||
<dependency id="System.Configuration.ConfigurationManager" version="7.0.0" />
|
||||
<dependency id="Microsoft.CodeAnalysis.CSharp" version="4.5.0" />
|
||||
<dependency id="System.Text.Json" version="7.0.2" />
|
||||
|
||||
<dependency id="FirebirdSql.Data.FirebirdClient" version="9.1.1" />
|
||||
<dependency id="MySqlConnector" version="2.2.5" />
|
||||
<dependency id="AdoNetCore.AseClient" version="0.19.2" />
|
||||
<dependency id="System.Data.SQLite.Core" version="1.0.117" />
|
||||
<dependency id="Microsoft.Data.SqlClient" version="5.1.1" />
|
||||
<dependency id="Microsoft.SqlServer.Types" version="160.1000.6" />
|
||||
<dependency id="System.Data.Odbc" version="7.0.0" />
|
||||
<dependency id="System.Data.OleDb" version="7.0.0" />
|
||||
<dependency id="Npgsql" version="7.0.2" />
|
||||
<dependency id="Oracle.ManagedDataAccess.Core" version="3.21.100" />
|
||||
<dependency id="Net.IBM.Data.Db2" version="6.0.0.300" />
|
||||
<dependency id="ClickHouse.Client" version="6.5.2" />
|
||||
<dependency id="Octonica.ClickHouseClient" version="2.2.9" />
|
||||
<!--<dependency id="linq2db4iSeries" version="4.3.0" />-->
|
||||
</group>
|
||||
</dependencies>
|
||||
<frameworkReferences>
|
||||
<group targetFramework=".NETCoreApp3.1">
|
||||
<group targetFramework="net6.0-windows">
|
||||
<frameworkReference name="Microsoft.WindowsDesktop.App.WPF" />
|
||||
</group>
|
||||
</frameworkReferences>
|
||||
</metadata>
|
||||
|
||||
<files>
|
||||
<file src="..\Source\bin\Release\netcoreapp3.1\linq2db.LINQPad.dll" target="lib\netcoreapp3.1\" />
|
||||
<file src="..\Source\bin\Release\netcoreapp3.1\Connection.png" target="lib\netcoreapp3.1\" />
|
||||
<file src="..\Source\bin\Release\netcoreapp3.1\FailedConnection.png" target="lib\netcoreapp3.1\" />
|
||||
<file src="README.md" />
|
||||
<file src="..\Source\bin\Release\net6.0-windows\linq2db.LINQPad.dll" target="lib\net6.0-windows\" />
|
||||
<file src="..\Source\bin\Release\net6.0-windows\Connection.png" target="lib\net6.0-windows\" />
|
||||
<file src="..\Source\bin\Release\net6.0-windows\FailedConnection.png" target="lib\net6.0-windows\" />
|
||||
<file src="README.md" />
|
||||
</files>
|
||||
</package>
|
||||
|
|
|
@ -1,42 +1,37 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="linq2db" Version="4.2.0" />
|
||||
<PackageVersion Include="linq2db" Version="5.1.1" />
|
||||
<PackageVersion Include="linq2db.Tools" Version="5.1.1" />
|
||||
|
||||
<PackageVersion Include="LINQPad.Reference" Version="1.3.0" />
|
||||
|
||||
<PackageVersion Include="PolySharp" Version="1.13.1" />
|
||||
|
||||
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageVersion Include="CodeJam" Version="4.1.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<PackageVersion Include="MySqlConnector" Version="2.1.13" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="7.0.2" />
|
||||
|
||||
<PackageVersion Include="System.Data.SQLite.Core" Version="1.0.117" />
|
||||
<PackageVersion Include="System.Data.Odbc" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="7.0.0" />
|
||||
<PackageVersion Include="MySqlConnector" Version="2.2.5" />
|
||||
<PackageVersion Include="AdoNetCore.AseClient" Version="0.19.2" />
|
||||
<PackageVersion Include="IBM.Data.DB.Provider" Version="11.5.5010.4" GeneratePathProperty="true" />
|
||||
<PackageVersion Include="System.Data.SQLite.Core" Version="1.0.116" />
|
||||
<PackageVersion Include="System.Data.SqlClient" Version="4.8.3" />
|
||||
<PackageVersion Include="Oracle.ManagedDataAccess" Version="19.14.0" />
|
||||
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="3.21.70" />
|
||||
<PackageVersion Include="System.Data.Odbc" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="6.0.0" />
|
||||
<PackageVersion Include="IBM.Data.DB2.Core" Version="3.1.0.600" />
|
||||
<PackageVersion Include="dotMorten.Microsoft.SqlServer.Types" Version="1.5.0" />
|
||||
<PackageVersion Include="Microsoft.SqlServer.Types" Version="14.0.1016.290" />
|
||||
<!--<PackageVersion Include="linq2db4iSeries" Version="2.9.0" />-->
|
||||
</ItemGroup>
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.1" />
|
||||
<PackageVersion Include="Oracle.ManagedDataAccess" Version="21.10.0" />
|
||||
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="3.21.100" />
|
||||
<PackageVersion Include="Net.IBM.Data.Db2" Version="6.0.0.300" />
|
||||
<PackageVersion Include="ClickHouse.Client" Version="6.5.2" />
|
||||
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="9.1.1" />
|
||||
<PackageVersion Include="Npgsql" Version="7.0.2" />
|
||||
<PackageVersion Include="Octonica.ClickHouseClient" Version="2.2.9" />
|
||||
<!--<PackageVersion Include="linq2db4iSeries" Version="5.1.0" />-->
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net461'">
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
|
||||
<PackageVersion Include="Npgsql" Version="4.1.10" />
|
||||
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="7.10.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' != 'net461' ">
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" />
|
||||
<PackageVersion Include="Npgsql" Version="6.0.6" />
|
||||
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageVersion Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,344 +0,0 @@
|
|||
<#
|
||||
{
|
||||
var beforeGenerateModel = BeforeGenerateModel;
|
||||
BeforeGenerateModel = () =>
|
||||
{
|
||||
beforeGenerateModel();
|
||||
NotifyPropertyChangedImpl();
|
||||
};
|
||||
|
||||
SetPropertyValueAction += (obj,prop,val) =>
|
||||
{
|
||||
if (prop == "IsNotifying")
|
||||
obj.IsNotifying = (bool)val;
|
||||
};
|
||||
}
|
||||
#><#+
|
||||
public bool ImplementNotifyPropertyChanging;
|
||||
public bool SkipNotifyPropertyChangedImplementation = false;
|
||||
|
||||
void NotifyPropertyChangedImpl()
|
||||
{
|
||||
foreach (Property prop in GetTreeNodes(Model).OfType<Property>().Where(p => p.IsNotifying).ToList())
|
||||
{
|
||||
List<IClassMember> parentMembers;
|
||||
|
||||
MemberGroup gr = null;
|
||||
|
||||
if (prop.Parent is Class)
|
||||
{
|
||||
var parent = (Class)prop.Parent;
|
||||
parentMembers = parent.Members;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parent = (MemberGroup)prop.Parent;
|
||||
|
||||
parent.IsCompact = false;
|
||||
|
||||
parentMembers = parent.Members;
|
||||
|
||||
if (parent.IsPropertyGroup)
|
||||
gr = parent;
|
||||
}
|
||||
|
||||
var name = prop.Name.Trim();
|
||||
var type = prop.BuildType().Trim();
|
||||
|
||||
if (gr == null)
|
||||
{
|
||||
gr = new MemberGroup
|
||||
{
|
||||
Region = name + " : " + type,
|
||||
Members = { prop },
|
||||
IsPropertyGroup = true,
|
||||
};
|
||||
|
||||
var index = parentMembers.IndexOf(prop);
|
||||
|
||||
parentMembers.RemoveAt(index);
|
||||
parentMembers.Insert (index, gr);
|
||||
}
|
||||
|
||||
gr.Conditional = prop.Conditional;
|
||||
prop.Conditional = null;
|
||||
|
||||
if (prop.IsAuto)
|
||||
{
|
||||
var field = new Field(() => type, "_" + ToCamelCase(name))
|
||||
{
|
||||
AccessModifier = AccessModifier.Private,
|
||||
InsertBlankLineAfter = false,
|
||||
};
|
||||
|
||||
if (prop.InitValue != null)
|
||||
field.InitValue = prop.InitValue;
|
||||
|
||||
gr.Members.Insert(0, field);
|
||||
|
||||
prop.Name = " " + name;
|
||||
prop.TypeBuilder = () => " " + type;
|
||||
prop.IsAuto = false;
|
||||
|
||||
if (prop.HasGetter) prop.GetBodyBuilders.Add(() => new [] { "return " + field.Name + ";" });
|
||||
if (prop.HasSetter) prop.SetBodyBuilders.Add(() => new [] { field.Name + " = value;" });
|
||||
}
|
||||
|
||||
var methods = new MemberGroup
|
||||
{
|
||||
Region = "INotifyPropertyChanged support",
|
||||
Members =
|
||||
{
|
||||
new Field(() => "const string", "NameOf" + name)
|
||||
{
|
||||
InitValue = ToStringLiteral(name),
|
||||
AccessModifier = AccessModifier.Public,
|
||||
},
|
||||
new Field(() => "PropertyChangedEventArgs", "_" + ToCamelCase(name) + "ChangedEventArgs")
|
||||
{
|
||||
InitValue = "new PropertyChangedEventArgs(NameOf" + name + ")",
|
||||
AccessModifier = AccessModifier.Private,
|
||||
IsStatic = true,
|
||||
IsReadonly = true,
|
||||
},
|
||||
new Method(() => "void", "On" + name + "Changed", null,
|
||||
() => new[] { "OnPropertyChanged(_" + ToCamelCase(name) + "ChangedEventArgs);" })
|
||||
{
|
||||
AccessModifier = AccessModifier.Private
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gr.Members.Add(methods);
|
||||
|
||||
if (prop.Dependents.Count == 0)
|
||||
prop.Dependents.Add(name);
|
||||
|
||||
if (ImplementNotifyPropertyChanging)
|
||||
{
|
||||
gr.Members.Add(new MemberGroup
|
||||
{
|
||||
Region = "INotifyPropertyChanging support",
|
||||
Members =
|
||||
{
|
||||
new Field(() => "PropertyChangingEventArgs", "_" + ToCamelCase(name) + "ChangingEventArgs")
|
||||
{
|
||||
InitValue = "new PropertyChangingEventArgs(NameOf" + name + ")",
|
||||
AccessModifier = AccessModifier.Private,
|
||||
IsStatic = true,
|
||||
IsReadonly = true,
|
||||
},
|
||||
new Method(() => "void", "On" + name + "Changing", null,
|
||||
() => new[] { "OnPropertyChanging(_" + ToCamelCase(name) + "ChangingEventArgs);" })
|
||||
{
|
||||
AccessModifier = AccessModifier.Private
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (prop.HasSetter)
|
||||
{
|
||||
var setBody = prop.BuildSetBody().Select(s => "\t" + s).ToArray();
|
||||
prop.SetBodyBuilders.Clear();
|
||||
prop.SetBodyBuilders.Add(() => setBody);
|
||||
|
||||
string getValue;
|
||||
|
||||
var getBody = prop.BuildGetBody().ToArray();
|
||||
if (getBody.Length == 1 && getBody[0].StartsWith("return"))
|
||||
{
|
||||
getValue = getBody[0].Substring("return".Length).Trim(' ', '\t', ';');
|
||||
}
|
||||
else
|
||||
{
|
||||
getValue = name;
|
||||
}
|
||||
|
||||
var insSpaces = setBody.Length > 1;
|
||||
var n = 0;
|
||||
|
||||
prop.SetBodyBuilders.Insert(n++, () => new [] {"if (" + getValue + " != value)", "{" });
|
||||
|
||||
if (ImplementNotifyPropertyChanging)
|
||||
{
|
||||
foreach (var dp in prop.Dependents)
|
||||
prop.SetBodyBuilders.Insert(n++, () => new [] { "\tOn" + dp + "Changing();" });
|
||||
prop.SetBodyBuilders.Insert(n++, () => new [] { "" });
|
||||
}
|
||||
|
||||
prop.SetBodyBuilders.Insert(n++, () => new [] { "\tBefore" + name + "Changed(value);" });
|
||||
|
||||
if (insSpaces)
|
||||
{
|
||||
prop.SetBodyBuilders.Insert(3, () => new [] { "" });
|
||||
prop.SetBodyBuilders.Add(() => new [] { "" });
|
||||
}
|
||||
|
||||
prop.SetBodyBuilders.Add(() => new [] { "\tAfter" + name + "Changed();" });
|
||||
prop.SetBodyBuilders.Add(() => new [] { "" });
|
||||
|
||||
foreach (var dp in prop.Dependents)
|
||||
prop.SetBodyBuilders.Add(() => new [] { "\tOn" + dp + "Changed();" });
|
||||
|
||||
prop.SetBodyBuilders.Add(() => new [] { "}" });
|
||||
|
||||
methods.Members.Insert(0, new MemberGroup
|
||||
{
|
||||
IsCompact = true,
|
||||
Members =
|
||||
{
|
||||
new Method(() => "void", "Before" + name + "Changed", new Func<string>[] { () => type + " newValue" }) { AccessModifier = AccessModifier.Partial },
|
||||
new Method(() => "void", "After" + name + "Changed") { AccessModifier = AccessModifier.Partial },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
prop.Parent.SetTree();
|
||||
|
||||
ITree p = prop.Parent;
|
||||
|
||||
while (!(p is Class) && p != null)
|
||||
p = p.Parent;
|
||||
|
||||
if (p != null)
|
||||
{
|
||||
var cl = (Class)p;
|
||||
|
||||
if (!SkipNotifyPropertyChangedImplementation && !cl.Interfaces.Contains("INotifyPropertyChanged"))
|
||||
{
|
||||
if (!Model.Usings.Contains("System.ComponentModel"))
|
||||
Model.Usings.Add("System.ComponentModel");
|
||||
|
||||
cl.Interfaces.Add("INotifyPropertyChanged");
|
||||
|
||||
cl.Members.Add(new MemberGroup
|
||||
{
|
||||
Region = "INotifyPropertyChanged support",
|
||||
Members =
|
||||
{
|
||||
new Event("PropertyChangedEventHandler", "PropertyChanged", true)
|
||||
{
|
||||
IsVirtual = true,
|
||||
Attributes = { new Attribute("field : NonSerialized") }
|
||||
},
|
||||
new Method(() => "void", "OnPropertyChanged", new Func<string>[] { () => "string propertyName" }, () => OnPropertyChangedBody)
|
||||
{
|
||||
AccessModifier = AccessModifier.Protected
|
||||
},
|
||||
new Method(() => "void", "OnPropertyChanged", new Func<string>[] { () => "PropertyChangedEventArgs arg" }, () => OnPropertyChangedArgBody)
|
||||
{
|
||||
AccessModifier = AccessModifier.Protected
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (ImplementNotifyPropertyChanging && !cl.Interfaces.Contains("INotifyPropertyChanging"))
|
||||
{
|
||||
if (!Model.Usings.Contains("System.ComponentModel"))
|
||||
Model.Usings.Add("System.ComponentModel");
|
||||
|
||||
cl.Interfaces.Add("INotifyPropertyChanging");
|
||||
|
||||
cl.Members.Add(new MemberGroup
|
||||
{
|
||||
Region = "INotifyPropertyChanging support",
|
||||
Members =
|
||||
{
|
||||
new Event("PropertyChangingEventHandler", "PropertyChanging", true)
|
||||
{
|
||||
IsVirtual = true,
|
||||
Attributes = { new Attribute("field : NonSerialized") }
|
||||
},
|
||||
new Method(() => "void", "OnPropertyChanging", new Func<string>[] { () => "string propertyName" }, () => OnPropertyChangingBody)
|
||||
{
|
||||
AccessModifier = AccessModifier.Protected
|
||||
},
|
||||
new Method(() => "void", "OnPropertyChanging", new Func<string>[] { () => "PropertyChangingEventArgs arg" }, () => OnPropertyChangingArgBody)
|
||||
{
|
||||
AccessModifier = AccessModifier.Protected
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string[] OnPropertyChangedBody = new[]
|
||||
{
|
||||
"var propertyChanged = PropertyChanged;",
|
||||
"",
|
||||
"if (propertyChanged != null)",
|
||||
"{",
|
||||
"\tpropertyChanged(this, new PropertyChangedEventArgs(propertyName));",
|
||||
"}",
|
||||
};
|
||||
|
||||
public string[] OnPropertyChangedArgBody = new[]
|
||||
{
|
||||
"var propertyChanged = PropertyChanged;",
|
||||
"",
|
||||
"if (propertyChanged != null)",
|
||||
"{",
|
||||
"\tpropertyChanged(this, arg);",
|
||||
"}",
|
||||
};
|
||||
|
||||
public string[] OnPropertyChangingBody = new[]
|
||||
{
|
||||
"var propertyChanging = PropertyChanging;",
|
||||
"",
|
||||
"if (propertyChanging != null)",
|
||||
"{",
|
||||
"\tpropertyChanging(this, new PropertyChangingEventArgs(propertyName));",
|
||||
"}",
|
||||
};
|
||||
|
||||
public string[] OnPropertyChangingArgBody = new[]
|
||||
{
|
||||
"var propertyChanging = PropertyChanging;",
|
||||
"",
|
||||
"if (propertyChanging != null)",
|
||||
"{",
|
||||
"\tpropertyChanging(this, arg);",
|
||||
"}",
|
||||
};
|
||||
|
||||
partial class Property
|
||||
{
|
||||
public bool IsNotifying;
|
||||
public List<string> Dependents = new List<string>();
|
||||
}
|
||||
|
||||
class NotifyingProperty : Property
|
||||
{
|
||||
public NotifyingProperty()
|
||||
{
|
||||
IsNotifying = true;
|
||||
}
|
||||
|
||||
public NotifyingProperty(ModelType type, string name, params string[] dependents)
|
||||
: base(() => type.ToTypeName(), name, null, null)
|
||||
{
|
||||
IsNotifying = true;
|
||||
|
||||
if (dependents.Length == 0)
|
||||
Dependents.Add(name);
|
||||
else
|
||||
Dependents.AddRange(dependents);
|
||||
}
|
||||
|
||||
public NotifyingProperty(string type, string name, params string[] dependents)
|
||||
: base(() => type, name, null, null)
|
||||
{
|
||||
IsNotifying = true;
|
||||
|
||||
if (dependents.Length == 0)
|
||||
Dependents.Add(name);
|
||||
else
|
||||
Dependents.AddRange(dependents);
|
||||
}
|
||||
}
|
||||
#>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2016-2022 Linq To DB Team
|
||||
Copyright (c) 2016-2023 Linq To DB Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
38
README.md
38
README.md
|
@ -1,37 +1,37 @@
|
|||
## LINQ to DB LINQPad Driver
|
||||
# LINQ to DB LINQPad Driver
|
||||
|
||||
[![NuGet Version and Downloads count](https://buildstats.info/nuget/linq2db.LINQPad?includePreReleases=true)](https://www.nuget.org/packages/linq2db.LINQPad) [![License](https://img.shields.io/github/license/linq2db/linq2db.LINQPad)](MIT-LICENSE.txt)
|
||||
|
||||
[![Master branch build](https://img.shields.io/azure-devops/build/linq2db/linq2db/8/master?label=build%20(master))](https://dev.azure.com/linq2db/linq2db/_build?definitionId=8&_a=summary) [![Latest build](https://img.shields.io/azure-devops/build/linq2db/linq2db/8?label=build%20(latest))](https://dev.azure.com/linq2db/linq2db/_build?definitionId=8&_a=summary)
|
||||
|
||||
linq2db.LINQPad is a driver for [LINQPad 5 (.NET Framework)](http://www.linqpad.net) and [LINQPad 6-7 (.NET Core)](http://www.linqpad.net).
|
||||
linq2db.LINQPad is a driver for [LINQPad 5 (.NET Framework 4.8)](http://www.linqpad.net) and [LINQPad 7 (.NET 6+)](http://www.linqpad.net).
|
||||
|
||||
Following databases supported (by all LINQPad versions if other not noted):
|
||||
Following databases supported (by all LINQPad versions if other not specified):
|
||||
|
||||
- **DB2** (LUW, z/OS) (LINQPad 6+ supports only 64-bit version)
|
||||
- **DB2 iSeries** (using [3rd-party provider](https://github.com/LinqToDB4iSeries/Linq2DB4iSeries)) *(iAccess 7.1+ software must be installed)*. **IMPORTANT:** currently available only for LINQPad 5 using linq2db.LINQPad version 2.9.3 or earlier
|
||||
- **ClickHouse**: using Binary (LINQPad 7), HTTP and MySQL interfaces
|
||||
- **DB2** (LUW, z/OS): LINQPad 7 x64 and LINQPad 5 x86
|
||||
- **Firebird**
|
||||
- **Informix** (LINQPad 6+ supports only 64-bit version)
|
||||
- **Microsoft Access** *(supports both OleDb and ODBC)*
|
||||
- **Microsoft Sql Server** 2000+ *(including **Microsoft Sql Azure**. LINQPad 6+ [doesn't support](https://stackoverflow.com/a/45418196) **Sql Server 2000**)*
|
||||
- **Microsoft Sql Server Compact (SqlCe)**
|
||||
- **MySql/MariaDB**
|
||||
- **Informix**: LINQPad 7 x64 and LINQPad 5 x86
|
||||
- **Microsoft Access**: both OLE DB and ODBC drivers
|
||||
- **Microsoft SQL Server** 2005+ *(including **Microsoft SQL Azure**)*
|
||||
- **Microsoft SQL Server Compact (SQL CE)**
|
||||
- **MariaDB**
|
||||
- **MySql**
|
||||
- **Oracle**
|
||||
- **PostgreSQL**
|
||||
- **SQLite**
|
||||
- **SAP HANA** *(client software must be installed, supports both Native and ODBC providers)*
|
||||
- **SAP/Sybase ASE**
|
||||
- **SQLite**
|
||||
|
||||
### Download
|
||||
## Download
|
||||
|
||||
Releases are hosted on [Github](https://github.com/linq2db/linq2db.LINQPad/releases) and on [Nuget](https://www.nuget.org/packages/linq2db.LINQPad) for LINQPad 6+ driver.
|
||||
Releases are hosted on [Github](https://github.com/linq2db/linq2db.LINQPad/releases) and on [nuget.org](https://www.nuget.org/packages/linq2db.LINQPad) for LINQPad 7 driver.
|
||||
|
||||
Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2db/_packaging?_a=package&feed=linq2db%40Local&package=linq2db.LINQPad&protocolType=NuGet). Feed [URL](https://pkgs.dev.azure.com/linq2db/linq2db/_packaging/linq2db/nuget/v3/index.json) ([how to use](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio#package-sources)).
|
||||
|
||||
## Installation
|
||||
|
||||
### Installation
|
||||
|
||||
#### LINQPad 6+ (NuGet)
|
||||
### LINQPad 7 (NuGet)
|
||||
|
||||
- Click "Add connection" in LINQPad.
|
||||
- In the "Choose Data Context" dialog, press the "View more drivers..." button.
|
||||
|
@ -41,7 +41,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d
|
|||
- In the "LINQ to DB connection" dialog, supply your connection information.
|
||||
- You're done.
|
||||
|
||||
#### LINQPad 6+ (Manual)
|
||||
### LINQPad 7 (Manual)
|
||||
|
||||
- Download latest **.lpx6** file from the link provided above.
|
||||
- Click "Add connection" in LINQPad.
|
||||
|
@ -52,7 +52,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d
|
|||
- In the "LINQ to DB connection" dialog, supply your connection information.
|
||||
- You're done.
|
||||
|
||||
#### LINQPad 5 (Choose a driver)
|
||||
### LINQPad 5 (Choose a driver)
|
||||
|
||||
- Click "Add connection" in LINQPad.
|
||||
- In the "Choose Data Context" dialog, press the "View more drivers..." button.
|
||||
|
@ -62,7 +62,7 @@ Latest build is hosted on [Azure Artifacts](https://dev.azure.com/linq2db/linq2d
|
|||
- In the "LINQ to DB connection" dialog, supply your connection information.
|
||||
- You're done.
|
||||
|
||||
#### LINQPad 5 (Manual)
|
||||
### LINQPad 5 (Manual)
|
||||
|
||||
- Download latest **.lpx** file from the link provided above.
|
||||
- Click "Add connection" in LINQPad.
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
#if NETCORE
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using LinqToDB.Configuration;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
internal class AppJsonConfig : ILinqToDBSettings
|
||||
{
|
||||
public static ILinqToDBSettings Load(string configPath)
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<JsonConfig>(File.ReadAllText(configPath));
|
||||
|
||||
return new AppJsonConfig(config?.ConnectionStrings?.Select(entry => (IConnectionStringSettings)new ConnectionStringSettings(entry.Key, entry.Value)).ToArray()
|
||||
?? Array.Empty<IConnectionStringSettings>());
|
||||
}
|
||||
|
||||
private readonly IConnectionStringSettings[] _connectionStrings;
|
||||
|
||||
public AppJsonConfig(IConnectionStringSettings[] connectionStrings)
|
||||
{
|
||||
_connectionStrings = connectionStrings;
|
||||
}
|
||||
|
||||
IEnumerable<IDataProviderSettings> ILinqToDBSettings.DataProviders => Array.Empty<IDataProviderSettings>();
|
||||
string? ILinqToDBSettings.DefaultConfiguration => null;
|
||||
string? ILinqToDBSettings.DefaultDataProvider => null;
|
||||
IEnumerable<IConnectionStringSettings> ILinqToDBSettings.ConnectionStrings => _connectionStrings;
|
||||
|
||||
private class JsonConfig
|
||||
{
|
||||
public IDictionary<string, string>? ConnectionStrings { get; set; }
|
||||
}
|
||||
|
||||
private class ConnectionStringSettings : IConnectionStringSettings
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public ConnectionStringSettings(string name, string connectionString)
|
||||
{
|
||||
_name = name;
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
string IConnectionStringSettings.ConnectionString => _connectionString;
|
||||
string IConnectionStringSettings.Name => _name;
|
||||
string? IConnectionStringSettings.ProviderName => null;
|
||||
bool IConnectionStringSettings.IsGlobal => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,166 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
// added temporary from https://github.com/linq2db/linq2db/pull/1393
|
||||
internal static class CSharpTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved words (keywords) taken from
|
||||
/// <see href="https://github.com/dotnet/csharplang/blob/master/spec/lexical-structure.md#keywords"/>.
|
||||
/// List actual for C# 8.0.
|
||||
/// </summary>
|
||||
private static readonly ISet<string> _reservedWords
|
||||
= new HashSet<string>()
|
||||
{
|
||||
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
|
||||
"char", "checked", "class", "const", "continue", "decimal", "default", "delegate",
|
||||
"do", "double", "else", "enum", "event", "explicit", "extern", "false",
|
||||
"finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit",
|
||||
"in", "int", "interface", "internal", "is", "lock", "long", "namespace",
|
||||
"new", "null", "object", "operator", "out", "override", "params", "private",
|
||||
"protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short",
|
||||
"sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw",
|
||||
"true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort",
|
||||
"using", "virtual", "void", "volatile", "while"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contextual words taken from
|
||||
/// <see href="https://msdn.microsoft.com/en-us/library/x53a06bb%28v=vs.140%29.aspx"/>.
|
||||
/// List actual for C# 7.3.
|
||||
/// </summary>
|
||||
private static readonly ISet<string> _contextualWords
|
||||
= new HashSet<string>()
|
||||
{
|
||||
"add", "alias", "ascending", "async", "await", "by", "descending", "dynamic",
|
||||
"equals", "from", "get", "global", "group", "into", "join", "let",
|
||||
"nameof", "on", "orderby", "partial", "remove", "select", "set", "unmanaged",
|
||||
"value", "var", "when", "where", "yield"
|
||||
};
|
||||
|
||||
private static readonly ISet<UnicodeCategory> _otherCharsCategories;
|
||||
|
||||
private static readonly ISet<UnicodeCategory> _startCharCategories;
|
||||
|
||||
static CSharpTools()
|
||||
{
|
||||
_startCharCategories = new HashSet<UnicodeCategory>()
|
||||
{
|
||||
// Lu letter
|
||||
UnicodeCategory.UppercaseLetter,
|
||||
// Ll letter
|
||||
UnicodeCategory.LowercaseLetter,
|
||||
// Lt letter
|
||||
UnicodeCategory.TitlecaseLetter,
|
||||
// Lm letter
|
||||
UnicodeCategory.ModifierLetter,
|
||||
// Lo letter
|
||||
UnicodeCategory.OtherLetter,
|
||||
// Nl letter
|
||||
UnicodeCategory.LetterNumber
|
||||
};
|
||||
|
||||
_otherCharsCategories = new HashSet<UnicodeCategory>(_startCharCategories)
|
||||
{
|
||||
// Mn
|
||||
UnicodeCategory.NonSpacingMark,
|
||||
// Mc
|
||||
UnicodeCategory.SpacingCombiningMark,
|
||||
// Nd
|
||||
UnicodeCategory.DecimalDigitNumber,
|
||||
// Pc
|
||||
UnicodeCategory.ConnectorPunctuation,
|
||||
// Cf
|
||||
UnicodeCategory.Format
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <paramref name="name"/> to valid C# identifier.
|
||||
/// </summary>
|
||||
public static string ToValidIdentifier(string? name)
|
||||
{
|
||||
if (name == null || name == string.Empty || name == "@")
|
||||
{
|
||||
return "_";
|
||||
}
|
||||
|
||||
if (_reservedWords.Contains(name) || _contextualWords.Contains(name))
|
||||
{
|
||||
return "@" + name;
|
||||
}
|
||||
|
||||
if (name.StartsWith("@"))
|
||||
{
|
||||
if (_reservedWords.Contains(name.Substring(1)) || _contextualWords.Contains(name.Substring(1)))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = name.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var chr in name)
|
||||
{
|
||||
var cat = CharUnicodeInfo.GetUnicodeCategory(chr);
|
||||
if (sb.Length == 0 && !_startCharCategories.Contains(cat) && chr != '_')
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
|
||||
if (sb.Length != 0 && !_otherCharsCategories.Contains(cat))
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(chr);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length >= 2 && sb[0] == '_' && sb[1] == '_' && (sb.Length == 2 || sb[2] != '_'))
|
||||
{
|
||||
sb.Insert(0, '_');
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ToStringLiteral(string? value)
|
||||
{
|
||||
if (value == null)
|
||||
return "null";
|
||||
|
||||
var sb = new StringBuilder("\"");
|
||||
|
||||
foreach (var chr in value)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
case '\t': sb.Append("\\t"); break;
|
||||
case '\n': sb.Append("\\n"); break;
|
||||
case '\r': sb.Append("\\r"); break;
|
||||
case '\\': sb.Append("\\\\"); break;
|
||||
case '"': sb.Append("\\\""); break;
|
||||
case '\0': sb.Append("\\0"); break;
|
||||
case '\u0085':
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
sb.Append($"\\u{(ushort)chr:X4}"); break;
|
||||
default: sb.Append(chr); break;
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append('"');
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
22
Source/CX.cs
22
Source/CX.cs
|
@ -1,22 +0,0 @@
|
|||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
// context properties names
|
||||
static class CX
|
||||
{
|
||||
public const string ProviderName = "providerName";
|
||||
public const string ProviderPath = "providerPath";
|
||||
public const string ConnectionString = "connectionString";
|
||||
public const string ExcludeRoutines = "excludeRoutines";
|
||||
public const string ExcludeFKs = "excludeFKs";
|
||||
public const string IncludeSchemas = "includeSchemas";
|
||||
public const string ExcludeSchemas = "excludeSchemas";
|
||||
public const string IncludeCatalogs = "includeCatalogs";
|
||||
public const string ExcludeCatalogs = "excludeCatalogs";
|
||||
public const string OptimizeJoins = "optimizeJoins";
|
||||
public const string UseProviderSpecificTypes = "useProviderSpecificTypes";
|
||||
public const string UseCustomFormatter = "useCustomFormatter";
|
||||
public const string CommandTimeout = "commandTimeout";
|
||||
public const string NormalizeNames = "normalizeNames";
|
||||
public const string CustomConfiguration = "customConfiguration";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
#if !NET5_0_OR_GREATER
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a readonly abstraction of a set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the set.</typeparam>
|
||||
internal interface IReadOnlySet<T> : IReadOnlyCollection<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if the set contains a specific item
|
||||
/// </summary>
|
||||
/// <param name="item">The item to check if the set contains.</param>
|
||||
/// <returns><see langword="true" /> if found; otherwise <see langword="false" />.</returns>
|
||||
bool Contains(T item);
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) subset of a specified collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set.</param>
|
||||
/// <returns><see langword="true" /> if the current set is a proper subset of other; otherwise <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool IsProperSubsetOf(IEnumerable<T> other);
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) superset of a specified collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set.</param>
|
||||
/// <returns><see langword="true" /> if the collection is a proper superset of other; otherwise <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool IsProperSupersetOf(IEnumerable<T> other);
|
||||
/// <summary>
|
||||
/// Determine whether the current set is a subset of a specified collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set.</param>
|
||||
/// <returns><see langword="true" /> if the current set is a subset of other; otherwise <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool IsSubsetOf(IEnumerable<T> other);
|
||||
/// <summary>
|
||||
/// Determine whether the current set is a super set of a specified collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set</param>
|
||||
/// <returns><see langword="true" /> if the current set is a subset of other; otherwise <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool IsSupersetOf(IEnumerable<T> other);
|
||||
/// <summary>
|
||||
/// Determines whether the current set overlaps with the specified collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set.</param>
|
||||
/// <returns><see langword="true" /> if the current set and other share at least one common element; otherwise, <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool Overlaps(IEnumerable<T> other);
|
||||
/// <summary>
|
||||
/// Determines whether the current set and the specified collection contain the same elements.
|
||||
/// </summary>
|
||||
/// <param name="other">The collection to compare to the current set.</param>
|
||||
/// <returns><see langword="true" /> if the current set is equal to other; otherwise, <see langword="false" />.</returns>
|
||||
/// <exception cref="ArgumentNullException">other is <see langword="null" />.</exception>
|
||||
bool SetEquals(IEnumerable<T> other);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,35 @@
|
|||
#if !NET5_0_OR_GREATER
|
||||
namespace System.Collections.Generic;
|
||||
|
||||
internal sealed class ReadOnlyHashSet<T> : IReadOnlySet<T>
|
||||
{
|
||||
private readonly ISet<T> _set;
|
||||
|
||||
public ReadOnlyHashSet(ISet<T> set)
|
||||
{
|
||||
_set = set;
|
||||
}
|
||||
|
||||
int IReadOnlyCollection<T>.Count => _set.Count;
|
||||
|
||||
bool IReadOnlySet<T>.Contains(T item) => _set.Contains(item);
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => _set.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_set).GetEnumerator();
|
||||
|
||||
bool IReadOnlySet<T>.IsProperSubsetOf(IEnumerable<T> other) => _set.IsProperSubsetOf(other);
|
||||
|
||||
bool IReadOnlySet<T>.IsProperSupersetOf(IEnumerable<T> other) => _set.IsProperSupersetOf(other);
|
||||
|
||||
bool IReadOnlySet<T>.IsSubsetOf(IEnumerable<T> other) => _set.IsSubsetOf(other);
|
||||
|
||||
bool IReadOnlySet<T>.IsSupersetOf(IEnumerable<T> other) => _set.IsSupersetOf(other);
|
||||
|
||||
bool IReadOnlySet<T>.Overlaps(IEnumerable<T> other) => _set.Overlaps(other);
|
||||
|
||||
bool IReadOnlySet<T>.SetEquals(IEnumerable<T> other) => _set.SetEquals(other);
|
||||
|
||||
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,13 @@
|
|||
namespace System.Collections.Generic;
|
||||
|
||||
internal static class ReadOnlySetExtensions
|
||||
{
|
||||
public static IReadOnlySet<T> AsReadOnly<T>(this HashSet<T> set)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
return set;
|
||||
#else
|
||||
return new ReadOnlyHashSet<T>(set);
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Xml;
|
||||
using LinqToDB.Configuration;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Implements Linq To DB connection settings provider, which use data from JSON config.
|
||||
/// Used as settings source for static data context.
|
||||
/// </summary>
|
||||
internal sealed class AppConfig : ILinqToDBSettings
|
||||
{
|
||||
public static ILinqToDBSettings LoadJson(string configPath)
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<JsonConfig>(File.ReadAllText(configPath));
|
||||
|
||||
if (config?.ConnectionStrings?.Count is null or 0)
|
||||
return new AppConfig(Array.Empty<IConnectionStringSettings>());
|
||||
|
||||
var connections = new Dictionary<string, ConnectionStringSettings>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var cn in config.ConnectionStrings)
|
||||
{
|
||||
if (cn.Key.EndsWith("_ProviderName", StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
connections.Add(cn.Key, new ConnectionStringSettings(cn.Key, cn.Value));
|
||||
}
|
||||
|
||||
foreach (var cn in config.ConnectionStrings)
|
||||
{
|
||||
if (!cn.Key.EndsWith("_ProviderName", StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
var key = cn.Key.Substring(0, cn.Key.Length - "_ProviderName".Length);
|
||||
if (connections.TryGetValue(key, out var cs))
|
||||
cs.ProviderName = cn.Value;
|
||||
}
|
||||
|
||||
return new AppConfig(connections.Values.ToArray());
|
||||
}
|
||||
|
||||
public static ILinqToDBSettings LoadAppConfig(string configPath)
|
||||
{
|
||||
var xml = new XmlDocument() { XmlResolver = null };
|
||||
xml.Load(XmlReader.Create(new StringReader(File.ReadAllText(configPath)), new XmlReaderSettings() { XmlResolver = null }));
|
||||
|
||||
var connections = xml.SelectNodes("/configuration/connectionStrings/add");
|
||||
|
||||
if (connections?.Count is null or 0)
|
||||
return new AppConfig(Array.Empty<IConnectionStringSettings>());
|
||||
|
||||
var settings = new List<ConnectionStringSettings>();
|
||||
|
||||
foreach (XmlElement node in connections)
|
||||
{
|
||||
var name = node.Attributes["name" ]?.Value;
|
||||
var connectionString = node.Attributes["connectionString"]?.Value;
|
||||
var providerName = node.Attributes["providerName" ]?.Value;
|
||||
|
||||
if (name != null && connectionString != null)
|
||||
settings.Add(new ConnectionStringSettings(name, connectionString) { ProviderName = providerName });
|
||||
}
|
||||
|
||||
return new AppConfig(settings.ToArray());
|
||||
}
|
||||
|
||||
private readonly IConnectionStringSettings[] _connectionStrings;
|
||||
|
||||
public AppConfig(IConnectionStringSettings[] connectionStrings)
|
||||
{
|
||||
_connectionStrings = connectionStrings;
|
||||
}
|
||||
|
||||
IEnumerable<IDataProviderSettings> ILinqToDBSettings.DataProviders => Array.Empty<IDataProviderSettings>();
|
||||
string? ILinqToDBSettings.DefaultConfiguration => null;
|
||||
string? ILinqToDBSettings.DefaultDataProvider => null;
|
||||
IEnumerable<IConnectionStringSettings> ILinqToDBSettings.ConnectionStrings => _connectionStrings;
|
||||
|
||||
private sealed class JsonConfig
|
||||
{
|
||||
public IDictionary<string, string>? ConnectionStrings { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ConnectionStringSettings : IConnectionStringSettings
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public ConnectionStringSettings(string name, string connectionString)
|
||||
{
|
||||
_name = name;
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
string IConnectionStringSettings.ConnectionString => _connectionString;
|
||||
string IConnectionStringSettings.Name => _name;
|
||||
bool IConnectionStringSettings.IsGlobal => false;
|
||||
|
||||
public string? ProviderName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,541 @@
|
|||
using System.Data.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Xml.Linq;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.LINQPad.Json;
|
||||
using PN = LinqToDB.ProviderName;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
// IMPORTANT:
|
||||
// settings, marked by [JsonIgnore] stored in default LINQPad connection option properties and must be copied manually on settings save/load
|
||||
internal sealed class ConnectionSettings
|
||||
{
|
||||
#region Save/Load/Migrate
|
||||
/// <summary>
|
||||
/// Starting from v5 release we store json string in settings instead of multiple XML nodes to simplify settings management.
|
||||
/// </summary>
|
||||
private const string SETTINGS_NODE = "SettingsV5";
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
static ConnectionSettings()
|
||||
{
|
||||
_jsonOptions = new()
|
||||
{
|
||||
// deserialization options: use permissive options
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
// serialization options
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
// register IReadOnlySet<T> converter factory
|
||||
_jsonOptions.Converters.Add(IReadOnlySetConverter<string>.Factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load connection settings from LINQPad connection object.
|
||||
/// </summary>
|
||||
public static ConnectionSettings Load(IConnectionInfo cxInfo)
|
||||
{
|
||||
ConnectionSettings? settings = null;
|
||||
|
||||
var json = GetString(cxInfo, SETTINGS_NODE);
|
||||
|
||||
if (json != null)
|
||||
{
|
||||
settings = JsonSerializer.Deserialize<ConnectionSettings>(json, _jsonOptions);
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
settings.Connection ??= new();
|
||||
settings.Schema ??= new();
|
||||
settings.Scaffold ??= new();
|
||||
settings.LinqToDB ??= new();
|
||||
settings.StaticContext ??= new();
|
||||
}
|
||||
}
|
||||
|
||||
settings ??= Legacy.Load(cxInfo);
|
||||
|
||||
// load data from predefined IConnectionInfo properties
|
||||
// Main reason we use predefined properties is to provide connection configuration options to LINQPad so it could use it
|
||||
// for raw database access functionality
|
||||
settings.Connection.ConnectionString = cxInfo.DatabaseInfo.CustomCxString;
|
||||
settings.Connection.Server = cxInfo.DatabaseInfo.Server;
|
||||
settings.Connection.DatabaseName = cxInfo.DatabaseInfo.Database;
|
||||
settings.Connection.DbVersion = cxInfo.DatabaseInfo.DbVersion;
|
||||
settings.Connection.EncryptConnectionString = cxInfo.DatabaseInfo.EncryptCustomCxString;
|
||||
settings.Connection.ProviderFactory = cxInfo.DatabaseInfo.Provider;
|
||||
settings.Connection.DisplayName = cxInfo.DisplayName;
|
||||
settings.Connection.IsProduction = cxInfo.IsProduction;
|
||||
settings.Connection.Persistent = cxInfo.Persist;
|
||||
|
||||
settings.Scaffold.Pluralize = !cxInfo.DynamicSchemaOptions.NoPluralization;
|
||||
settings.Scaffold.Capitalize = !cxInfo.DynamicSchemaOptions.NoCapitalization;
|
||||
|
||||
settings.StaticContext.ConfigurationPath = cxInfo.AppConfigPath;
|
||||
settings.StaticContext.ContextTypeName = cxInfo.CustomTypeInfo.CustomTypeName;
|
||||
settings.StaticContext.ContextAssemblyPath = cxInfo.CustomTypeInfo.CustomAssemblyPath;
|
||||
|
||||
// manually decrypt secondary connection
|
||||
if (settings.Connection.EncryptConnectionString && settings.Connection.SecondaryConnectionString != null)
|
||||
settings.Connection.SecondaryConnectionString = cxInfo.Decrypt(settings.Connection.SecondaryConnectionString);
|
||||
|
||||
return settings;
|
||||
|
||||
// TODO: debug method to reset modifications
|
||||
//return LoadLegacySettings(cxInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save connection settings to LINQPad connection object.
|
||||
/// This method should be called from <see cref="DriverHelper.ShowConnectionDialog(IConnectionInfo, ConnectionDialogOptions, bool)"/> method only.
|
||||
/// </summary>
|
||||
public void Save(IConnectionInfo cxInfo)
|
||||
{
|
||||
// encrypt sencondary connection string manually
|
||||
if (Connection.EncryptConnectionString && Connection.SecondaryConnectionString != null)
|
||||
Connection.SecondaryConnectionString = cxInfo.Encrypt(Connection.SecondaryConnectionString);
|
||||
|
||||
// save data, stored in predefined IConnectionInfo properties to them
|
||||
cxInfo.DatabaseInfo.CustomCxString = Connection.ConnectionString;
|
||||
cxInfo.DatabaseInfo.Provider = Connection.ProviderFactory;
|
||||
cxInfo.DatabaseInfo.EncryptCustomCxString = Connection.EncryptConnectionString;
|
||||
cxInfo.DatabaseInfo.DbVersion = Connection.DbVersion;
|
||||
cxInfo.DatabaseInfo.Database = Connection.DatabaseName;
|
||||
cxInfo.DatabaseInfo.Server = Connection.Server;
|
||||
|
||||
cxInfo.DynamicSchemaOptions.NoPluralization = !Scaffold.Pluralize;
|
||||
cxInfo.DynamicSchemaOptions.NoCapitalization = !Scaffold.Capitalize;
|
||||
|
||||
cxInfo.DisplayName = Connection.DisplayName;
|
||||
cxInfo.IsProduction = Connection.IsProduction;
|
||||
cxInfo.Persist = Connection.Persistent;
|
||||
cxInfo.AppConfigPath = StaticContext.ConfigurationPath;
|
||||
cxInfo.CustomTypeInfo.CustomTypeName = StaticContext.ContextTypeName;
|
||||
cxInfo.CustomTypeInfo.CustomAssemblyPath = StaticContext.ContextAssemblyPath;
|
||||
|
||||
var json = JsonSerializer.Serialize(this, _jsonOptions);
|
||||
SetString(cxInfo, SETTINGS_NODE, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy options migration support.
|
||||
/// </summary>
|
||||
private static class Legacy
|
||||
{
|
||||
// list item separators for legacy options
|
||||
private static readonly char[] _listSeparators = new[]{ ',', ';' };
|
||||
|
||||
// legacy options
|
||||
private const string ProviderName = "providerName";
|
||||
private const string ProviderPath = "providerPath";
|
||||
private const string ConnectionString = "connectionString";
|
||||
private const string ExcludeRoutines = "excludeRoutines";
|
||||
private const string ExcludeFKs = "excludeFKs";
|
||||
private const string IncludeSchemas = "includeSchemas";
|
||||
private const string ExcludeSchemas = "excludeSchemas";
|
||||
private const string IncludeCatalogs = "includeCatalogs";
|
||||
private const string ExcludeCatalogs = "excludeCatalogs";
|
||||
private const string OptimizeJoins = "optimizeJoins";
|
||||
private const string UseProviderSpecificTypes = "useProviderSpecificTypes";
|
||||
private const string UseCustomFormatter = "useCustomFormatter";
|
||||
private const string CommandTimeout = "commandTimeout";
|
||||
private const string NormalizeNames = "normalizeNames";
|
||||
private const string CustomConfiguration = "customConfiguration";
|
||||
|
||||
public static ConnectionSettings Load(IConnectionInfo cxInfo)
|
||||
{
|
||||
var settings = new ConnectionSettings();
|
||||
settings.Connection = new();
|
||||
settings.Schema = new();
|
||||
settings.Scaffold = new();
|
||||
settings.LinqToDB = new();
|
||||
settings.StaticContext = new();
|
||||
|
||||
// 1. ProviderName migration
|
||||
|
||||
// old provider name option replaced with two options: database and database provider
|
||||
settings.Connection.Provider = GetString(cxInfo, ProviderName);
|
||||
|
||||
// this native oracle provider was removed long time ago and not supported in v5 too
|
||||
if (settings.Connection.Provider == PN.OracleNative)
|
||||
settings.Connection.Provider = PN.OracleManaged;
|
||||
|
||||
// switch contains only provider names, used by pre-v5 driver
|
||||
settings.Connection.Database = settings.Connection.Provider switch
|
||||
{
|
||||
|
||||
PN.AccessOdbc => PN.Access,
|
||||
PN.MySqlConnector => PN.MySql,
|
||||
PN.SybaseManaged => PN.Sybase,
|
||||
PN.SQLiteClassic => PN.SQLite,
|
||||
PN.InformixDB2 => PN.Informix,
|
||||
PN.SapHanaNative
|
||||
or PN.SapHanaOdbc => PN.SapHana,
|
||||
PN.OracleManaged => PN.Oracle,
|
||||
// preserve same name
|
||||
PN.Firebird
|
||||
or PN.Access
|
||||
or PN.PostgreSQL
|
||||
or PN.DB2LUW
|
||||
or PN.DB2zOS
|
||||
or PN.SqlServer
|
||||
//or DB2iSeriesProviderName.DB2
|
||||
or PN.SqlCe => settings.Connection.Provider,
|
||||
_ => null
|
||||
};
|
||||
|
||||
// 2. IncludeSchemas, ExcludeSchemas, IncludeCatalogs and ExcludeCatalogs migration
|
||||
|
||||
// 2. convert comma/semicolon-separated strings with schemas/catalogs to list + flag
|
||||
var strValue = GetString(cxInfo, ExcludeSchemas);
|
||||
var schemas = strValue == null ? null : new HashSet<string>(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries));
|
||||
if (schemas != null && schemas.Count > 0)
|
||||
settings.Schema.IncludeSchemas = false;
|
||||
else
|
||||
{
|
||||
strValue = GetString(cxInfo, IncludeSchemas);
|
||||
schemas = strValue == null ? null : new HashSet<string>(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries));
|
||||
if (schemas != null && schemas.Count > 0)
|
||||
settings.Schema.IncludeSchemas = true;
|
||||
}
|
||||
|
||||
settings.Schema.Schemas = schemas?.AsReadOnly();
|
||||
|
||||
strValue = GetString(cxInfo, ExcludeCatalogs);
|
||||
var catalogs = strValue == null ? null : new HashSet<string>(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries));
|
||||
if (catalogs != null && catalogs.Count > 0)
|
||||
settings.Schema.IncludeCatalogs = false;
|
||||
else
|
||||
{
|
||||
strValue = GetString(cxInfo, IncludeCatalogs);
|
||||
catalogs = strValue == null ? null : new HashSet<string>(strValue.Split(_listSeparators, StringSplitOptions.RemoveEmptyEntries));
|
||||
if (catalogs != null && catalogs.Count > 0)
|
||||
settings.Schema.IncludeCatalogs = true;
|
||||
}
|
||||
settings.Schema.Catalogs = catalogs?.AsReadOnly();
|
||||
|
||||
// 3. ExcludeRoutines migration
|
||||
|
||||
settings.Schema.LoadAggregateFunctions
|
||||
= settings.Schema.LoadScalarFunctions
|
||||
= settings.Schema.LoadTableFunctions
|
||||
= settings.Schema.LoadProcedures
|
||||
= !GetBoolean(cxInfo, ExcludeRoutines, true).Value;
|
||||
|
||||
// 4. ExcludeFKs migration
|
||||
settings.Schema.LoadForeignKeys = !GetBoolean(cxInfo, ExcludeFKs, false).Value;
|
||||
|
||||
// 5. ProviderPath migration
|
||||
settings.Connection.ProviderPath = GetString(cxInfo, ProviderPath);
|
||||
|
||||
// 6. CommandTimeout migration
|
||||
// note that in pre-v5 it was non-nullable option so it wasn't possible to use default db/provider timeout
|
||||
settings.Connection.CommandTimeout = GetInt32(cxInfo, CommandTimeout);
|
||||
|
||||
// 7. ConnectionString migration
|
||||
// note that in practice pre-v4 never stored connection in custom field and used CustomCxString as storage
|
||||
settings.Connection.ConnectionString = GetString(cxInfo, ConnectionString);
|
||||
if (!string.IsNullOrWhiteSpace(settings.Connection.ConnectionString))
|
||||
cxInfo.DatabaseInfo.CustomCxString = settings.Connection.ConnectionString;
|
||||
|
||||
// 8. OptimizeJoins migration
|
||||
settings.LinqToDB.OptimizeJoins = GetBoolean(cxInfo, OptimizeJoins, true).Value;
|
||||
|
||||
// 9. UseProviderSpecificTypes migration
|
||||
settings.Scaffold.UseProviderTypes = GetBoolean(cxInfo, UseProviderSpecificTypes, false).Value;
|
||||
|
||||
// 10. CustomConfiguration migration
|
||||
settings.StaticContext.ConfigurationName = GetString(cxInfo, CustomConfiguration);
|
||||
|
||||
// ignored options:
|
||||
// UseCustomFormatter - removed in v5
|
||||
// NormalizeNames - not used in pre-v5 and v5 (never used?)
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||
private static int? GetInt32(IConnectionInfo cxInfo, XName name, int? defaultValue = null)
|
||||
{
|
||||
var strValue = GetString(cxInfo, name);
|
||||
|
||||
if (strValue != null && int.TryParse(strValue, NumberStyles.None, CultureInfo.InvariantCulture, out var intValue))
|
||||
return intValue;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||
private static bool? GetBoolean(IConnectionInfo cxInfo, XName name, bool? defaultValue = null)
|
||||
{
|
||||
var strValue = GetString(cxInfo, name);
|
||||
return strValue == "true" ? true : strValue == "false" ? false : defaultValue;
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||
private static string? GetString(IConnectionInfo cxInfo, XName name, string? defaultValue = null) => cxInfo.DriverData.Element(name)?.Value ?? defaultValue;
|
||||
|
||||
private static void SetString(IConnectionInfo cxInfo, XName name, string? value)
|
||||
{
|
||||
if (value != null)
|
||||
cxInfo.DriverData.SetElementValue(name, value);
|
||||
else
|
||||
cxInfo.DriverData.Element(name)?.Remove();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public ConnectionOptions Connection { get; set; } = null!;
|
||||
public SchemaOptions Schema { get; set; } = null!;
|
||||
public ScaffoldOptions Scaffold { get; set; } = null!;
|
||||
public LinqToDbOptions LinqToDB { get; set; } = null!;
|
||||
public StaticContextOptions StaticContext { get; set; } = null!;
|
||||
|
||||
public sealed class ConnectionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Database identifier. Usually generic name from <see cref="PN"/>.
|
||||
/// </summary>
|
||||
public string? Database { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database provider identifier. Specific name from <see cref="PN"/>.
|
||||
/// </summary>
|
||||
public string? Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database provider assembly path.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ProviderPath
|
||||
{
|
||||
get => IntPtr.Size == 4 ? ProviderPathx86 ?? ProviderPathx64 : ProviderPathx64 ?? ProviderPathx86;
|
||||
set
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
ProviderPathx86 = value;
|
||||
else
|
||||
ProviderPathx64 = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Database provider assembly path.
|
||||
/// </summary>
|
||||
public string? ProviderPathx86 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database provider assembly path.
|
||||
/// </summary>
|
||||
public string? ProviderPathx64 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Command timeout. <c>null</c> for provider/database default timeout.
|
||||
/// </summary>
|
||||
public int? CommandTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database provider name for secondary schema connection.
|
||||
/// </summary>
|
||||
public string? SecondaryProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secondary schema connection string.
|
||||
/// </summary>
|
||||
public string? SecondaryConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User-defined connection name.
|
||||
/// Stored in <see cref="IConnectionInfo.DisplayName"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks connected database as containing production data.
|
||||
/// Stored in <see cref="IConnectionInfo.IsProduction"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool IsProduction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks connection as persistent (saved before restarts).
|
||||
/// Stored in <see cref="IConnectionInfo.Persist"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool Persistent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connection string. Stored in <see cref="IDatabaseInfo.CustomCxString"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stored in <see cref="IDatabaseInfo.Provider"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ProviderFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database information (<see cref="DbConnection.DataSource"/>).
|
||||
/// Stored in <see cref="IDatabaseInfo.Server"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? Server { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database information (<see cref="DbConnection.Database"/>).
|
||||
/// Stored in <see cref="IDatabaseInfo.Database"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? DatabaseName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database information (<see cref="DbConnection.ServerVersion"/>).
|
||||
/// Stored in <see cref="IDatabaseInfo.DbVersion"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? DbVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Instructs LINQPad to encrypt <see cref="ConnectionString"/> value.
|
||||
/// Also instruct us to encrypt <see cref="SecondaryConnectionString"/> value.
|
||||
/// Stored in <see cref="IDatabaseInfo.EncryptCustomCxString"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool EncryptConnectionString { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SchemaOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Include/exclude schemas, specified by <see cref="Schemas"/> option.
|
||||
/// </summary>
|
||||
public bool IncludeSchemas { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of schemas to include/exclude (defined by <see cref="IncludeSchemas"/> option).
|
||||
/// </summary>
|
||||
public IReadOnlySet<string>? Schemas { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include/exclude catalogs, specified by <see cref="Catalogs"/> option.
|
||||
/// </summary>
|
||||
public bool IncludeCatalogs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of catalogs to include/exclude (defined by <see cref="IncludeCatalogs"/> option).
|
||||
/// </summary>
|
||||
public IReadOnlySet<string>? Catalogs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate stored procedures.
|
||||
/// </summary>
|
||||
public bool LoadProcedures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate table functions.
|
||||
/// </summary>
|
||||
public bool LoadTableFunctions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate scalar functions.
|
||||
/// </summary>
|
||||
public bool LoadScalarFunctions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate aggregate functions.
|
||||
/// </summary>
|
||||
public bool LoadAggregateFunctions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populate foreign keys.
|
||||
/// </summary>
|
||||
public bool LoadForeignKeys { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ScaffoldOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use provider data types.
|
||||
/// </summary>
|
||||
public bool UseProviderTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Map FixedString(X) to <see cref="string"/> for ClickHouse.
|
||||
/// </summary>
|
||||
public bool ClickHouseFixedStringAsString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables pluralization context table property name and collection-type association property name.
|
||||
/// Stored in <see cref="IDynamicSchemaOptions.NoPluralization"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool Pluralize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables capitalization of table column properties.
|
||||
/// Stored in <see cref="IDynamicSchemaOptions.NoCapitalization"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool Capitalize { get; set; }
|
||||
}
|
||||
|
||||
public sealed class LinqToDbOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Value for <see cref="Common.Configuration.Linq.OptimizeJoins"/> Linq To DB setting.
|
||||
/// </summary>
|
||||
public bool OptimizeJoins { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StaticContextOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of custom configuration (connection string name), passed to context constructor.
|
||||
/// </summary>
|
||||
public string? ConfigurationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to custom configuration file.
|
||||
/// For LINQPad 5 it should be in app.config format, for .NET Core versions - in appsettings.json format.
|
||||
/// Stored in <see cref="IConnectionInfo.AppConfigPath"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore] // strored in linqpad storage
|
||||
public string? ConfigurationPath { get; set; }
|
||||
|
||||
#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
/// Path to appsettings.json configuration file for LINQPad 5.
|
||||
/// We cannot store it in <see cref="IConnectionInfo.AppConfigPath"/> as LINQPad will try to use
|
||||
/// it as app.config file and fail.
|
||||
/// </summary>
|
||||
public string? LocalConfigurationPath { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Full name of data context class (namespace + class name) in custom context assembly.
|
||||
/// Stored in <see cref="ICustomTypeInfo.CustomTypeName"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ContextTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full path to custom context assembly.
|
||||
/// Stored in <see cref="ICustomTypeInfo.CustomTypeName"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ContextAssemblyPath { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LinqToDB.LINQPad.Json;
|
||||
|
||||
internal sealed class IReadOnlySetConverter<T> : JsonConverter<IReadOnlySet<T>>
|
||||
{
|
||||
private readonly JsonConverter<T> _elementConverter;
|
||||
private readonly Type _elementType = typeof(T);
|
||||
|
||||
private static IReadOnlySetConverter<T>? _instance;
|
||||
|
||||
public static readonly JsonConverterFactory Factory = new IReadOnlySetConverterFactory();
|
||||
|
||||
private static JsonConverter GetInstance(JsonConverter<T> elementConverter)
|
||||
{
|
||||
return _instance ??= new IReadOnlySetConverter<T>(elementConverter);
|
||||
}
|
||||
|
||||
private IReadOnlySetConverter(JsonConverter<T> elementConverter)
|
||||
{
|
||||
_elementConverter = elementConverter;
|
||||
}
|
||||
|
||||
public override IReadOnlySet<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var hashSet = new HashSet<T>();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var item = _elementConverter.Read(ref reader, _elementType, options);
|
||||
hashSet.Add(item!);
|
||||
}
|
||||
|
||||
return hashSet.AsReadOnly();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, IReadOnlySet<T> value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in value)
|
||||
{
|
||||
_elementConverter.Write(writer, item, options);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
private sealed class IReadOnlySetConverterFactory : JsonConverterFactory
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
if (!typeToConvert.IsGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeToConvert.GetGenericTypeDefinition() == typeof(IReadOnlySet<>);
|
||||
}
|
||||
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var elementType = typeToConvert.GetGenericArguments()[0];
|
||||
|
||||
var converterType = typeof(IReadOnlySetConverter<>).MakeGenericType(elementType);
|
||||
return (JsonConverter)converterType
|
||||
.GetMethod(nameof(IReadOnlySetConverter<int>.GetInstance), BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.Invoke(null, new[] { options.GetConverter(elementType) })!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
<Window x:Class="LinqToDB.LINQPad.ConnectionDialog"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm = "clr-namespace:LinqToDB.LINQPad"
|
||||
|
||||
Title = "LINQ to DB Connection"
|
||||
mc:Ignorable = "d"
|
||||
Width = "550"
|
||||
SizeToContent = "Height"
|
||||
ResizeMode = "NoResize"
|
||||
WindowStartupLocation = "CenterScreen"
|
||||
FontSize = "14"
|
||||
d:DataContext = "{d:DesignInstance vm:ConnectionViewModel, IsDesignTimeCreatable=true}">
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0" Header="Custom Assembly Options" Visibility="{Binding StaticVisibility}">
|
||||
<Grid Margin="5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0">
|
||||
<Label Padding="0,0,0,3" DockPanel.Dock="Right">
|
||||
<Hyperlink Click="BrowseAssembly">Browse...</Hyperlink>
|
||||
</Label>
|
||||
<Label Padding="1,0,1,3">Path to custom assembly</Label>
|
||||
</DockPanel>
|
||||
<TextBox Grid.Row="1" Text="{Binding CustomAssemblyPath}" />
|
||||
|
||||
<DockPanel Grid.Row="2">
|
||||
<Label Padding="0,3,0,3" DockPanel.Dock="Right">
|
||||
<Hyperlink Click="ChooseType">Choose...</Hyperlink>
|
||||
</Label>
|
||||
<Label Padding="1,3,1,3">Full name of custom type</Label>
|
||||
</DockPanel>
|
||||
<TextBox Grid.Row="3" Text="{Binding CustomTypeName}" />
|
||||
|
||||
<DockPanel Grid.Row="4">
|
||||
<Label Padding="0,3,0,3" DockPanel.Dock="Right">
|
||||
<Hyperlink Click="BrowseAppConfig">Browse...</Hyperlink>
|
||||
</Label>
|
||||
<Label Padding="1,3,1,3">Application config file (if required)</Label>
|
||||
</DockPanel>
|
||||
<TextBox Grid.Row="5" Text="{Binding AppConfigPath}" />
|
||||
|
||||
<DockPanel Grid.Row="6">
|
||||
<Label Padding="0,3,0,3" DockPanel.Dock="Right">
|
||||
<Hyperlink Click="ChooseConfiguration">Choose...</Hyperlink>
|
||||
</Label>
|
||||
<Label Padding="1,3,1,3">Configuration (optional)</Label>
|
||||
</DockPanel>
|
||||
<TextBox Grid.Row="7" Text="{Binding CustomConfiguration}" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1" Header="Data Connection Options" Visibility="{Binding DynamicVisibility}">
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="87*"/>
|
||||
<ColumnDefinition Width="52*"/>
|
||||
<ColumnDefinition Width="363*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,5" Grid.ColumnSpan="3">
|
||||
<Label Padding="0,0,0,3">Data Provider</Label>
|
||||
<ComboBox ItemsSource="{Binding Providers}" SelectedValue="{Binding SelectedProvider}" DisplayMemberPath="Description" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0,0,0,5" Grid.ColumnSpan="3" >
|
||||
<Label Padding="0,0,0,3">Connection String</Label>
|
||||
<TextBox Text="{Binding ConnectionString}" TextWrapping="Wrap" Height="60" VerticalScrollBarVisibility="Auto" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CheckBox Grid.Column="0" Content="Encrypt Connection String" FontSize="12" IsChecked="{Binding EncryptConnectionString}" Margin="0,5,0,0" />
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<StackPanel.Resources>
|
||||
<vm:StringToIntegerConverter x:Key="StringToIntegerConverter" />
|
||||
</StackPanel.Resources>
|
||||
<TextBox Text="{Binding CommandTimeout, Converter={StaticResource StringToIntegerConverter}}" Width="50" Height="20" FontSize="12" />
|
||||
<Label FontSize="12" Height="20" Padding="5,2" Content="Command Timeout (in seconds)"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="3" Grid.ColumnSpan="3" Margin="0,5,0,5">
|
||||
<Label Padding="0,0,0,3">Include Schemas (dbo,master) (optional)</Label>
|
||||
<TextBox Text="{Binding IncludeSchemas}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="4" Margin="0,0,0,0" Grid.ColumnSpan="3" >
|
||||
<Label Padding="0,0,0,3">Exclude Schemas (dbo,master) (optional)</Label>
|
||||
<TextBox Text="{Binding ExcludeSchemas}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="5" Margin="0,5,0,5" Grid.ColumnSpan="3" >
|
||||
<Label Padding="0,0,0,3">Include Catalogs (optional)</Label>
|
||||
<TextBox Text="{Binding IncludeCatalogs}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="6" Margin="0,0,0,0" Grid.ColumnSpan="3" >
|
||||
<Label Padding="0,0,0,3">Exclude Catalogs (optional)</Label>
|
||||
<TextBox Text="{Binding ExcludeCatalogs}" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Grid Grid.Row="2" Visibility="{Binding DynamicVisibility}" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="5*" />
|
||||
<ColumnDefinition Width="4*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<GroupBox Grid.Column="0" Header="Data Context Options" Margin="0,0,3,0">
|
||||
<StackPanel Margin="5">
|
||||
<CheckBox Content="Pluralize Table properties" FontSize="12" IsChecked="{Binding Pluralize}" />
|
||||
<CheckBox Content="Capitalize property names" FontSize="12" IsChecked="{Binding Capitalize}" Margin="0,5" />
|
||||
<CheckBox Content="Include Stored Procedures and Functions" FontSize="12" IsChecked="{Binding IncludeRoutines}" />
|
||||
<CheckBox Content="Include Foreign Keys" FontSize="12" IsChecked="{Binding IncludeFKs}" Margin="0,5" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Column="1" Header="LINQ to DB Options" Margin="3,0,0,0">
|
||||
<StackPanel Margin="5">
|
||||
<CheckBox Content="Use provider specific types" FontSize="12" IsChecked="{Binding UseProviderSpecificTypes}" />
|
||||
<CheckBox Content="Use LINQ to DB formatter" FontSize="12" IsChecked="{Binding UseCustomFormatter}" Margin="0,5" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<GroupBox Grid.Row="3" Header="Connection Options">
|
||||
<StackPanel Margin="5">
|
||||
<Label Padding="0,0,0,3">Friendly Name (optional)</Label>
|
||||
<TextBox Text="{Binding Name}" />
|
||||
<Grid Margin="0,5,0,0" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<CheckBox Content="Remember this connection" FontSize="12" IsChecked="{Binding Persist}" Margin="0,0,0,5" />
|
||||
<CheckBox Content="Contains production data" FontSize="12" IsChecked="{Binding IsProduction}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1">
|
||||
<CheckBox Content="Optimize joins" FontSize="12" IsChecked="{Binding OptimizeJoins}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<DockPanel Grid.Row="0" Visibility="{Binding ProviderPathVisibility}" Margin="0,5,0,0">
|
||||
<Label Padding="0,0,0,3" DockPanel.Dock="Right">
|
||||
<Hyperlink Click="BrowseProvider">Browse...</Hyperlink>
|
||||
</Label>
|
||||
<Label Padding="1,0,1,3" Content="{Binding ProviderPathLabel, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<TextBox Grid.Row="1" Text="{Binding ProviderPath}" Visibility="{Binding ProviderPathVisibility}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<Grid Grid.Row="4" Margin="0,10,0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="70" />
|
||||
<ColumnDefinition Width="70" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0" Width="65" HorizontalAlignment="Left" Content="Test" Click="TestClick" />
|
||||
<Button Grid.Column="1" Width="65" HorizontalAlignment="Right" Content="OK" IsDefault="True" Click="OKClick" />
|
||||
<Button Grid.Column="2" Width="65" HorizontalAlignment="Right" Content="Cancel" IsCancel="True" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
|
@ -1,295 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LINQPad.Extensibility.DataContext.UI;
|
||||
using LinqToDB.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
public partial class ConnectionDialog
|
||||
{
|
||||
public ConnectionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private ConnectionViewModel? Model => DataContext as ConnectionViewModel;
|
||||
|
||||
ConnectionDialog(ConnectionViewModel model)
|
||||
: this()
|
||||
{
|
||||
DataContext = model;
|
||||
|
||||
((INotifyPropertyChanged)model).PropertyChanged += (sender, args) =>
|
||||
{
|
||||
if (args.PropertyName == nameof(ConnectionViewModel.IncludeRoutines) && model.IncludeRoutines)
|
||||
{
|
||||
MessageBox.Show(this,
|
||||
"Including Stored Procedures may be dangerous in production if the selected database driver does not support CommandBehavior.SchemaOnly option or procedure is not safe for CommandBehavior.SchemaOnly execution mode.",
|
||||
"Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
if (Model != null && args.PropertyName == nameof(ConnectionViewModel.ProviderPathLabel))
|
||||
Model.ProviderPath = GetDefaultProviderPath();
|
||||
};
|
||||
}
|
||||
|
||||
Func<ConnectionViewModel?, Exception?>? _connectionTester;
|
||||
|
||||
public static bool Show(ConnectionViewModel model, Func<ConnectionViewModel?, Exception?>? connectionTester)
|
||||
{
|
||||
return new ConnectionDialog(model) { _connectionTester = connectionTester }.ShowDialog() == true;
|
||||
}
|
||||
|
||||
void TestClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_connectionTester != null)
|
||||
{
|
||||
Exception? ex;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
ex = _connectionTester(Model);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
|
||||
if (ex == null)
|
||||
{
|
||||
MessageBox.Show(this, "Successful!", "Connection Test", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OKClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Exception? ex;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
ex = _connectionTester?.Invoke(Model);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
if (ex == null)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MessageBox.Show(
|
||||
this,
|
||||
$"{ex.Message}\r\n\r\nDo you want to continue?",
|
||||
"Error",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Stop) == MessageBoxResult.Yes)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrowseAssembly(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model == null)
|
||||
return;
|
||||
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "Choose custom assembly",
|
||||
DefaultExt = ".dll",
|
||||
FileName = Model.CustomAssemblyPath,
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.CustomAssemblyPath = dialog.FileName;
|
||||
}
|
||||
|
||||
string? GetDefaultProviderPath()
|
||||
{
|
||||
if (Model == null)
|
||||
return null;
|
||||
|
||||
return Model.SelectedProvider?.Name switch
|
||||
{
|
||||
ProviderName.SqlCe => IntPtr.Size == 4
|
||||
? @"c:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Private\System.Data.SqlServerCe.dll"
|
||||
: @"c:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Private\System.Data.SqlServerCe.dll",
|
||||
ProviderName.SapHanaNative => @"c:\Program Files (x86)\sap\hdbclient\dotnetcore\v2.1\Sap.Data.Hana.Core.v2.1.dll",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
void BrowseProvider(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model == null)
|
||||
return;
|
||||
|
||||
var defaultPath = GetDefaultProviderPath();
|
||||
if (defaultPath == null)
|
||||
return;
|
||||
|
||||
var fileName = Path.GetFileName(defaultPath);
|
||||
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = $"Select {fileName}",
|
||||
DefaultExt = ".dll",
|
||||
FileName = Model.ProviderPath,
|
||||
InitialDirectory = Path.GetDirectoryName(Model.ProviderPath ?? defaultPath),
|
||||
Filter = $"{fileName}|{fileName}",
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.ProviderPath = dialog.FileName;
|
||||
}
|
||||
|
||||
void ChooseType(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model != null)
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
Model.CustomAssemblyPath = Model.CustomAssemblyPath!.Trim();
|
||||
|
||||
var assembly = DataContextDriver.LoadAssemblySafely(Model.CustomAssemblyPath);
|
||||
var customTypes = assembly.GetExportedTypes().Where(IsDataConnection).Select(t => t.FullName).Cast<object>().ToArray();
|
||||
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
|
||||
var result = (string?)Dialogs.PickFromList("Choose Custom Type", customTypes);
|
||||
|
||||
if (result != null)
|
||||
Model.CustomTypeName = result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "Assembly load error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDataConnection(Type type)
|
||||
{
|
||||
var dcType = typeof(DataConnection);
|
||||
|
||||
Type? currentType = type;
|
||||
do
|
||||
{
|
||||
if (currentType.FullName == dcType.FullName)
|
||||
return true;
|
||||
currentType = currentType.BaseType;
|
||||
} while (currentType != null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BrowseAppConfig(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model != null)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "Choose application config file",
|
||||
DefaultExt = ".config",
|
||||
FileName = Model.AppConfigPath,
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.AppConfigPath = dialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseConfiguration(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model != null && !string.IsNullOrWhiteSpace(Model.AppConfigPath))
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
|
||||
#if NETCORE
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
if (Model.AppConfigPath!.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var config = AppJsonConfig.Load(Model.AppConfigPath!);
|
||||
if (config.ConnectionStrings.Any())
|
||||
{
|
||||
var result = (string?)Dialogs.PickFromList("Choose Connection String", config.ConnectionStrings.Select(_ => _.Name).ToArray());
|
||||
|
||||
if (result != null)
|
||||
Model.CustomConfiguration = result;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "Config file open error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var config = ConfigurationManager.OpenExeConfiguration(Model.CustomAssemblyPath);
|
||||
|
||||
var configurations = new List<object>();
|
||||
|
||||
for (var i = 0; i < config.ConnectionStrings.ConnectionStrings.Count; i++)
|
||||
{
|
||||
configurations.Add(config.ConnectionStrings.ConnectionStrings[i].Name);
|
||||
}
|
||||
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
|
||||
var result = (string?)Dialogs.PickFromList("Choose Connection String", configurations.ToArray());
|
||||
|
||||
if (result != null)
|
||||
Model.CustomConfiguration = result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "Config file open error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
partial class ConnectionViewModel
|
||||
{
|
||||
public class ProviderInfo
|
||||
{
|
||||
public ProviderInfo(string name, string description)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
}
|
||||
|
||||
public ConnectionViewModel()
|
||||
{
|
||||
_providers = new ObservableCollection<ProviderInfo>(
|
||||
ProviderHelper.DynamicProviders.Select(p => new ProviderInfo(p.Name, p.Description))
|
||||
.OrderBy(s => s.Description.ToLower()));
|
||||
|
||||
_optimizeJoins = true;
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,49 +0,0 @@
|
|||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ output extension=".generated.cs" #>
|
||||
<#@ include file="$(ProjectDir)..\LinqToDB.Templates\T4Model.ttinclude" #>
|
||||
<#@ include file="$(ProjectDir)..\LinqToDB.Templates\NotifyPropertyChanged.ttinclude" #>
|
||||
<#
|
||||
EnableNullableReferenceTypes = true;
|
||||
|
||||
Model.Usings.Add("System.Collections.ObjectModel");
|
||||
Model.Usings.Add("System.Windows");
|
||||
|
||||
Model.Namespace.Name = "LinqToDB.LINQPad";
|
||||
|
||||
Model.Types.Add(
|
||||
new Class("ConnectionViewModel",
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "Name"),
|
||||
new NotifyingProperty(new ModelType("ProviderInfo", true, true), "SelectedProvider", "ProviderPathVisibility", "ProviderPathLabel", "ProviderPath"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "ConnectionString"),
|
||||
new NotifyingProperty("int", "CommandTimeout"),
|
||||
new NotifyingProperty("bool", "Persist"),
|
||||
new NotifyingProperty("bool", "IsProduction"),
|
||||
new NotifyingProperty("bool", "Pluralize"),
|
||||
new NotifyingProperty("bool", "Capitalize"),
|
||||
new NotifyingProperty("bool", "IncludeRoutines"),
|
||||
new NotifyingProperty("bool", "IncludeFKs"),
|
||||
new NotifyingProperty("bool", "EncryptConnectionString"),
|
||||
new NotifyingProperty("bool", "UseCustomFormatter"),
|
||||
new NotifyingProperty("bool", "UseProviderSpecificTypes"),
|
||||
new NotifyingProperty("bool", "OptimizeJoins"),
|
||||
//new NotifyingProperty("bool", "NormalizeNames"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "IncludeSchemas"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "ExcludeSchemas"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "IncludeCatalogs"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "ExcludeCatalogs"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "CustomAssemblyPath"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "CustomTypeName"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "AppConfigPath"),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "CustomConfiguration"),
|
||||
new NotifyingProperty("bool", "IsDynamic", "IsDynamic", "DynamicVisibility", "StaticVisibility"),
|
||||
new NotifyingProperty("Visibility", "DynamicVisibility").InitGetter(() => new [] { "IsDynamic ? Visibility.Visible : Visibility.Collapsed" }),
|
||||
new NotifyingProperty("Visibility", "StaticVisibility").InitGetter(() => new [] { "!IsDynamic ? Visibility.Visible : Visibility.Collapsed" }),
|
||||
new NotifyingProperty("Visibility", "ProviderPathVisibility") { Conditional = "NETCORE" }.InitGetter(() => new [] { "SelectedProvider?.Name == ProviderName.SqlCe || SelectedProvider?.Name == ProviderName.SapHanaNative ? Visibility.Visible : Visibility.Collapsed" }),
|
||||
new NotifyingProperty("Visibility", "ProviderPathVisibility") { Conditional = "!NETCORE" }.InitGetter(() => new [] { "Visibility.Collapsed" }),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "ProviderPathLabel").InitGetter(() => new [] { "SelectedProvider?.Name == ProviderName.SqlCe ? \"Specify path to System.Data.SqlServerCe.dll\" : SelectedProvider?.Name == ProviderName.SapHanaNative ? \"Specify path to Sap.Data.Hana.Core.v2.1.dll\" : null " }),
|
||||
new NotifyingProperty(ModelType.Create<string>(true), "ProviderPath"),
|
||||
new NotifyingProperty("ObservableCollection<ProviderInfo>", "Providers"))
|
||||
);
|
||||
|
||||
GenerateModel();
|
||||
#>
|
|
@ -0,0 +1,82 @@
|
|||
using System.Data.Common;
|
||||
using System.Data.OleDb;
|
||||
using System.Data.Odbc;
|
||||
using System.Data;
|
||||
using System.Runtime.InteropServices;
|
||||
using LinqToDB.DataProvider;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class AccessProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.Access , "OLE DB"),
|
||||
new (ProviderName.AccessOdbc, "ODBC" ),
|
||||
};
|
||||
|
||||
public AccessProvider()
|
||||
: base(ProviderName.Access, "Microsoft Access", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool SupportsSecondaryConnection => true;
|
||||
public override bool AutomaticProviderSelection => true;
|
||||
|
||||
public override string? GetProviderDownloadUrl(string? providerName)
|
||||
{
|
||||
return "https://www.microsoft.com/en-us/download/details.aspx?id=54920";
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && providerName == ProviderName.Access)
|
||||
OleDbConnection.ReleaseObjectPool();
|
||||
|
||||
if (providerName == ProviderName.AccessOdbc)
|
||||
OdbcConnection.ReleaseObjectPool();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
var connectionString = settings.Connection.Provider == ProviderName.Access
|
||||
? settings.Connection.ConnectionString
|
||||
: settings.Connection.SecondaryProvider == ProviderName.Access
|
||||
? settings.Connection.SecondaryConnectionString
|
||||
: null;
|
||||
|
||||
if (connectionString == null || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return null;
|
||||
|
||||
// only OLE DB schema has required information
|
||||
IDataProvider provider;
|
||||
if (settings.Connection.Provider == ProviderName.Access)
|
||||
provider = DatabaseProviders.GetDataProvider(settings);
|
||||
else
|
||||
provider = DatabaseProviders.GetDataProvider(settings.Connection.SecondaryProvider, settings.Connection.SecondaryConnectionString, null);
|
||||
|
||||
using var cn = (OleDbConnection)provider.CreateConnection(connectionString);
|
||||
cn.Open();
|
||||
|
||||
var dt1 = cn.GetSchema("Tables" ).Rows.Cast<DataRow>().Max(static r => (DateTime)r["DATE_MODIFIED"]);
|
||||
var dt2 = cn.GetSchema("Procedures").Rows.Cast<DataRow>().Max(static r => (DateTime)r["DATE_MODIFIED"]);
|
||||
return dt1 > dt2 ? dt1 : dt2;
|
||||
}
|
||||
|
||||
public override ProviderInfo? GetProviderByConnectionString(string connectionString)
|
||||
{
|
||||
var isOleDb = connectionString.IndexOf("Microsoft.Jet.OLEDB", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| connectionString.IndexOf("Microsoft.ACE.OLEDB", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
// we don't check for ODBC provider marker - it will fail on connection test if wrong
|
||||
return _providers[isOleDb ? 0 : 1];
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
if (providerName == ProviderName.AccessOdbc)
|
||||
return OdbcFactory.Instance;
|
||||
|
||||
return OleDbFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System.Data.Common;
|
||||
using ClickHouse.Client.ADO;
|
||||
using LinqToDB.Data;
|
||||
using MySqlConnector;
|
||||
#if !NETFRAMEWORK
|
||||
using Octonica.ClickHouseClient;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class ClickHouseProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.ClickHouseClient , "HTTP(S) Interface (ClickHouse.Client)" ),
|
||||
new (ProviderName.ClickHouseMySql , "MySQL Interface (MySqlConnector)" ),
|
||||
#if !NETFRAMEWORK
|
||||
// octonica provider doesn't support NETFX or NESTANDARD
|
||||
new (ProviderName.ClickHouseOctonica, "Binary (TCP) Interface (Octonica.ClickHouseClient)"),
|
||||
#endif
|
||||
};
|
||||
|
||||
public ClickHouseProvider()
|
||||
: base(ProviderName.ClickHouse, "ClickHouse", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
// octonica provider doesn't implement connection pooling
|
||||
// client provider use http connections pooling
|
||||
if (providerName == ProviderName.ClickHouseMySql)
|
||||
MySqlConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(metadata_modification_time) FROM system.tables WHERE database = database()").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
if (providerName == ProviderName.ClickHouseClient)
|
||||
return new ClickHouseConnectionFactory();
|
||||
#if !NETFRAMEWORK
|
||||
if (providerName == ProviderName.ClickHouseOctonica)
|
||||
return new ClickHouseDbProviderFactory();
|
||||
#endif
|
||||
|
||||
return MySqlConnectorFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using LinqToDB.Data;
|
||||
using System.Data.Common;
|
||||
#if NETFRAMEWORK
|
||||
using IBM.Data.DB2;
|
||||
#else
|
||||
using IBM.Data.Db2;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class DB2Provider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.DB2LUW , "DB2 for Linux, UNIX and Windows (LUW)"),
|
||||
// zOS provider not tested at all as we don't have access to database instance
|
||||
new (ProviderName.DB2zOS , "DB2 for z/OS" ),
|
||||
//new (DB2iSeriesProviderName.DB2, "DB2 for i (iSeries)" ),
|
||||
};
|
||||
|
||||
public DB2Provider()
|
||||
//: base(ProviderName.DB2, "IBM DB2 (LUW, z/OS or iSeries)", _providers)
|
||||
: base(ProviderName.DB2, "IBM DB2 (LUW or z/OS)", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
DB2Connection.ReleaseObjectPool();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
var sql = settings.Connection.Provider switch
|
||||
{
|
||||
ProviderName.DB2LUW => "SELECT MAX(TIME) FROM (SELECT MAX(ALTER_TIME) AS TIME FROM SYSCAT.ROUTINES UNION SELECT MAX(ALTER_TIME) AS TIME FROM SYSCAT.TABLES)",
|
||||
ProviderName.DB2zOS => "SELECT MAX(TIME) FROM (SELECT MAX(ALTEREDTS) AS TIME FROM SYSIBM.SYSROUTINES UNION SELECT MAX(ALTEREDTS) AS TIME FROM SYSIBM.SYSTABLES)",
|
||||
//DB2iSeriesProviderName.DB2 => "SELECT MAX(TIME) FROM (SELECT MAX(LAST_ALTERED) AS TIME FROM QSYS2.SYSROUTINES UNION SELECT MAX(ROUTINE_CREATED) AS TIME FROM QSYS2.SYSROUTINES UNION SELECT MAX(LAST_ALTERED_TIMESTAMP) AS TIME FROM QSYS2.SYSTABLES)",
|
||||
_ => throw new LinqToDBLinqPadException($"Unknown DB2 provider '{settings.Connection.Provider}'")
|
||||
};
|
||||
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>(sql).FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return DB2Factory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System.Data.Common;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal abstract class DatabaseProviderBase : IDatabaseProvider
|
||||
{
|
||||
protected DatabaseProviderBase(string database, string description, IReadOnlyList<ProviderInfo> providers)
|
||||
{
|
||||
Database = database;
|
||||
Description = description;
|
||||
Providers = providers;
|
||||
}
|
||||
|
||||
public string Database { get; }
|
||||
public string Description { get; }
|
||||
public IReadOnlyList<ProviderInfo> Providers { get; }
|
||||
|
||||
public virtual bool SupportsSecondaryConnection { get; }
|
||||
public virtual bool AutomaticProviderSelection { get; }
|
||||
|
||||
public virtual IReadOnlyCollection<Assembly> GetAdditionalReferences (string providerName ) => Array.Empty<Assembly>();
|
||||
public virtual string? GetProviderAssemblyName (string providerName ) => null;
|
||||
public virtual ProviderInfo? GetProviderByConnectionString(string connectionString ) => null;
|
||||
public virtual string? GetProviderDownloadUrl (string? providerName ) => null;
|
||||
public virtual bool IsProviderPathSupported (string providerName ) => false;
|
||||
public virtual void RegisterProviderFactory (string providerName, string providerPath) { }
|
||||
public virtual string? TryGetDefaultPath (string providerName ) => null;
|
||||
#if NETFRAMEWORK
|
||||
public virtual void Unload ( ) { }
|
||||
#endif
|
||||
|
||||
public abstract void ClearAllPools (string providerName );
|
||||
public abstract DateTime? GetLastSchemaUpdate(ConnectionSettings settings);
|
||||
public abstract DbProviderFactory GetProviderFactory (string providerName );
|
||||
|
||||
public virtual IDataProvider GetDataProvider(string providerName, string connectionString)
|
||||
{
|
||||
return DataConnection.GetDataProvider(providerName, connectionString)
|
||||
?? throw new LinqToDBLinqPadException($"Can not activate provider '{providerName}'");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
using System.Data.Common;
|
||||
using LinqToDB.DataProvider;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal static class DatabaseProviders
|
||||
{
|
||||
public static readonly IReadOnlyDictionary<string, IDatabaseProvider> Providers;
|
||||
public static readonly IReadOnlyDictionary<string, IDatabaseProvider> ProvidersByProviderName;
|
||||
|
||||
static DatabaseProviders()
|
||||
{
|
||||
var providers = new Dictionary<string, IDatabaseProvider >();
|
||||
var providersByName = new Dictionary<string, IDatabaseProvider >();
|
||||
Providers = providers;
|
||||
ProvidersByProviderName = providersByName;
|
||||
|
||||
Register(providers, providersByName, new AccessProvider ());
|
||||
Register(providers, providersByName, new FirebirdProvider ());
|
||||
Register(providers, providersByName, new MySqlProvider ());
|
||||
Register(providers, providersByName, new PostgreSQLProvider());
|
||||
Register(providers, providersByName, new SybaseAseProvider ());
|
||||
Register(providers, providersByName, new SQLiteProvider ());
|
||||
Register(providers, providersByName, new SqlCeProvider ());
|
||||
#if !NETFRAMEWORK
|
||||
if (IntPtr.Size == 8)
|
||||
Register(providers, providersByName, new DB2Provider ());
|
||||
#else
|
||||
if (IntPtr.Size == 4)
|
||||
Register(providers, providersByName, new DB2Provider ());
|
||||
#endif
|
||||
Register(providers, providersByName, new InformixProvider ());
|
||||
Register(providers, providersByName, new SapHanaProvider ());
|
||||
Register(providers, providersByName, new OracleProvider ());
|
||||
Register(providers, providersByName, new SqlServerProvider ());
|
||||
Register(providers, providersByName, new ClickHouseProvider());
|
||||
|
||||
static void Register(
|
||||
Dictionary<string, IDatabaseProvider> providers,
|
||||
Dictionary<string, IDatabaseProvider> providersByName,
|
||||
IDatabaseProvider provider)
|
||||
{
|
||||
providers.Add(provider.Database, provider);
|
||||
|
||||
foreach (var info in provider.Providers)
|
||||
providersByName.Add(info.Name, provider);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
foreach (var provider in Providers.Values)
|
||||
provider.Unload();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
// trigger .cctors
|
||||
}
|
||||
|
||||
public static DbConnection CreateConnection (ConnectionSettings settings) => GetDataProvider(settings).CreateConnection(settings.Connection.ConnectionString!);
|
||||
|
||||
public static DbProviderFactory GetProviderFactory(ConnectionSettings settings) => GetProviderByName(settings.Connection.Provider!).GetProviderFactory(settings.Connection.Provider!);
|
||||
|
||||
public static IDataProvider GetDataProvider(ConnectionSettings settings) => GetDataProvider(settings.Connection.Provider, settings.Connection.ConnectionString, settings.Connection.ProviderPath);
|
||||
|
||||
public static IDataProvider GetDataProvider(string? providerName, string? connectionString, string? providerPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerName))
|
||||
throw new LinqToDBLinqPadException("Can not activate provider. Provider is not selected.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
throw new LinqToDBLinqPadException($"Can not activate provider '{providerName}'. Connection string not specified.");
|
||||
|
||||
var databaseProvider = GetProviderByName(providerName!);
|
||||
if (providerPath != null)
|
||||
databaseProvider.RegisterProviderFactory(providerName!, providerPath);
|
||||
|
||||
return databaseProvider.GetDataProvider(providerName!, connectionString!);
|
||||
}
|
||||
|
||||
private static IDatabaseProvider GetProviderByName(string providerName)
|
||||
{
|
||||
if (ProvidersByProviderName.TryGetValue(providerName, out var provider))
|
||||
return provider;
|
||||
|
||||
throw new LinqToDBLinqPadException($"Cannot find database provider '{providerName}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets database provider abstraction by database name.
|
||||
/// </summary>
|
||||
/// <param name="database">Database name (identifier of provider abstraction).</param>
|
||||
public static IDatabaseProvider GetProvider(string? database)
|
||||
{
|
||||
if (database != null && Providers.TryGetValue(database, out var provider))
|
||||
return provider;
|
||||
|
||||
throw new LinqToDBLinqPadException($"Cannot find provider for database '{database}'");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System.Data.Common;
|
||||
using FirebirdSql.Data.FirebirdClient;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class FirebirdProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.Firebird, "Firebird"),
|
||||
};
|
||||
|
||||
public FirebirdProvider()
|
||||
: base(ProviderName.Firebird, "Firebird", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
FbConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
// no information in schema
|
||||
return null;
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return FirebirdClientFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using System.Data.Common;
|
||||
using LinqToDB.DataProvider;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Provides database provider abstraction.
|
||||
/// </summary>
|
||||
internal interface IDatabaseProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets database name (generic <see cref="ProviderName"/> value).
|
||||
/// </summary>
|
||||
string Database { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets database provider name for UI.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When <c>true</c>, database provider supports secondary connection for database schema population.
|
||||
/// </summary>
|
||||
bool SupportsSecondaryConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Release all connections.
|
||||
/// </summary>
|
||||
void ClearAllPools(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// Returns last schema update time.
|
||||
/// </summary>
|
||||
DateTime? GetLastSchemaUpdate(ConnectionSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Returns additional reference assemblies for dynamic model compilation (except main provider assembly).
|
||||
/// </summary>
|
||||
IReadOnlyCollection<Assembly> GetAdditionalReferences(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// List of supported provider names for provider.
|
||||
/// </summary>
|
||||
IReadOnlyList<ProviderInfo> Providers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When <c>true</c>, connection settings UI doesn't allow user to select provider type.
|
||||
/// <see cref="GetProviderByConnectionString(string)"/> method will be used to infer provider automatically.
|
||||
/// Note that provider selection also unavailable when there is only one provider supported by database.
|
||||
/// </summary>
|
||||
bool AutomaticProviderSelection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to infer provider by database connection string.
|
||||
/// </summary>
|
||||
ProviderInfo? GetProviderByConnectionString(string connectionString);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c>, if specified provider for current database provider supports provider assembly path configuration.
|
||||
/// </summary>
|
||||
bool IsProviderPathSupported(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// If provider supports assembly path configuration, method
|
||||
/// returns help text for configuration UI to help user locate and/or install provider.
|
||||
/// </summary>
|
||||
string? GetProviderAssemblyName(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// If provider supports assembly path configuration, method could return URL to provider download page.
|
||||
/// </summary>
|
||||
string? GetProviderDownloadUrl(string? providerName);
|
||||
|
||||
/// <summary>
|
||||
/// If provider supports assembly path configuration (<see cref="IsProviderPathSupported(string)"/>), method tries to return default path to provider assembly,
|
||||
/// but only if assembly exists on specified path.
|
||||
/// </summary>
|
||||
string? TryGetDefaultPath(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// If provider supports assembly path configuration (<see cref="IsProviderPathSupported(string)"/>), method
|
||||
/// performs provider factory registration to allow Linq To DB locate provider assembly.
|
||||
/// </summary>
|
||||
void RegisterProviderFactory(string providerName, string providerPath);
|
||||
|
||||
// Technically, factory is needed for raw SQL queries only for LINQPad 5 as v6+ has code to work without factory. Still it wasn't backported to LINQPad 5 and doesn't hurt to support.
|
||||
// LINQPad currently calls only CreateCommand and CreateDataAdapter methods.
|
||||
/// <summary>
|
||||
/// Returns ADO.NET provider classes factory.
|
||||
/// </summary>
|
||||
DbProviderFactory GetProviderFactory(string providerName);
|
||||
|
||||
/// <summary>
|
||||
/// Returns linq2db data provider.
|
||||
/// </summary>
|
||||
IDataProvider GetDataProvider(string providerName, string connectionString);
|
||||
|
||||
#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
/// Performs clanup on domain unload.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System.Data.Common;
|
||||
#if NETFRAMEWORK
|
||||
using IBM.Data.DB2;
|
||||
#else
|
||||
using IBM.Data.Db2;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class InformixProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.InformixDB2, "Informix")
|
||||
};
|
||||
|
||||
public InformixProvider()
|
||||
: base(ProviderName.Informix, "IBM Informix", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
DB2Connection.ReleaseObjectPool();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
// Informix provides only table creation date without time, which is useless
|
||||
return null;
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return DB2Factory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System.Data.Common;
|
||||
using LinqToDB.Data;
|
||||
using MySqlConnector;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class MySqlProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.MySqlConnector, "MySql/MariaDB"),
|
||||
};
|
||||
|
||||
public MySqlProvider()
|
||||
: base(ProviderName.MySql, "MySql/MariaDB", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
MySqlConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(u.time) FROM (SELECT MAX(UPDATE_TIME) AS time FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() UNION SELECT MAX(CREATE_TIME) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() UNION SELECT MAX(LAST_ALTERED) FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()) as u").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return MySqlConnectorFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Data.Common;
|
||||
using LinqToDB.Data;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class OracleProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.Oracle , "Detect Dialect Automatically", true),
|
||||
new (ProviderName.Oracle11Managed, "Oracle 11g Dialect" ),
|
||||
new (ProviderName.OracleManaged , "Oracle 12c Dialect" ),
|
||||
};
|
||||
|
||||
public OracleProvider()
|
||||
: base(ProviderName.Oracle, "Oracle", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
OracleConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(LAST_DDL_TIME) FROM USER_OBJECTS WHERE OBJECT_TYPE IN ('TABLE', 'VIEW', 'INDEX', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'MATERIALIZED VIEW') AND STATUS = 'VALID'").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return OracleClientFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System.Data.Common;
|
||||
using Npgsql;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class PostgreSQLProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new(ProviderName.PostgreSQL , "Detect Dialect Automatically", true),
|
||||
new(ProviderName.PostgreSQL92, "PostgreSQL 9.2 Dialect" ),
|
||||
new(ProviderName.PostgreSQL93, "PostgreSQL 9.3 Dialect" ),
|
||||
new(ProviderName.PostgreSQL95, "PostgreSQL 9.5 Dialect" ),
|
||||
new(ProviderName.PostgreSQL15, "PostgreSQL 15 Dialect" ),
|
||||
};
|
||||
|
||||
public PostgreSQLProvider()
|
||||
: base(ProviderName.PostgreSQL, "PostgreSQL", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
NpgsqlConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
// no information in schema
|
||||
return null;
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return NpgsqlFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Database provider descriptor for specific database.
|
||||
/// </summary>
|
||||
/// <param name="Name">Provider identifier (e.g. value from <see cref="ProviderName"/> class).</param>
|
||||
/// <param name="DisplayName">Provider display name in settings dialog.</param>
|
||||
/// <param name="IsDefault">When set, specified provider dialect will be selected automatically.</param>
|
||||
internal sealed record ProviderInfo(string Name, string DisplayName, bool IsDefault = false);
|
|
@ -0,0 +1,33 @@
|
|||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class SQLiteProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new(ProviderName.SQLiteClassic, "SQLite")
|
||||
};
|
||||
|
||||
public SQLiteProvider()
|
||||
: base(ProviderName.SQLite, "SQLite", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
SQLiteConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
// no information in schema
|
||||
return null;
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return SQLiteFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
using System.Data.Common;
|
||||
using System.Data.Odbc;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider.SapHana;
|
||||
#if !NETFRAMEWORK
|
||||
using System.IO;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class SapHanaProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new(ProviderName.SapHanaNative, "Native Provider (Sap.Data.Hana)"),
|
||||
new(ProviderName.SapHanaOdbc , "ODBC Provider (HANAODBC/HANAODBC32)"),
|
||||
};
|
||||
|
||||
public SapHanaProvider()
|
||||
: base(ProviderName.SapHana, "SAP HANA", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override string? GetProviderDownloadUrl(string? providerName)
|
||||
{
|
||||
return "https://tools.hana.ondemand.com/#hanatools";
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
if (providerName == ProviderName.SapHanaOdbc)
|
||||
OdbcConnection.ReleaseObjectPool();
|
||||
else if (providerName == ProviderName.SapHanaNative)
|
||||
{
|
||||
var typeName = $"{SapHanaProviderAdapter.ClientNamespace}.HanaConnection, {SapHanaProviderAdapter.AssemblyName}";
|
||||
Type.GetType(typeName, false)?.GetMethod("ClearAllPools", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(time) FROM (SELECT MAX(CREATE_TIME) AS time FROM M_CS_TABLES UNION SELECT MAX(MODIFY_TIME) FROM M_CS_TABLES UNION SELECT MAX(CREATE_TIME) FROM M_RS_TABLES UNION SELECT MAX(CREATE_TIME) FROM PROCEDURES UNION SELECT MAX(CREATE_TIME) FROM FUNCTIONS)").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
if (providerName == ProviderName.SapHanaOdbc)
|
||||
return OdbcFactory.Instance;
|
||||
|
||||
var typeName = $"{SapHanaProviderAdapter.ClientNamespace}.HanaFactory, {SapHanaProviderAdapter.AssemblyName}";
|
||||
return (DbProviderFactory)Type.GetType(typeName, false)?.GetField("Instance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null)!;
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
public override bool IsProviderPathSupported(string providerName)
|
||||
{
|
||||
return providerName == ProviderName.SapHanaNative;
|
||||
}
|
||||
|
||||
public override string? GetProviderAssemblyName(string providerName)
|
||||
{
|
||||
return providerName == ProviderName.SapHanaNative ? "Sap.Data.Hana.Core.v2.1.dll" : null;
|
||||
}
|
||||
|
||||
public override string? TryGetDefaultPath(string providerName)
|
||||
{
|
||||
if (providerName == ProviderName.SapHanaNative)
|
||||
{
|
||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
|
||||
if (!string.IsNullOrEmpty(programFiles))
|
||||
{
|
||||
|
||||
var path = Path.Combine(programFiles, "sap\\hdbclient\\dotnetcore\\v2.1\\Sap.Data.Hana.Core.v2.1.dll");
|
||||
|
||||
if (File.Exists(path))
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool _factoryRegistered;
|
||||
public override void RegisterProviderFactory(string providerName, string providerPath)
|
||||
{
|
||||
if (providerName == ProviderName.SapHanaNative && !_factoryRegistered)
|
||||
{
|
||||
if (!File.Exists(providerPath))
|
||||
throw new LinqToDBLinqPadException($"Cannot find SAP HANA provider assembly at '{providerPath}'");
|
||||
|
||||
try
|
||||
{
|
||||
var sapHanaAssembly = Assembly.LoadFrom(providerPath);
|
||||
DbProviderFactories.RegisterFactory("Sap.Data.Hana", sapHanaAssembly.GetType("Sap.Data.Hana.HanaFactory")!);
|
||||
_factoryRegistered = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new LinqToDBLinqPadException($"Failed to initialize SAP HANA provider factory: ({ex.GetType().Name}) {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System.Data.Common;
|
||||
#if !NETFRAMEWORK
|
||||
using System.IO;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class SqlCeProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.SqlCe, "Microsoft SQL Server Compact Edition")
|
||||
};
|
||||
|
||||
public SqlCeProvider()
|
||||
: base(ProviderName.SqlCe, "Microsoft SQL Server Compact Edition (SQL CE)", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
// no information in schema
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string? GetProviderDownloadUrl(string? providerName)
|
||||
{
|
||||
return "https://www.microsoft.com/en-us/download/details.aspx?id=30709";
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
// connection pooling not supported by provider
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
var typeName = "System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe";
|
||||
return (DbProviderFactory)Type.GetType(typeName, false)?.GetField("Instance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null)!;
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
public override bool IsProviderPathSupported(string providerName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string? GetProviderAssemblyName(string providerName)
|
||||
{
|
||||
return "System.Data.SqlServerCe.dll";
|
||||
}
|
||||
|
||||
public override string? TryGetDefaultPath(string providerName)
|
||||
{
|
||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
if (!string.IsNullOrEmpty(programFiles))
|
||||
{
|
||||
var path = Path.Combine(programFiles, "Microsoft SQL Server Compact Edition\\v4.0\\Private\\System.Data.SqlServerCe.dll");
|
||||
|
||||
if (File.Exists(path))
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool _factoryRegistered;
|
||||
public override void RegisterProviderFactory(string providerName, string providerPath)
|
||||
{
|
||||
if (_factoryRegistered)
|
||||
return;
|
||||
|
||||
if (!File.Exists(providerPath))
|
||||
throw new LinqToDBLinqPadException($"Cannot find SQL CE provider assembly at '{providerPath}'");
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.LoadFrom(providerPath);
|
||||
DbProviderFactories.RegisterFactory("System.Data.SqlServerCe.4.0", assembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory")!);
|
||||
_factoryRegistered = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new LinqToDBLinqPadException($"Failed to initialize SQL CE provider factory: ({ex.GetType().Name}) {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using System.Data.Common;
|
||||
#if NETFRAMEWORK
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider;
|
||||
using LinqToDB.DataProvider.SqlServer;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Types;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class SqlServerProvider : DatabaseProviderBase
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
static SqlServerProvider()
|
||||
{
|
||||
var oldCurrent = Directory.GetCurrentDirectory();
|
||||
var newCurrent = Path.GetDirectoryName(_additionalAssemblies[0].Location);
|
||||
|
||||
if (oldCurrent != newCurrent)
|
||||
Directory.SetCurrentDirectory(newCurrent);
|
||||
|
||||
// This will trigger GLNativeMethods .cctor, which loads native runtime for spatial types from relative path
|
||||
// We need to reset current directory before it otherwise it fails to find runtime dll as LINQPad 5 default directory is LINQPad directory, not driver's dir
|
||||
var type = _additionalAssemblies[0].GetType("Microsoft.SqlServer.Types.GLNativeMethods");
|
||||
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
|
||||
|
||||
if (oldCurrent != newCurrent)
|
||||
Directory.SetCurrentDirectory(oldCurrent);
|
||||
}
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
private static extern bool FreeLibrary(IntPtr hModule);
|
||||
|
||||
public static void UnloadModule(string moduleName)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Unload()
|
||||
{
|
||||
// must unload unmanaged dlls to allow LINQPad 5 to delete old driver instance without error
|
||||
// as SQL Server types lib doesn't implement cleanup for unloadable domains...
|
||||
foreach (ProcessModule mod in Process.GetCurrentProcess().Modules)
|
||||
if (string.Equals(mod.ModuleName, "SqlServerSpatial160.dll", StringComparison.OrdinalIgnoreCase))
|
||||
FreeLibrary(mod.BaseAddress);
|
||||
}
|
||||
#endif
|
||||
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new (ProviderName.SqlServer , "Detect Dialect Automatically", true),
|
||||
new (ProviderName.SqlServer2005, "SQL Server 2005 Dialect" ),
|
||||
new (ProviderName.SqlServer2008, "SQL Server 2008 Dialect" ),
|
||||
new (ProviderName.SqlServer2012, "SQL Server 2012 Dialect" ),
|
||||
new (ProviderName.SqlServer2014, "SQL Server 2014 Dialect" ),
|
||||
new (ProviderName.SqlServer2016, "SQL Server 2016 Dialect" ),
|
||||
new (ProviderName.SqlServer2017, "SQL Server 2017 Dialect" ),
|
||||
new (ProviderName.SqlServer2019, "SQL Server 2019 Dialect" ),
|
||||
new (ProviderName.SqlServer2022, "SQL Server 2022 Dialect" ),
|
||||
};
|
||||
|
||||
public SqlServerProvider()
|
||||
: base(ProviderName.SqlServer, "Microsoft SQL Server", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<Assembly> _additionalAssemblies = new[] { typeof(SqlHierarchyId).Assembly };
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
SqlConnection.ClearAllPools();
|
||||
}
|
||||
|
||||
public override IReadOnlyCollection<Assembly> GetAdditionalReferences(string providerName)
|
||||
{
|
||||
return _additionalAssemblies;
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(modify_date) FROM sys.objects").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return SqlClientFactory.Instance;
|
||||
}
|
||||
|
||||
|
||||
public override IDataProvider GetDataProvider(string providerName, string connectionString)
|
||||
{
|
||||
// provider detector fails to detect Microsoft.Data.SqlClient
|
||||
// kinda regression in linq2db v5
|
||||
return providerName switch
|
||||
{
|
||||
ProviderName.SqlServer2005 => SqlServerTools.GetDataProvider(SqlServerVersion.v2005 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2008 => SqlServerTools.GetDataProvider(SqlServerVersion.v2008 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2012 => SqlServerTools.GetDataProvider(SqlServerVersion.v2012 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2014 => SqlServerTools.GetDataProvider(SqlServerVersion.v2014 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2016 => SqlServerTools.GetDataProvider(SqlServerVersion.v2016 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2017 => SqlServerTools.GetDataProvider(SqlServerVersion.v2017 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2019 => SqlServerTools.GetDataProvider(SqlServerVersion.v2019 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer2022 => SqlServerTools.GetDataProvider(SqlServerVersion.v2022 , DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
ProviderName.SqlServer => SqlServerTools.GetDataProvider(SqlServerVersion.AutoDetect, DataProvider.SqlServer.SqlServerProvider.MicrosoftDataSqlClient, connectionString),
|
||||
_ => base.GetDataProvider(providerName, connectionString)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System.Data.Common;
|
||||
using AdoNetCore.AseClient;
|
||||
using LinqToDB.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal sealed class SybaseAseProvider : DatabaseProviderBase
|
||||
{
|
||||
private static readonly IReadOnlyList<ProviderInfo> _providers = new ProviderInfo[]
|
||||
{
|
||||
new(ProviderName.SybaseManaged, "SAP/Sybase ASE")
|
||||
};
|
||||
|
||||
public SybaseAseProvider()
|
||||
: base(ProviderName.Sybase, "SAP/Sybase ASE", _providers)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClearAllPools(string providerName)
|
||||
{
|
||||
AseConnection.ClearPools();
|
||||
}
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(ConnectionSettings settings)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(settings);
|
||||
return db.Query<DateTime?>("SELECT MAX(crdate) FROM sysobjects").FirstOrDefault();
|
||||
}
|
||||
|
||||
public override DbProviderFactory GetProviderFactory(string providerName)
|
||||
{
|
||||
return AseClientFactory.Instance;
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CodeJam.Strings;
|
||||
using CodeJam.Xml;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
|
||||
#if !NETCORE
|
||||
using System.Buffers;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
static class DriverHelper
|
||||
{
|
||||
public const string Author = "Linq To DB Team";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
#if !NETCORE
|
||||
ConfigureRedirects();
|
||||
SapHanaSPS04Fixes();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string GetConnectionDescription(IConnectionInfo cxInfo)
|
||||
{
|
||||
var providerName = (string?)cxInfo.DriverData.Element(CX.ProviderName);
|
||||
var dbInfo = cxInfo.DatabaseInfo;
|
||||
|
||||
return $"[{providerName}] {dbInfo.Server}\\{dbInfo.Database} (v.{dbInfo.DbVersion})";
|
||||
}
|
||||
|
||||
public static void ClearConnectionPools(IConnectionInfo cxInfo)
|
||||
{
|
||||
using var db = new LINQPadDataConnection(cxInfo);
|
||||
if (db.Connection is SqlConnection connection)
|
||||
SqlConnection.ClearPool(connection);
|
||||
}
|
||||
|
||||
#region ShowConnectionDialog
|
||||
|
||||
public static bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection, bool isDynamic)
|
||||
{
|
||||
var model = new ConnectionViewModel();
|
||||
var providerName = isNewConnection
|
||||
? ProviderName.SqlServer
|
||||
: (string?)cxInfo.DriverData.Element(CX.ProviderName);
|
||||
|
||||
if (providerName != null)
|
||||
model.SelectedProvider = model.Providers.FirstOrDefault(p => p.Name == providerName);
|
||||
|
||||
model.Name = cxInfo.DisplayName;
|
||||
model.IsDynamic = isDynamic;
|
||||
model.CustomAssemblyPath = cxInfo.CustomTypeInfo.CustomAssemblyPath;
|
||||
model.CustomTypeName = cxInfo.CustomTypeInfo.CustomTypeName;
|
||||
model.AppConfigPath = cxInfo.AppConfigPath;
|
||||
model.CustomConfiguration = cxInfo.DriverData.Element(CX.CustomConfiguration)?.Value;
|
||||
model.Persist = cxInfo.Persist;
|
||||
model.IsProduction = cxInfo.IsProduction;
|
||||
model.EncryptConnectionString = cxInfo.DatabaseInfo.EncryptCustomCxString;
|
||||
model.Pluralize = !cxInfo.DynamicSchemaOptions.NoPluralization;
|
||||
model.Capitalize = !cxInfo.DynamicSchemaOptions.NoCapitalization;
|
||||
model.IncludeRoutines = cxInfo.DriverData.Element(CX.ExcludeRoutines)?.Value.ToLower() == "false";
|
||||
model.IncludeFKs = cxInfo.DriverData.Element(CX.ExcludeFKs)?.Value.ToLower() != "true";
|
||||
model.ConnectionString = cxInfo.DatabaseInfo.CustomCxString.IsNullOrWhiteSpace() ? (string?)cxInfo.DriverData.Element(CX.ConnectionString) : cxInfo.DatabaseInfo.CustomCxString;
|
||||
model.IncludeSchemas = cxInfo.DriverData.Element(CX.IncludeSchemas) ?.Value;
|
||||
model.ExcludeSchemas = cxInfo.DriverData.Element(CX.ExcludeSchemas) ?.Value;
|
||||
model.IncludeCatalogs = cxInfo.DriverData.Element(CX.IncludeCatalogs) ?.Value;
|
||||
model.ExcludeCatalogs = cxInfo.DriverData.Element(CX.ExcludeCatalogs) ?.Value;
|
||||
//model.NormalizeNames = cxInfo.DriverData.Element(CX.NormalizeNames) ?.Value.ToLower() == "true";
|
||||
model.UseProviderSpecificTypes = cxInfo.DriverData.Element(CX.UseProviderSpecificTypes)?.Value.ToLower() == "true";
|
||||
model.UseCustomFormatter = cxInfo.DriverData.Element(CX.UseCustomFormatter) ?.Value.ToLower() == "true";
|
||||
model.CommandTimeout = cxInfo.DriverData.ElementValueOrDefault(CX.CommandTimeout, str => str.ToInt32() ?? 0, 0);
|
||||
|
||||
model.OptimizeJoins = cxInfo.DriverData.Element(CX.OptimizeJoins) == null || cxInfo.DriverData.Element(CX.OptimizeJoins)?.Value.ToLower() == "true";
|
||||
model.ProviderPath = (string?)cxInfo.DriverData.Element(CX.ProviderPath);
|
||||
|
||||
if (ConnectionDialog.Show(model, isDynamic ? TestConnection : null))
|
||||
{
|
||||
providerName = model.SelectedProvider?.Name;
|
||||
|
||||
cxInfo.DriverData.SetElementValue(CX.ProviderName, providerName);
|
||||
cxInfo.DriverData.SetElementValue(CX.ProviderPath, model.ProviderPath);
|
||||
cxInfo.DriverData.SetElementValue(CX.ConnectionString, null);
|
||||
cxInfo.DriverData.SetElementValue(CX.ExcludeRoutines, !model.IncludeRoutines ? "true" : "false");
|
||||
cxInfo.DriverData.SetElementValue(CX.ExcludeFKs, !model.IncludeFKs ? "true" : "false");
|
||||
cxInfo.DriverData.SetElementValue(CX.IncludeSchemas, model.IncludeSchemas. IsNullOrWhiteSpace() ? null : model.IncludeSchemas);
|
||||
cxInfo.DriverData.SetElementValue(CX.ExcludeSchemas, model.ExcludeSchemas. IsNullOrWhiteSpace() ? null : model.ExcludeSchemas);
|
||||
cxInfo.DriverData.SetElementValue(CX.IncludeCatalogs, model.IncludeCatalogs.IsNullOrWhiteSpace() ? null : model.IncludeSchemas);
|
||||
cxInfo.DriverData.SetElementValue(CX.ExcludeCatalogs, model.ExcludeCatalogs.IsNullOrWhiteSpace() ? null : model.ExcludeSchemas);
|
||||
cxInfo.DriverData.SetElementValue(CX.OptimizeJoins, model.OptimizeJoins ? "true" : "false");
|
||||
//cxInfo.DriverData.SetElementValue(CX.NormalizeNames, model.NormalizeNames ? "true" : null);
|
||||
cxInfo.DriverData.SetElementValue(CX.UseProviderSpecificTypes, model.UseProviderSpecificTypes ? "true" : null);
|
||||
cxInfo.DriverData.SetElementValue(CX.UseCustomFormatter, model.UseCustomFormatter ? "true" : null);
|
||||
cxInfo.DriverData.SetElementValue(CX.CommandTimeout, model.CommandTimeout.ToString());
|
||||
|
||||
try
|
||||
{
|
||||
if (model.ConnectionString != null)
|
||||
{
|
||||
var providerInfo = ProviderHelper.GetProvider(providerName, model.ProviderPath);
|
||||
cxInfo.DatabaseInfo.Provider = providerInfo.GetConnectionNamespace();
|
||||
var provider = providerInfo.GetDataProvider(model.ConnectionString);
|
||||
|
||||
using var db = new DataConnection(provider, model.ConnectionString)
|
||||
{
|
||||
CommandTimeout = model.CommandTimeout
|
||||
};
|
||||
|
||||
cxInfo.DatabaseInfo.Provider = db.Connection.GetType().Namespace;
|
||||
cxInfo.DatabaseInfo.Server = db.Connection.DataSource;
|
||||
cxInfo.DatabaseInfo.Database = db.Connection.Database;
|
||||
cxInfo.DatabaseInfo.DbVersion = db.Connection.ServerVersion;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
cxInfo.DriverData.SetElementValue(CX.CustomConfiguration, model.CustomConfiguration.IsNullOrWhiteSpace() ? null : model.CustomConfiguration);
|
||||
|
||||
cxInfo.CustomTypeInfo.CustomAssemblyPath = model.CustomAssemblyPath;
|
||||
cxInfo.CustomTypeInfo.CustomTypeName = model.CustomTypeName;
|
||||
cxInfo.AppConfigPath = model.AppConfigPath;
|
||||
cxInfo.DatabaseInfo.CustomCxString = model.ConnectionString;
|
||||
cxInfo.DatabaseInfo.EncryptCustomCxString = model.EncryptConnectionString;
|
||||
cxInfo.DynamicSchemaOptions.NoPluralization = !model.Pluralize;
|
||||
cxInfo.DynamicSchemaOptions.NoCapitalization = !model.Capitalize;
|
||||
cxInfo.DynamicSchemaOptions.ExcludeRoutines = !model.IncludeRoutines;
|
||||
cxInfo.Persist = model.Persist;
|
||||
cxInfo.IsProduction = model.IsProduction;
|
||||
cxInfo.DisplayName = model.Name.IsNullOrWhiteSpace() ? null : model.Name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Exception? TestConnection(ConnectionViewModel? model)
|
||||
{
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (model.SelectedProvider != null && model.ConnectionString != null)
|
||||
{
|
||||
var provider = ProviderHelper.GetProvider(model.SelectedProvider.Name, model.ProviderPath).GetDataProvider(model.ConnectionString);
|
||||
|
||||
using var con = provider.CreateConnection(model.ConnectionString);
|
||||
con.Open();
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static Action<TraceInfo> GetOnTraceConnection(QueryExecutionManager executionManager)
|
||||
{
|
||||
return info =>
|
||||
{
|
||||
if (info.TraceInfoStep == TraceInfoStep.BeforeExecute)
|
||||
{
|
||||
executionManager.SqlTranslationWriter.WriteLine(info.SqlText);
|
||||
}
|
||||
else if (info.TraceLevel == TraceLevel.Error)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var ex = info.Exception; ex != null; ex = ex.InnerException)
|
||||
{
|
||||
sb
|
||||
.AppendLine()
|
||||
.AppendLine("/*")
|
||||
.AppendLine($"Exception: {ex.GetType()}")
|
||||
.AppendLine($"Message : {ex.Message}")
|
||||
.AppendLine(ex.StackTrace)
|
||||
.AppendLine("*/")
|
||||
;
|
||||
}
|
||||
|
||||
executionManager.SqlTranslationWriter.WriteLine(sb.ToString());
|
||||
}
|
||||
else if (info.RecordsAffected != null)
|
||||
{
|
||||
executionManager.SqlTranslationWriter.WriteLine($"-- Execution time: {info.ExecutionTime}. Records affected: {info.RecordsAffected}.\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionManager.SqlTranslationWriter.WriteLine($"-- Execution time: {info.ExecutionTime}\r\n");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#if !NETCORE
|
||||
static void ConfigureRedirects()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
|
||||
{
|
||||
var requestedAssembly = new AssemblyName(args.Name!);
|
||||
if (requestedAssembly.Name == "linq2db")
|
||||
return typeof(DataContext).Assembly;
|
||||
|
||||
// manage netstandard dll hell
|
||||
if (requestedAssembly.Name == "System.Threading.Tasks.Extensions")
|
||||
return typeof(ValueTask).Assembly;
|
||||
if (requestedAssembly.Name == "System.Runtime.CompilerServices.Unsafe")
|
||||
return typeof(Unsafe).Assembly;
|
||||
if (requestedAssembly.Name == "System.Memory")
|
||||
return typeof(Span<>).Assembly;
|
||||
if (requestedAssembly.Name == "System.Buffers")
|
||||
return typeof(ArrayPool<>).Assembly;
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
static void SapHanaSPS04Fixes()
|
||||
{
|
||||
// recent SAP HANA provider (SPS04 040, fixed in 045) uses Assembly.GetEntryAssembly() calls during native dlls discovery, which
|
||||
// leads to NRE as it returns null under NETFX, so we need to fake this method result to unblock HANA testing
|
||||
// https://github.com/microsoft/vstest/issues/1834
|
||||
// https://dejanstojanovic.net/aspnet/2015/january/set-entry-assembly-in-unit-testing-methods/
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.GetCallingAssembly();
|
||||
|
||||
var manager = new AppDomainManager();
|
||||
var entryAssemblyfield = manager.GetType().GetField("m_entryAssembly", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
entryAssemblyfield.SetValue(manager, assembly);
|
||||
|
||||
var domain = AppDomain.CurrentDomain;
|
||||
var domainManagerField = domain.GetType().GetField("_domainManager", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
domainManagerField.SetValue(domain, manager);
|
||||
}
|
||||
catch { /* ne shmagla */ }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
using System.IO;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.LINQPad.UI;
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
#if NETFRAMEWORK
|
||||
using System.Text.Json;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Metadata;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Contains shared driver code for dynamic (scaffolded) and static (precompiled) drivers.
|
||||
/// </summary>
|
||||
internal static class DriverHelper
|
||||
{
|
||||
public const string Name = "Linq To DB";
|
||||
public const string Author = "Linq To DB Team";
|
||||
|
||||
/// <summary>
|
||||
/// Returned by <see cref="DataContextDriver.GetNamespacesToAdd(IConnectionInfo)"/> method implementation.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyCollection<string> DefaultImports = new[]
|
||||
{
|
||||
"LinqToDB",
|
||||
"LinqToDB.Data",
|
||||
"LinqToDB.Mapping"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initialization method, called from driver's static constructor.
|
||||
/// </summary>
|
||||
public static void Init()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
// Dynamically resolve assembly bindings to currently used assembly version for transitive dependencies. Used by.NET Framework build (LINQPad 5).
|
||||
AppDomain.CurrentDomain.AssemblyResolve += static (sender, args) =>
|
||||
{
|
||||
var requestedAssembly = new AssemblyName(args.Name!);
|
||||
|
||||
if (requestedAssembly.Name == "linq2db")
|
||||
return typeof(DataContext).Assembly;
|
||||
|
||||
// manage transitive dependencies dll hell
|
||||
if (requestedAssembly.Name == "System.Threading.Tasks.Extensions")
|
||||
return typeof(ValueTask).Assembly;
|
||||
if (requestedAssembly.Name == "System.Runtime.CompilerServices.Unsafe")
|
||||
return typeof(Unsafe).Assembly;
|
||||
if (requestedAssembly.Name == "System.Memory")
|
||||
return typeof(Span<>).Assembly;
|
||||
if (requestedAssembly.Name == "System.Buffers")
|
||||
return typeof(ArrayPool<>).Assembly;
|
||||
if (requestedAssembly.Name == "System.Collections.Immutable")
|
||||
return typeof(ImmutableArray).Assembly;
|
||||
if (requestedAssembly.Name == "System.Diagnostics.DiagnosticSource")
|
||||
return typeof(DiagnosticSource).Assembly;
|
||||
if (requestedAssembly.Name == "System.Reflection.Metadata")
|
||||
return typeof(Blob).Assembly;
|
||||
if (requestedAssembly.Name == "System.Text.Json")
|
||||
return typeof(JsonDocument).Assembly;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
AppDomain.CurrentDomain.DomainUnload += static (_, _) => DatabaseProviders.Unload();
|
||||
#endif
|
||||
|
||||
DatabaseProviders.Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="DataContextDriver.InitializeContext(IConnectionInfo, object, QueryExecutionManager)"/> method.
|
||||
/// </summary>
|
||||
public static MappingSchema InitializeContext(IConnectionInfo cxInfo, IDataContext context, QueryExecutionManager executionManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
// apply context-specific Linq To DB options
|
||||
Common.Configuration.Linq.OptimizeJoins = settings.LinqToDB.OptimizeJoins;
|
||||
|
||||
if (context is DataConnection dc)
|
||||
{
|
||||
dc.OnTraceConnection = GetSqlLogAction(executionManager);
|
||||
DataConnection.TurnTraceSwitchOn();
|
||||
}
|
||||
else if (context is DataContext dctx)
|
||||
{
|
||||
dctx.OnTraceConnection = GetSqlLogAction(executionManager);
|
||||
DataConnection.TurnTraceSwitchOn();
|
||||
}
|
||||
|
||||
return context.MappingSchema;
|
||||
|
||||
// Implements Linq To DB connection logging handler to feed SQL logs to LINQPad.
|
||||
static Action<TraceInfo> GetSqlLogAction(QueryExecutionManager executionManager)
|
||||
{
|
||||
return info =>
|
||||
{
|
||||
switch (info.TraceInfoStep)
|
||||
{
|
||||
case TraceInfoStep.BeforeExecute:
|
||||
// log SQL query
|
||||
executionManager.SqlTranslationWriter.WriteLine(info.SqlText);
|
||||
break;
|
||||
case TraceInfoStep.Error:
|
||||
// log error
|
||||
if (info.Exception != null)
|
||||
{
|
||||
for (var ex = info.Exception; ex != null; ex = ex.InnerException)
|
||||
{
|
||||
executionManager.SqlTranslationWriter.WriteLine();
|
||||
executionManager.SqlTranslationWriter.WriteLine("/*");
|
||||
executionManager.SqlTranslationWriter.WriteLine($"Exception: {ex.GetType()}");
|
||||
executionManager.SqlTranslationWriter.WriteLine($"Message : {ex.Message}");
|
||||
executionManager.SqlTranslationWriter.WriteLine(ex.StackTrace);
|
||||
executionManager.SqlTranslationWriter.WriteLine("*/");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TraceInfoStep.Completed:
|
||||
// log data reader execution stats
|
||||
executionManager.SqlTranslationWriter.WriteLine($"-- Data read time: {info.ExecutionTime}. Records fetched: {info.RecordsAffected}.\r\n");
|
||||
break;
|
||||
case TraceInfoStep.AfterExecute:
|
||||
// log query execution stats
|
||||
if (info.RecordsAffected != null)
|
||||
executionManager.SqlTranslationWriter.WriteLine($"-- Execution time: {info.ExecutionTime}. Records affected: {info.RecordsAffected}.\r\n");
|
||||
else
|
||||
executionManager.SqlTranslationWriter.WriteLine($"-- Execution time: {info.ExecutionTime}\r\n");
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex, nameof(InitializeContext));
|
||||
return MappingSchema.Default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="DataContextDriver.GetConnectionDescription(IConnectionInfo)"/> method.
|
||||
/// </summary>
|
||||
public static string GetConnectionDescription(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
// this is default connection name string in connecion explorer when user doesn't specify own name
|
||||
return $"[Linq To DB: {settings.Connection.Provider}] {settings.Connection.Server}\\{settings.Connection.DatabaseName} (v.{settings.Connection.DbVersion})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex, nameof(GetConnectionDescription));
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="DataContextDriver.ClearConnectionPools(IConnectionInfo)"/> method.
|
||||
/// </summary>
|
||||
public static void ClearConnectionPools(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
DatabaseProviders.GetProvider(settings.Connection.Database).ClearAllPools(settings.Connection.Provider!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex, nameof(ClearConnectionPools));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShowConnectionDialog(IConnectionInfo cxInfo, ConnectionDialogOptions dialogOptions, bool isDynamic)
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
var model = new SettingsModel(settings, !isDynamic);
|
||||
|
||||
if (SettingsDialog.Show(
|
||||
model,
|
||||
isDynamic ? TestDynamicConnection : TestStaticConnection,
|
||||
isDynamic ? "Connection to database failed." : "Invalid configuration."))
|
||||
{
|
||||
model.Save();
|
||||
settings.Save(cxInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
static Exception? TestStaticConnection(SettingsModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
// basic checks
|
||||
if (model.StaticConnection.ContextAssemblyPath == null)
|
||||
throw new LinqToDBLinqPadException("Data context assembly not specified");
|
||||
|
||||
if (model.StaticConnection.ContextTypeName == null)
|
||||
throw new LinqToDBLinqPadException("Data context class not specified");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
static Exception? TestDynamicConnection(SettingsModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: add secondary connection test
|
||||
if (model.DynamicConnection.Database == null)
|
||||
throw new LinqToDBLinqPadException("Database is not selected");
|
||||
|
||||
if (model.DynamicConnection.Provider == null)
|
||||
throw new LinqToDBLinqPadException("Database provider is not selected");
|
||||
|
||||
if (model.DynamicConnection.ConnectionString == null)
|
||||
throw new LinqToDBLinqPadException("Connection string is not specified");
|
||||
|
||||
if (model.DynamicConnection.SecondaryProvider != null
|
||||
&& model.DynamicConnection.Provider.Name == model.DynamicConnection.SecondaryProvider.Name)
|
||||
throw new LinqToDBLinqPadException("Secondary connection shouldn't use same provider type as primary connection");
|
||||
|
||||
if (model.DynamicConnection.Database.IsProviderPathSupported(model.DynamicConnection.Provider.Name))
|
||||
{
|
||||
if (model.DynamicConnection.ProviderPath == null)
|
||||
throw new LinqToDBLinqPadException("Provider path is not specified");
|
||||
if (!File.Exists(model.DynamicConnection.ProviderPath))
|
||||
throw new LinqToDBLinqPadException($"Cannot access provider assembly at {model.DynamicConnection.ProviderPath}");
|
||||
}
|
||||
|
||||
var provider = DatabaseProviders.GetDataProvider(model.DynamicConnection.Provider.Name, model.DynamicConnection.ConnectionString, model.DynamicConnection.ProviderPath);
|
||||
|
||||
using (var con = provider.CreateConnection(model.DynamicConnection.ConnectionString))
|
||||
con.Open();
|
||||
|
||||
if (model.DynamicConnection.Database.SupportsSecondaryConnection
|
||||
&& model.DynamicConnection.SecondaryProvider != null
|
||||
&& model.DynamicConnection.SecondaryConnectionString != null)
|
||||
{
|
||||
var secondaryProvider = DatabaseProviders.GetDataProvider(model.DynamicConnection.SecondaryProvider.Name, model.DynamicConnection.SecondaryConnectionString, null);
|
||||
using var con = secondaryProvider.CreateConnection(model.DynamicConnection.SecondaryConnectionString);
|
||||
con.Open();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intercepts exceptions from driver to linqpad
|
||||
public static void HandleException(Exception ex, string method)
|
||||
{
|
||||
Notification.Error($"Unhandled error in method '{method}': {ex.Message}\r\n{ex.StackTrace}", "Linq To DB Driver Error");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetAssembliesToAdd(IConnectionInfo cxInfo)
|
||||
{
|
||||
#if !NETFRAMEWORK
|
||||
yield return "*";
|
||||
#endif
|
||||
yield return typeof(DataConnection).Assembly.Location;
|
||||
yield return typeof(LINQPadDataConnection).Assembly.Location;
|
||||
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
Type cnType;
|
||||
IDatabaseProvider provider;
|
||||
try
|
||||
{
|
||||
provider = DatabaseProviders.GetProvider(settings.Connection.Database);
|
||||
using var cn = DatabaseProviders.CreateConnection(ConnectionSettings.Load(cxInfo));
|
||||
cnType = cn.GetType();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex, nameof(GetAssembliesToAdd));
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var assembly in provider.GetAdditionalReferences(settings.Connection.Provider!))
|
||||
yield return assembly.FullName!;
|
||||
|
||||
yield return cnType.Assembly.Location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.Mapping;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
#if NETFRAMEWORK
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Numerics;
|
||||
#else
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
// IMPORTANT:
|
||||
// 1. driver must be public or it will be missing from create connection dialog (existing connections will work)
|
||||
// 2. don't rename class or namespace as it is used by LINQPad as driver identifier. If renamed, old connections will disappear from UI
|
||||
/// <summary>
|
||||
/// Implements LINQPad driver for synamic (scaffolded from DB schema) model.
|
||||
/// </summary>
|
||||
public sealed class LinqToDBDriver : DynamicDataContextDriver
|
||||
{
|
||||
private MappingSchema? _mappingSchema;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Name => DriverHelper.Name;
|
||||
/// <inheritdoc/>
|
||||
public override string Author => DriverHelper.Author;
|
||||
|
||||
static LinqToDBDriver() => DriverHelper.Init();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetConnectionDescription(IConnectionInfo cxInfo) => DriverHelper.GetConnectionDescription(cxInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DateTime? GetLastSchemaUpdate(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
return DatabaseProviders.GetProvider(settings.Connection.Database).GetLastSchemaUpdate(settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetLastSchemaUpdate));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShowConnectionDialog(IConnectionInfo cxInfo, ConnectionDialogOptions dialogOptions) => DriverHelper.ShowConnectionDialog(cxInfo, dialogOptions, true);
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
// TODO: switch to generator
|
||||
private static readonly Regex _runtimeTokenExtractor = new (@"^.+\\(?<token>[^\\]+)\\[^\\]+$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
private static IEnumerable<string> GetFallbackTokens(string forToken)
|
||||
{
|
||||
switch (forToken)
|
||||
{
|
||||
case "net8.0":
|
||||
yield return "net8.0";
|
||||
goto case "net7.0";
|
||||
case "net7.0":
|
||||
yield return "net7.0";
|
||||
goto case "net6.0";
|
||||
case "net6.0":
|
||||
yield return "net6.0";
|
||||
goto case "net5.0";
|
||||
case "net5.0":
|
||||
yield return "net5.0";
|
||||
goto case "netcoreapp3.1";
|
||||
case "netcoreapp3.1":
|
||||
yield return "netcoreapp3.1";
|
||||
goto case "netstandard2.1";
|
||||
case "netstandard2.1":
|
||||
yield return "netstandard2.1";
|
||||
yield return "netstandard2.0";
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return forToken;
|
||||
}
|
||||
|
||||
private MetadataReference MakeReferenceByRuntime(string runtimeToken, string reference)
|
||||
{
|
||||
var token = _runtimeTokenExtractor.Match(reference).Groups["token"].Value;
|
||||
|
||||
foreach (var fallback in GetFallbackTokens(runtimeToken))
|
||||
{
|
||||
if (token == fallback)
|
||||
return MetadataReference.CreateFromFile(reference);
|
||||
|
||||
var newReference = reference.Replace($"\\{token}\\", $"\\{fallback}\\");
|
||||
|
||||
if (File.Exists(newReference))
|
||||
return MetadataReference.CreateFromFile(newReference);
|
||||
}
|
||||
|
||||
return MetadataReference.CreateFromFile(reference);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<ExplorerItem> GetSchemaAndBuildAssembly(IConnectionInfo cxInfo, AssemblyName assemblyToBuild, ref string? nameSpace, ref string typeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
var (items, text, providerAssemblyLocation) = DynamicSchemaGenerator.GetModel(settings, ref nameSpace, ref typeName);
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(text);
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
// TODO: find better way to do it
|
||||
// hack to overwrite provider assembly references that target wrong runtime
|
||||
// e.g. referenceAssemblies contains path to net5 MySqlConnector
|
||||
// but GetCoreFxReferenceAssemblies returns netcoreapp3.1 runtime references
|
||||
var coreAssemblies = GetCoreFxReferenceAssemblies(cxInfo);
|
||||
var runtimeToken = _runtimeTokenExtractor.Match(coreAssemblies[0]).Groups["token"].Value;
|
||||
#endif
|
||||
var references = new List<MetadataReference>()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
MetadataReference.CreateFromFile(typeof(object). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(Enumerable). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(IDbConnection). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(PhysicalAddress) .Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(BigInteger) .Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(IAsyncDisposable) .Assembly.Location),
|
||||
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(string).Assembly.Location), "netstandard.dll")),
|
||||
#endif
|
||||
MetadataReference.CreateFromFile(typeof(DataConnection). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(LINQPadDataConnection).Assembly.Location),
|
||||
};
|
||||
|
||||
foreach (var assembly in DatabaseProviders.GetProvider(settings.Connection.Database).GetAdditionalReferences(settings.Connection.Provider!))
|
||||
references.Add(MetadataReference.CreateFromFile(assembly.Location));
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
references.Add(MakeReferenceByRuntime(runtimeToken, providerAssemblyLocation));
|
||||
references.AddRange(coreAssemblies.Select(static path => MetadataReference.CreateFromFile(path)));
|
||||
#else
|
||||
references.Add(MetadataReference.CreateFromFile(providerAssemblyLocation));
|
||||
#endif
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
assemblyToBuild.Name!,
|
||||
syntaxTrees : new[] { syntaxTree },
|
||||
references : references,
|
||||
options : new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
using (var stream = new FileStream(assemblyToBuild.CodeBase!, FileMode.Create))
|
||||
{
|
||||
var result = compilation.Emit(stream);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
var failures = result.Diagnostics.Where(static diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
|
||||
|
||||
foreach (var diagnostic in failures)
|
||||
throw new LinqToDBLinqPadException(diagnostic.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error($"{ex}\n{ex.StackTrace}", "Schema Build Error");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ParameterDescriptor[] _contextParameters = new[]
|
||||
{
|
||||
new ParameterDescriptor("provider", typeof(string).FullName),
|
||||
new ParameterDescriptor("providerPath", typeof(string).FullName),
|
||||
new ParameterDescriptor("connectionString", typeof(string).FullName),
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo) => _contextParameters;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object?[] GetContextConstructorArguments(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
return new object?[]
|
||||
{
|
||||
settings.Connection.Provider,
|
||||
settings.Connection.ProviderPath,
|
||||
settings.Connection.ConnectionString
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetContextConstructorArguments));
|
||||
return new object[3];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<string> GetAssembliesToAdd(IConnectionInfo cxInfo) => DriverHelper.GetAssembliesToAdd(cxInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<string> GetNamespacesToAdd(IConnectionInfo cxInfo) => DriverHelper.DefaultImports;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearConnectionPools(IConnectionInfo cxInfo) => DriverHelper.ClearConnectionPools(cxInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
|
||||
{
|
||||
_mappingSchema = DriverHelper.InitializeContext(cxInfo, (DataConnection)context, executionManager);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager, object[] constructorArguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
((DataConnection)context).Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(TearDownContext));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IDbConnection GetIDbConnection(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatabaseProviders.CreateConnection(ConnectionSettings.Load(cxInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetIDbConnection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PreprocessObjectToWrite(ref object objectToWrite, ObjectGraphInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
objectToWrite = ValueFormatter.Format(objectToWrite);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(PreprocessObjectToWrite));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DbProviderFactory GetProviderFactory(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatabaseProviders.GetProviderFactory(ConnectionSettings.Load(cxInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetProviderFactory));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.CodeModel;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataModel;
|
||||
using LinqToDB.Metadata;
|
||||
using LinqToDB.Naming;
|
||||
using LinqToDB.Scaffold;
|
||||
using LinqToDB.Schema;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal static class DynamicSchemaGenerator
|
||||
{
|
||||
private static ScaffoldOptions GetOptions(ConnectionSettings settings, string? contextNamespace, string contextName)
|
||||
{
|
||||
var options = ScaffoldOptions.Default();
|
||||
|
||||
// set schema load options
|
||||
options.Schema.IncludeSchemas = settings.Schema.IncludeSchemas;
|
||||
foreach (var schema in (settings.Schema.Schemas ?? (IEnumerable<string>)Array.Empty<string>()))
|
||||
options.Schema.Schemas.Add(schema);
|
||||
|
||||
options.Schema.IncludeCatalogs = settings.Schema.IncludeCatalogs;
|
||||
foreach (var catalog in (settings.Schema.Catalogs ?? (IEnumerable<string>)Array.Empty<string>()))
|
||||
options.Schema.Catalogs.Add(catalog);
|
||||
|
||||
options.Schema.LoadedObjects = SchemaObjects.Table | SchemaObjects.View;
|
||||
|
||||
if (settings.Schema.LoadForeignKeys ) options.Schema.LoadedObjects |= SchemaObjects.ForeignKey;
|
||||
if (settings.Schema.LoadProcedures ) options.Schema.LoadedObjects |= SchemaObjects.StoredProcedure;
|
||||
if (settings.Schema.LoadTableFunctions ) options.Schema.LoadedObjects |= SchemaObjects.TableFunction;
|
||||
if (settings.Schema.LoadScalarFunctions ) options.Schema.LoadedObjects |= SchemaObjects.ScalarFunction;
|
||||
if (settings.Schema.LoadAggregateFunctions) options.Schema.LoadedObjects |= SchemaObjects.AggregateFunction;
|
||||
|
||||
options.Schema.PreferProviderSpecificTypes = settings.Scaffold.UseProviderTypes;
|
||||
options.Schema.IgnoreDuplicateForeignKeys = false;
|
||||
options.Schema.UseSafeSchemaLoad = false;
|
||||
options.Schema.LoadDatabaseName = false;
|
||||
options.Schema.LoadProceduresSchema = true;
|
||||
// TODO: disabled due to generation bug in current scaffolder
|
||||
options.Schema.EnableSqlServerReturnValue = false;
|
||||
//options.Schema.EnableSqlServerReturnValue = true;
|
||||
|
||||
// set data model options
|
||||
if (!settings.Scaffold.Capitalize)
|
||||
options.DataModel.EntityColumnPropertyNameOptions.Casing = NameCasing.None;
|
||||
if (!settings.Scaffold.Pluralize)
|
||||
{
|
||||
options.DataModel.EntityContextPropertyNameOptions.Pluralization = Pluralization.None;
|
||||
options.DataModel.TargetMultipleAssociationPropertyNameOptions.Pluralization = Pluralization.None;
|
||||
}
|
||||
|
||||
options.DataModel.GenerateDefaultSchema = true;
|
||||
options.DataModel.GenerateDataType = true;
|
||||
options.DataModel.GenerateDbType = true;
|
||||
options.DataModel.GenerateLength = true;
|
||||
options.DataModel.GeneratePrecision = true;
|
||||
options.DataModel.GenerateScale = true;
|
||||
options.DataModel.HasDefaultConstructor = false;
|
||||
options.DataModel.HasConfigurationConstructor = false;
|
||||
options.DataModel.HasUntypedOptionsConstructor = false;
|
||||
options.DataModel.HasTypedOptionsConstructor = false;
|
||||
options.DataModel.ContextClassName = contextName;
|
||||
options.DataModel.BaseContextClass = "LinqToDB.LINQPad.LINQPadDataConnection";
|
||||
options.DataModel.GenerateAssociations = true;
|
||||
options.DataModel.GenerateAssociationExtensions = false;
|
||||
options.DataModel.AssociationCollectionType = "System.Collections.Generic.List<>";
|
||||
options.DataModel.MapProcedureResultToEntity = true;
|
||||
options.DataModel.TableFunctionReturnsTable = true;
|
||||
options.DataModel.GenerateProceduresSchemaError = false;
|
||||
options.DataModel.SkipProceduresWithSchemaErrors = true;
|
||||
options.DataModel.GenerateProcedureResultAsList = false;
|
||||
options.DataModel.GenerateProcedureParameterDbType = true;
|
||||
options.DataModel.GenerateProcedureSync = true;
|
||||
options.DataModel.GenerateProcedureAsync = false;
|
||||
options.DataModel.GenerateSchemaAsType = false;
|
||||
options.DataModel.GenerateIEquatable = false;
|
||||
options.DataModel.GenerateFindExtensions = FindTypes.None;
|
||||
options.DataModel.OrderFindParametersByColumnOrdinal = true;
|
||||
|
||||
// set code generation options
|
||||
options.CodeGeneration.EnableNullableReferenceTypes = false;
|
||||
options.CodeGeneration.SuppressMissingXmlDocWarnings = true;
|
||||
options.CodeGeneration.MarkAsAutoGenerated = false;
|
||||
options.CodeGeneration.ClassPerFile = false;
|
||||
options.CodeGeneration.Namespace = contextNamespace;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static (List<ExplorerItem> items, string sourceCode, string providerAssemblyLocation) GetModel(
|
||||
ConnectionSettings settings,
|
||||
ref string? contextNamespace,
|
||||
ref string contextName)
|
||||
{
|
||||
var scaffoldOptions = GetOptions(settings, contextNamespace, contextName);
|
||||
|
||||
var provider = DatabaseProviders.GetDataProvider(settings);
|
||||
|
||||
using var db = new DataConnection(provider, settings.Connection.ConnectionString!);
|
||||
if (settings.Connection.CommandTimeout != null)
|
||||
db.CommandTimeout = settings.Connection.CommandTimeout.Value;
|
||||
|
||||
var providerAssemblyLocation = db.Connection.GetType().Assembly.Location;
|
||||
|
||||
var sqlBuilder = db.DataProvider.CreateSqlBuilder(db.MappingSchema, db.Options);
|
||||
var language = LanguageProviders.CSharp;
|
||||
var interceptor = new ModelProviderInterceptor(settings, sqlBuilder);
|
||||
var generator = new Scaffolder(language, HumanizerNameConverter.Instance, scaffoldOptions, interceptor);
|
||||
|
||||
var legacySchemaProvider = new LegacySchemaProvider(db, scaffoldOptions.Schema, language);
|
||||
ISchemaProvider schemaProvider = legacySchemaProvider;
|
||||
ITypeMappingProvider typeMappingsProvider = legacySchemaProvider;
|
||||
|
||||
DatabaseModel dataModel;
|
||||
if (settings.Connection.Database == ProviderName.Access && settings.Connection.SecondaryConnectionString != null)
|
||||
{
|
||||
var secondaryProvider = DatabaseProviders.GetDataProvider(settings.Connection.SecondaryProvider, settings.Connection.SecondaryConnectionString, null);
|
||||
using var sdc = new DataConnection(secondaryProvider, settings.Connection.SecondaryConnectionString);
|
||||
|
||||
if (settings.Connection.CommandTimeout != null)
|
||||
sdc.CommandTimeout = settings.Connection.CommandTimeout.Value;
|
||||
|
||||
var secondLegacyProvider = new LegacySchemaProvider(sdc, scaffoldOptions.Schema, language);
|
||||
schemaProvider = settings.Connection.Provider == ProviderName.Access
|
||||
? new MergedAccessSchemaProvider(schemaProvider, secondLegacyProvider)
|
||||
: new MergedAccessSchemaProvider(secondLegacyProvider, schemaProvider);
|
||||
typeMappingsProvider = new AggregateTypeMappingsProvider(typeMappingsProvider, secondLegacyProvider);
|
||||
dataModel = generator.LoadDataModel(schemaProvider, typeMappingsProvider);
|
||||
}
|
||||
else
|
||||
dataModel = generator.LoadDataModel(schemaProvider, typeMappingsProvider);
|
||||
|
||||
var files = generator.GenerateCodeModel(
|
||||
sqlBuilder,
|
||||
dataModel,
|
||||
MetadataBuilders.GetMetadataBuilder(generator.Language, MetadataSource.Attributes),
|
||||
new ProviderSpecificStructsEqualityFixer(generator.Language),
|
||||
new DataModelAugmentor(language, language.TypeParser.Parse<LINQPadDataConnection>(), settings.Connection.CommandTimeout));
|
||||
|
||||
// IMPORTANT:
|
||||
// real identifiers from generated code set to data model only after this line (GenerateSourceCode call)
|
||||
// so we call GetTree or read identifiers from dataModel.DataContext.Class before this line
|
||||
var sourceCode = generator.GenerateSourceCode(dataModel, files)[0].Code;
|
||||
|
||||
contextNamespace = dataModel.DataContext.Class.Namespace;
|
||||
contextName = dataModel.DataContext.Class.Name;
|
||||
|
||||
return (interceptor.GetTree(), sourceCode, providerAssemblyLocation);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using LinqToDB.CodeModel;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// AST modification visitor used to add custom scaffold code:
|
||||
/// <list type="bullet">
|
||||
/// <item>add custom constructor to generated data context class</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
internal sealed class DataModelAugmentor : ConvertCodeModelVisitor
|
||||
{
|
||||
private readonly IEqualityComparer<IType> _typeComparer;
|
||||
private readonly IType _baseContextType;
|
||||
private readonly CodeBuilder _ast;
|
||||
private readonly int? _commandTimeout;
|
||||
|
||||
public DataModelAugmentor(
|
||||
ILanguageProvider languageProvider,
|
||||
IType baseContextType,
|
||||
int? commandTimeout)
|
||||
{
|
||||
_ast = languageProvider.ASTBuilder;
|
||||
_typeComparer = languageProvider.TypeEqualityComparerWithoutNRT;
|
||||
_baseContextType = baseContextType;
|
||||
_commandTimeout = commandTimeout;
|
||||
}
|
||||
|
||||
protected override ICodeElement Visit(CodeClass @class)
|
||||
{
|
||||
// identify context class
|
||||
if (@class.Inherits != null && _typeComparer.Equals(@class.Inherits.Type, _baseContextType))
|
||||
{
|
||||
var members = @class.Members.ToList();
|
||||
var constructors = new ConstructorGroup(@class);
|
||||
members.Add(constructors);
|
||||
|
||||
// context found
|
||||
@class = new CodeClass(
|
||||
@class.CustomAttributes,
|
||||
@class.Attributes,
|
||||
@class.XmlDoc,
|
||||
@class.Type,
|
||||
@class.Name,
|
||||
@class.Parent,
|
||||
@class.Inherits,
|
||||
@class.Implements,
|
||||
members,
|
||||
@class.TypeInitializer);
|
||||
|
||||
constructors.Class = @class;
|
||||
|
||||
// generate
|
||||
// .ctor(string provider, string? assemblyPath, string connectionString)
|
||||
var parametrizedCtor = constructors.New().SetModifiers(Modifiers.Public);
|
||||
var providerParam = _ast.Parameter(WellKnownTypes.System.String, _ast.Name("provider"), CodeParameterDirection.In);
|
||||
var assemblyPathParam = _ast.Parameter(WellKnownTypes.System.String.WithNullability(true), _ast.Name("assemblyPath"), CodeParameterDirection.In);
|
||||
var connectionStringParam = _ast.Parameter(WellKnownTypes.System.String, _ast.Name("connectionString"), CodeParameterDirection.In);
|
||||
|
||||
parametrizedCtor
|
||||
.Parameter(providerParam)
|
||||
.Parameter(assemblyPathParam)
|
||||
.Parameter(connectionStringParam)
|
||||
.Base(providerParam.Reference, assemblyPathParam.Reference, connectionStringParam.Reference);
|
||||
|
||||
// set default CommandTimeout from LINQPad connection settings
|
||||
if (_commandTimeout != null)
|
||||
{
|
||||
parametrizedCtor
|
||||
.Body()
|
||||
.Append(_ast.Assign(_ast.Member(@class.This, WellKnownTypes.LinqToDB.Data.DataConnection_CommandTimeout), _ast.Constant(_commandTimeout.Value, true)));
|
||||
}
|
||||
|
||||
return @class;
|
||||
}
|
||||
|
||||
return base.Visit(@class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,594 @@
|
|||
using System.Text;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.CodeModel;
|
||||
using LinqToDB.Common;
|
||||
using LinqToDB.DataModel;
|
||||
using LinqToDB.Scaffold;
|
||||
using LinqToDB.Schema;
|
||||
using LinqToDB.SqlProvider;
|
||||
using LinqToDB.SqlQuery;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Scaffold interceptor used to populate generated data model for dynamic context (with proper type/member identifiers).
|
||||
/// </summary>
|
||||
internal sealed class ModelProviderInterceptor : ScaffoldInterceptors
|
||||
{
|
||||
private readonly ISqlBuilder _sqlBuilder;
|
||||
private readonly bool _replaceClickHouseFixedString;
|
||||
|
||||
// stores populated model information:
|
||||
// - FK associations
|
||||
// - schema-scoped objects (views, tables, routines)
|
||||
private readonly List<AssociationData> _associations = new ();
|
||||
private readonly Dictionary<string, SchemaData> _schemaItems = new ();
|
||||
|
||||
public ModelProviderInterceptor(ConnectionSettings settings, ISqlBuilder sqlBuilder)
|
||||
{
|
||||
_sqlBuilder = sqlBuilder;
|
||||
_replaceClickHouseFixedString = settings.Connection.Database == ProviderName.ClickHouse && settings.Scaffold.ClickHouseFixedStringAsString;
|
||||
}
|
||||
|
||||
#region model DTOs
|
||||
|
||||
private record PackageData (List<TableData> Tables, List<TableData> Views, List<ProcedureData> Procedures, List<TableFunctionData> TableFunctions, List<ScalarOrAggregateFunctionData> ScalarFunctions, List<ScalarOrAggregateFunctionData> AggregateFunctions);
|
||||
private sealed record SchemaData (List<TableData> Tables, List<TableData> Views, List<ProcedureData> Procedures, List<TableFunctionData> TableFunctions, List<ScalarOrAggregateFunctionData> ScalarFunctions, List<ScalarOrAggregateFunctionData> AggregateFunctions, Dictionary<string, PackageData> Packages) : PackageData(Tables, Views, Procedures, TableFunctions, ScalarFunctions, AggregateFunctions);
|
||||
private sealed record TableData (string ContextName, IType ContextType, string DbName, List<ColumnData> Columns);
|
||||
private sealed record ColumnData (string MemberName, IType Type, string DbName, bool IsPrimaryKey, bool IsIdentity, DataType? DataType, DatabaseType DbType);
|
||||
private sealed record AssociationData (string MemberName, IType Type, bool FromSide, bool OneToMany, string KeyName, TableData Source, TableData Target);
|
||||
private sealed record ResultColumnData (string MemberName, IType Type, string DbName, DataType? DataType, DatabaseType DbType);
|
||||
private sealed record ParameterData (string Name, IType Type, ParameterDirection Direction);
|
||||
private abstract record FunctionBaseData (string MethodName, string DbName, IReadOnlyList<ParameterData> Parameters);
|
||||
private sealed record ProcedureData (string MethodName, string DbName, IReadOnlyList<ParameterData> Parameters, IReadOnlyList<ResultColumnData>? Result) : FunctionBaseData(MethodName, DbName, Parameters);
|
||||
private sealed record TableFunctionData (string MethodName, string DbName, IReadOnlyList<ParameterData> Parameters, IReadOnlyList<ResultColumnData>? Result) : FunctionBaseData(MethodName, DbName, Parameters);
|
||||
private sealed record ScalarOrAggregateFunctionData(string MethodName, string DbName, IReadOnlyList<ParameterData> Parameters, IType ResultType) : FunctionBaseData(MethodName, DbName, Parameters);
|
||||
|
||||
public enum ParameterDirection
|
||||
{
|
||||
None,
|
||||
Ref,
|
||||
Out
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type Mapping
|
||||
public override TypeMapping? GetTypeMapping(DatabaseType databaseType, ITypeParser typeParser, TypeMapping? defaultMapping)
|
||||
{
|
||||
if (_replaceClickHouseFixedString && databaseType.Name?.StartsWith("FixedString(", StringComparison.Ordinal) == true)
|
||||
return new TypeMapping(WellKnownTypes.System.String, DataType.NChar);
|
||||
|
||||
return base.GetTypeMapping(databaseType, typeParser, defaultMapping);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Model Population
|
||||
|
||||
public override void AfterSourceCodeGenerated(FinalDataModel model)
|
||||
{
|
||||
// tables lookup for association model population
|
||||
var tablesLookup = new Dictionary<EntityModel, TableData>();
|
||||
|
||||
foreach (var entity in model.Entities ) ProcessEntity (entity, tablesLookup);
|
||||
foreach (var association in model.Associations ) ProcessAssociation (association, tablesLookup);
|
||||
foreach (var proc in model.StoredProcedures ) ProcessStoredProcedure (proc);
|
||||
foreach (var func in model.TableFunctions ) ProcessTableFunction (func);
|
||||
foreach (var func in model.ScalarFunctions ) ProcessScalarFunction (func);
|
||||
foreach (var func in model.AggregateFunctions) ProcessAggregateFunction(func);
|
||||
}
|
||||
|
||||
private PackageData GetSchemaOrPackage(string? schemaName, string? packageName)
|
||||
{
|
||||
if (!_schemaItems.TryGetValue(schemaName ?? string.Empty, out var schema))
|
||||
_schemaItems.Add(schemaName ?? string.Empty, schema = new SchemaData(new(), new(), new(), new(), new(), new(), new()));
|
||||
|
||||
if (packageName != null)
|
||||
{
|
||||
if (!schema.Packages.TryGetValue(packageName, out var package))
|
||||
schema.Packages.Add(packageName, package = new(new(), new(), new(), new(), new(), new()));
|
||||
return package;
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
private void ProcessEntity(EntityModel entityModel, Dictionary<EntityModel, TableData> tablesLookup)
|
||||
{
|
||||
var schemaName = entityModel.Metadata.Name!.Value.Schema;
|
||||
var schema = GetSchemaOrPackage(schemaName, entityModel.Metadata.Name!.Value.Package);
|
||||
var columns = new List<ColumnData>();
|
||||
|
||||
foreach (var column in entityModel.Columns)
|
||||
{
|
||||
columns.Add(new ColumnData(
|
||||
column.Property.Name,
|
||||
column.Property.Type!,
|
||||
column.Metadata.Name!,
|
||||
column.Metadata.IsPrimaryKey,
|
||||
column.Metadata.IsIdentity,
|
||||
column.Metadata.DataType,
|
||||
column.Metadata.DbType!));
|
||||
}
|
||||
|
||||
var table = new TableData(
|
||||
entityModel.ContextProperty!.Name,
|
||||
entityModel.ContextProperty.Type!,
|
||||
GetDbName(entityModel.Metadata.Name!.Value.Name, schemaName),
|
||||
columns);
|
||||
|
||||
tablesLookup.Add(entityModel, table);
|
||||
|
||||
if (entityModel.Metadata.IsView)
|
||||
schema.Views.Add(table);
|
||||
else
|
||||
schema.Tables.Add(table);
|
||||
}
|
||||
|
||||
private void ProcessAssociation(AssociationModel associationModel, Dictionary<EntityModel, TableData> tablesLookup)
|
||||
{
|
||||
if ( tablesLookup.TryGetValue(associationModel.Source, out var fromTable)
|
||||
&& tablesLookup.TryGetValue(associationModel.Target, out var toTable))
|
||||
{
|
||||
_associations.Add(new AssociationData(
|
||||
associationModel.Property!.Name,
|
||||
associationModel.Property.Type!,
|
||||
true,
|
||||
associationModel.ManyToOne,
|
||||
associationModel.ForeignKeyName!,
|
||||
fromTable,
|
||||
toTable));
|
||||
|
||||
_associations.Add(new AssociationData(
|
||||
associationModel.BackreferenceProperty!.Name,
|
||||
associationModel.BackreferenceProperty.Type!,
|
||||
false,
|
||||
associationModel.ManyToOne,
|
||||
associationModel.ForeignKeyName!,
|
||||
toTable,
|
||||
fromTable));
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessStoredProcedure(StoredProcedureModel procedureModel)
|
||||
{
|
||||
var schema = GetSchemaOrPackage(procedureModel.Name.Schema, procedureModel.Name.Package);
|
||||
var parameters = CollectParameters(procedureModel.Parameters);
|
||||
|
||||
if (procedureModel.Return != null)
|
||||
parameters.Add(new ParameterData(procedureModel.Return.Parameter.Name, procedureModel.Return.Parameter.Type, ParameterDirection.Out));
|
||||
|
||||
List<ResultColumnData>? result = null;
|
||||
|
||||
if (procedureModel.Results.Count > 0)
|
||||
result = CollectResultData(procedureModel.Results[0]);
|
||||
|
||||
schema.Procedures.Add(new ProcedureData(
|
||||
procedureModel.Method.Name,
|
||||
GetDbName(procedureModel.Name.Name, procedureModel.Name.Schema),
|
||||
parameters,
|
||||
result));
|
||||
}
|
||||
|
||||
private void ProcessTableFunction(TableFunctionModel functionModel)
|
||||
{
|
||||
var schema = GetSchemaOrPackage(functionModel.Name.Schema, functionModel.Name.Package);
|
||||
var parameters = CollectParameters(functionModel.Parameters);
|
||||
var result = CollectResultData(functionModel.Result!);
|
||||
|
||||
schema.TableFunctions.Add(new TableFunctionData(
|
||||
functionModel.Method.Name,
|
||||
GetDbName(functionModel.Name.Name, functionModel.Name.Schema),
|
||||
parameters,
|
||||
result));
|
||||
}
|
||||
|
||||
private void ProcessAggregateFunction(AggregateFunctionModel functionModel)
|
||||
{
|
||||
var schema = GetSchemaOrPackage(functionModel.Name.Schema, functionModel.Name.Package);
|
||||
var parameters = CollectParameters(functionModel.Parameters);
|
||||
|
||||
schema.AggregateFunctions.Add(new ScalarOrAggregateFunctionData(
|
||||
functionModel.Method.Name,
|
||||
GetDbName(functionModel.Name.Name, functionModel.Name.Schema),
|
||||
parameters,
|
||||
functionModel.ReturnType));
|
||||
}
|
||||
|
||||
private void ProcessScalarFunction(ScalarFunctionModel functionModel)
|
||||
{
|
||||
var schema = GetSchemaOrPackage(functionModel.Name.Schema, functionModel.Name.Package);
|
||||
var parameters = CollectParameters(functionModel.Parameters);
|
||||
|
||||
schema.ScalarFunctions.Add(new ScalarOrAggregateFunctionData(
|
||||
functionModel.Method.Name,
|
||||
GetDbName(functionModel.Name.Name, functionModel.Name.Schema),
|
||||
parameters,
|
||||
functionModel.Return!));
|
||||
}
|
||||
|
||||
private static List<ResultColumnData>? CollectResultData(FunctionResult procedureModel)
|
||||
{
|
||||
var table = procedureModel.CustomTable;
|
||||
if (table == null)
|
||||
return null;
|
||||
|
||||
var result = new List<ResultColumnData>(table.Columns.Count);
|
||||
|
||||
foreach (var column in table.Columns)
|
||||
{
|
||||
result.Add(new ResultColumnData(
|
||||
column.Property.Name,
|
||||
column.Property.Type!,
|
||||
column.Metadata.Name!,
|
||||
column.Metadata.DataType,
|
||||
column.Metadata.DbType!));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<ParameterData> CollectParameters(List<FunctionParameterModel> parameters)
|
||||
{
|
||||
var parametersData = new List<ParameterData>(parameters.Count);
|
||||
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var direction = param.Direction switch
|
||||
{
|
||||
System.Data.ParameterDirection.InputOutput => ParameterDirection.Ref,
|
||||
System.Data.ParameterDirection.Output
|
||||
or System.Data.ParameterDirection.ReturnValue => ParameterDirection.Out,
|
||||
_ => ParameterDirection.None
|
||||
};
|
||||
|
||||
parametersData.Add(new ParameterData(
|
||||
param.Parameter.Name,
|
||||
param.Parameter.Type,
|
||||
direction));
|
||||
}
|
||||
|
||||
return parametersData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convert data model to LINQPad tree model
|
||||
|
||||
public List<ExplorerItem> GetTree()
|
||||
{
|
||||
var tablesLookup = new Dictionary<TableData, ExplorerItem>();
|
||||
|
||||
// don't create schema node for single schema without name (default schema)
|
||||
if (_schemaItems.Count == 1 && _schemaItems.ContainsKey(string.Empty))
|
||||
{
|
||||
var result = PopulateSchemaMembers(string.Empty, tablesLookup);
|
||||
PopulateAssociations(_associations, tablesLookup);
|
||||
return result;
|
||||
}
|
||||
|
||||
var model = new List<ExplorerItem>();
|
||||
|
||||
foreach (var schema in _schemaItems.Keys.OrderBy(static _ => _))
|
||||
{
|
||||
// for cases when default (empty) schema exists in model with named schemas
|
||||
if (schema.Length == 0)
|
||||
model.Add(new ExplorerItem("<default>", ExplorerItemKind.Schema, ExplorerIcon.Schema)
|
||||
{
|
||||
ToolTipText = $"default schema",
|
||||
SqlName = string.Empty,
|
||||
Children = PopulateSchemaMembers(schema, tablesLookup)
|
||||
});
|
||||
else
|
||||
model.Add(new ExplorerItem(schema, ExplorerItemKind.Schema, ExplorerIcon.Schema)
|
||||
{
|
||||
ToolTipText = $"schema: {schema}",
|
||||
SqlName = GetDbName(schema),
|
||||
Children = PopulateSchemaMembers(schema, tablesLookup)
|
||||
});
|
||||
}
|
||||
|
||||
// associations need references to table nodes and could define cross-schema references, so we must create them after all table nodes created
|
||||
// for all schemas
|
||||
PopulateAssociations(_associations, tablesLookup);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private List<ExplorerItem> PopulateSchemaMembers(string schemaName, Dictionary<TableData, ExplorerItem> tablesLookup)
|
||||
{
|
||||
var items = new List<ExplorerItem>();
|
||||
var data = _schemaItems[schemaName];
|
||||
|
||||
if (data.Packages.Count > 0)
|
||||
{
|
||||
foreach (var package in data.Packages.Keys.OrderBy(_ => _))
|
||||
{
|
||||
var children = new List<ExplorerItem>();
|
||||
PopulateSchemaOrPackageMembers(tablesLookup, children, data.Packages[package]);
|
||||
items.Add(new ExplorerItem(package, ExplorerItemKind.Schema, ExplorerIcon.Schema)
|
||||
{
|
||||
ToolTipText = $"package: {package}",
|
||||
SqlName = GetDbName(package),
|
||||
Children = children
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PopulateSchemaOrPackageMembers(tablesLookup, items, data);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private void PopulateSchemaOrPackageMembers(Dictionary<TableData, ExplorerItem> tablesLookup, List<ExplorerItem> items, PackageData data)
|
||||
{
|
||||
if (data.Tables.Count > 0)
|
||||
items.Add(PopulateTables(data.Tables, "Tables", ExplorerIcon.Table, tablesLookup));
|
||||
|
||||
if (data.Views.Count > 0)
|
||||
items.Add(PopulateTables(data.Views, "Views", ExplorerIcon.View, tablesLookup));
|
||||
|
||||
if (data.Procedures.Count > 0)
|
||||
items.Add(PopulateStoredProcedures(data.Procedures));
|
||||
|
||||
if (data.TableFunctions.Count > 0)
|
||||
items.Add(PopulateTableFunctions(data.TableFunctions));
|
||||
|
||||
if (data.ScalarFunctions.Count > 0)
|
||||
items.Add(PopulateScalarFunctions(data.ScalarFunctions, "Scalar Functions"));
|
||||
|
||||
if (data.AggregateFunctions.Count > 0)
|
||||
items.Add(PopulateScalarFunctions(data.AggregateFunctions, "Aggregate Functions"));
|
||||
}
|
||||
|
||||
private ExplorerItem PopulateStoredProcedures(List<ProcedureData> procedures)
|
||||
{
|
||||
var items = new List<ExplorerItem>(procedures.Count);
|
||||
|
||||
foreach (var func in procedures.OrderBy(static f => f.MethodName))
|
||||
{
|
||||
List<ExplorerItem>? children = null;
|
||||
var size = func.Parameters.Count + (func.Result != null ? 1 : 0);
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
children = new List<ExplorerItem>(size);
|
||||
AddParameters(func.Parameters, children);
|
||||
|
||||
if (func.Result != null)
|
||||
AddResultTable(func.Result, children);
|
||||
}
|
||||
|
||||
items.Add(new ExplorerItem(func.MethodName, ExplorerItemKind.QueryableObject, ExplorerIcon.StoredProc)
|
||||
{
|
||||
DragText = $"this.{func.MethodName}({string.Join(", ", func.Parameters.Select(GetParameterName))})",
|
||||
Children = children,
|
||||
IsEnumerable = func.Result != null,
|
||||
SqlName = func.DbName
|
||||
});
|
||||
}
|
||||
|
||||
return new ExplorerItem("Stored Procedures", ExplorerItemKind.Category, ExplorerIcon.StoredProc)
|
||||
{
|
||||
Children = items
|
||||
};
|
||||
}
|
||||
|
||||
private void AddResultTable(IReadOnlyList<ResultColumnData> resultColumns, List<ExplorerItem> children)
|
||||
{
|
||||
var columns = new List<ExplorerItem>(resultColumns.Count);
|
||||
|
||||
foreach (var column in resultColumns)
|
||||
{
|
||||
var dbName = GetDbName(column.DbName);
|
||||
var dbType = $"{GetTypeName(column.DataType, column.DbType)} {(column.Type.IsNullable ? "NULL" : "NOT NULL")}";
|
||||
|
||||
columns.Add(new ExplorerItem($"{column.MemberName} : {SimpleBuildTypeName(column.Type)}", ExplorerItemKind.Property, ExplorerIcon.Column)
|
||||
{
|
||||
ToolTipText = $"{dbName} {dbType}",
|
||||
DragText = column.MemberName,
|
||||
SqlName = dbName,
|
||||
SqlTypeDeclaration = dbType,
|
||||
});
|
||||
}
|
||||
|
||||
children.Add(new ExplorerItem("Result Table", ExplorerItemKind.Category, ExplorerIcon.Table)
|
||||
{
|
||||
Children = columns
|
||||
});
|
||||
}
|
||||
|
||||
private void AddParameters(IReadOnlyList<ParameterData> parameters, List<ExplorerItem> children)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
children.Add(new ExplorerItem($"{GetParameterName(param)} : {SimpleBuildTypeName(param.Type)}", ExplorerItemKind.Parameter, ExplorerIcon.Parameter));
|
||||
}
|
||||
|
||||
private static string GetParameterName(ParameterData param)
|
||||
{
|
||||
return $"{(param.Direction == ParameterDirection.Out ? "out " : param.Direction == ParameterDirection.Ref ? "ref " : null)}{param.Name}";
|
||||
}
|
||||
|
||||
private ExplorerItem PopulateTableFunctions(List<TableFunctionData> functions)
|
||||
{
|
||||
var items = new List<ExplorerItem>(functions.Count);
|
||||
|
||||
foreach (var func in functions.OrderBy(static f => f.MethodName))
|
||||
{
|
||||
var children = new List<ExplorerItem>(func.Parameters.Count + 1);
|
||||
|
||||
AddParameters(func.Parameters, children);
|
||||
if (func.Result != null)
|
||||
AddResultTable(func.Result, children);
|
||||
|
||||
items.Add(new ExplorerItem(func.MethodName, ExplorerItemKind.QueryableObject, ExplorerIcon.TableFunction)
|
||||
{
|
||||
DragText = $"{func.MethodName}({string.Join(", ", func.Parameters.Select(GetParameterName))})",
|
||||
Children = children,
|
||||
IsEnumerable = true,
|
||||
SqlName = func.DbName
|
||||
});
|
||||
}
|
||||
|
||||
return new ExplorerItem("Table Functions", ExplorerItemKind.Category, ExplorerIcon.TableFunction)
|
||||
{
|
||||
Children = items
|
||||
};
|
||||
}
|
||||
|
||||
private ExplorerItem PopulateScalarFunctions(List<ScalarOrAggregateFunctionData> functions, string categoryName)
|
||||
{
|
||||
var items = new List<ExplorerItem>(functions.Count);
|
||||
|
||||
foreach (var func in functions.OrderBy(static f => f.MethodName))
|
||||
{
|
||||
List<ExplorerItem>? children = null;
|
||||
if (func.Parameters.Count > 0)
|
||||
{
|
||||
children = new List<ExplorerItem>(func.Parameters.Count);
|
||||
AddParameters(func.Parameters, children);
|
||||
}
|
||||
|
||||
items.Add(new ExplorerItem(func.MethodName, ExplorerItemKind.QueryableObject, ExplorerIcon.TableFunction)
|
||||
{
|
||||
DragText = $"ExtensionMethods.{func.MethodName}({string.Join(", ", func.Parameters.Select(GetParameterName))})",
|
||||
Children = children,
|
||||
IsEnumerable = false,
|
||||
SqlName = func.DbName
|
||||
});
|
||||
}
|
||||
|
||||
return new ExplorerItem(categoryName, ExplorerItemKind.Category, ExplorerIcon.TableFunction)
|
||||
{
|
||||
Children = items
|
||||
};
|
||||
}
|
||||
|
||||
private ExplorerItem PopulateTables(List<TableData> tables, string category, ExplorerIcon icon, Dictionary<TableData, ExplorerItem> tablesLookup)
|
||||
{
|
||||
var children = new List<ExplorerItem>(tables.Count);
|
||||
|
||||
foreach (var table in tables.OrderBy(static t => t.ContextName))
|
||||
{
|
||||
var tableChildren = new List<ExplorerItem>(table.Columns.Count);
|
||||
|
||||
foreach (var column in table.Columns)
|
||||
{
|
||||
var dbName = GetDbName(column.DbName);
|
||||
var dbType = $"{GetTypeName(column.DataType, column.DbType)} {(column.Type.IsNullable ? "NULL" : "NOT NULL")}{(column.IsIdentity ? " IDENTITY" : string.Empty)}";
|
||||
|
||||
tableChildren.Add(
|
||||
new ExplorerItem(
|
||||
$"{column.MemberName} : {SimpleBuildTypeName(column.Type)}",
|
||||
ExplorerItemKind.Property,
|
||||
column.IsPrimaryKey ? ExplorerIcon.Key : ExplorerIcon.Column)
|
||||
{
|
||||
ToolTipText = $"{dbName} {dbType}",
|
||||
DragText = column.MemberName,
|
||||
SqlName = dbName,
|
||||
SqlTypeDeclaration = dbType,
|
||||
});
|
||||
}
|
||||
|
||||
var tableNode = new ExplorerItem(table.ContextName, ExplorerItemKind.QueryableObject, icon)
|
||||
{
|
||||
DragText = table.ContextName,
|
||||
ToolTipText = SimpleBuildTypeName(table.ContextType),
|
||||
SqlName = table.DbName,
|
||||
IsEnumerable = true,
|
||||
// we don't sort columns/associations and render associations after columns intentionally
|
||||
Children = tableChildren
|
||||
};
|
||||
|
||||
tablesLookup.Add(table, tableNode);
|
||||
|
||||
children.Add(tableNode);
|
||||
}
|
||||
|
||||
return new ExplorerItem(category, ExplorerItemKind.Category, icon)
|
||||
{
|
||||
Children = children
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateAssociations(List<AssociationData> associations, Dictionary<TableData, ExplorerItem> tablesLookup)
|
||||
{
|
||||
foreach (var association in associations)
|
||||
{
|
||||
if (tablesLookup.TryGetValue(association.Source, out var sourceNode)
|
||||
&& tablesLookup.TryGetValue(association.Target, out var targetNode))
|
||||
{
|
||||
sourceNode.Children.Add(
|
||||
new ExplorerItem(
|
||||
association.MemberName,
|
||||
association.OneToMany && association.FromSide
|
||||
? ExplorerItemKind.CollectionLink
|
||||
: ExplorerItemKind.ReferenceLink,
|
||||
association.OneToMany && association.FromSide
|
||||
? ExplorerIcon.OneToMany
|
||||
: association.OneToMany && !association.FromSide
|
||||
? ExplorerIcon.ManyToOne
|
||||
: ExplorerIcon.OneToOne)
|
||||
{
|
||||
DragText = association.MemberName,
|
||||
ToolTipText = $"{SimpleBuildTypeName(association.Type)}{(!association.FromSide ? " // Back Reference" : null)}",
|
||||
SqlName = association.KeyName,
|
||||
IsEnumerable = association.OneToMany && association.FromSide,
|
||||
HyperlinkTarget = targetNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetDbName(string name, string? schema = null)
|
||||
{
|
||||
return _sqlBuilder!.BuildObjectName(
|
||||
new StringBuilder(),
|
||||
new SqlObjectName(Name: name, Schema: schema),
|
||||
tableOptions: TableOptions.NotSet)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
private string GetTypeName(DataType? dataType, DatabaseType type)
|
||||
{
|
||||
return _sqlBuilder!.BuildDataType(
|
||||
new StringBuilder(),
|
||||
new SqlDataType(new DbDataType(typeof(object),
|
||||
dataType : dataType ?? DataType.Undefined,
|
||||
dbType : type.Name,
|
||||
length : type.Length,
|
||||
precision: type.Precision,
|
||||
scale : type.Scale)))
|
||||
.ToString();
|
||||
}
|
||||
|
||||
private readonly Dictionary<IType, string> _typeNameCache = new();
|
||||
|
||||
// we use this method as we don't have type-only generation logic in scaffold framework
|
||||
// and actually we don't need such logic - simple C# type name generator below is enough for us
|
||||
private string SimpleBuildTypeName(IType type)
|
||||
{
|
||||
if (!_typeNameCache.TryGetValue(type, out var typeName))
|
||||
{
|
||||
typeName = type.Kind switch
|
||||
{
|
||||
TypeKind.Regular
|
||||
or TypeKind.TypeArgument => type.Name!.Name,
|
||||
TypeKind.Array => $"{SimpleBuildTypeName(type.ArrayElementType!)}[]",
|
||||
TypeKind.Dynamic => "dynamic",
|
||||
TypeKind.Generic => $"{type.Name!.Name}<{string.Join(", ", type.TypeArguments!.Select(SimpleBuildTypeName))}>",
|
||||
TypeKind.OpenGeneric => $"{type.Name!.Name}<{string.Join(", ", type.TypeArguments!.Select(static _ => string.Empty))}>",
|
||||
_ => throw new InvalidOperationException($"Unsupported type kind: {type.Kind}")
|
||||
};
|
||||
|
||||
if (type.IsNullable)
|
||||
typeName += "?";
|
||||
|
||||
_typeNameCache.Add(type, typeName);
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
// IMPORTANT:
|
||||
// 1. driver must be public or it will be missing from create connection dialog (existing connections will work)
|
||||
// 2. don't rename class or namespace as it is used by LINQPad as driver identifier. If renamed, old connections will disappear from UI
|
||||
/// <summary>
|
||||
/// Implements LINQPad driver for static (pre-compiled) model.
|
||||
/// </summary>
|
||||
public sealed class LinqToDBStaticDriver : StaticDataContextDriver
|
||||
{
|
||||
private MappingSchema? _mappingSchema;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Name => DriverHelper.Name;
|
||||
/// <inheritdoc/>
|
||||
public override string Author => DriverHelper.Author;
|
||||
|
||||
static LinqToDBStaticDriver() => DriverHelper.Init();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetConnectionDescription(IConnectionInfo cxInfo) => DriverHelper.GetConnectionDescription(cxInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShowConnectionDialog(IConnectionInfo cxInfo, ConnectionDialogOptions dialogOptions) => DriverHelper.ShowConnectionDialog(cxInfo, dialogOptions, false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type? customType)
|
||||
{
|
||||
if (customType == null)
|
||||
return new();
|
||||
|
||||
try
|
||||
{
|
||||
return StaticSchemaGenerator.GetSchema(customType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error($"{ex}\n{ex.StackTrace}", "Schema Load Error");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ParameterDescriptor[] _contextParameters = new[]
|
||||
{
|
||||
new ParameterDescriptor("configuration", typeof(string).FullName)
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
if (settings.StaticContext.ConfigurationName != null)
|
||||
return _contextParameters;
|
||||
|
||||
return base.GetContextConstructorParameters(cxInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetContextConstructorParameters));
|
||||
return Array.Empty<ParameterDescriptor>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object[] GetContextConstructorArguments(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ConnectionSettings.Load(cxInfo);
|
||||
|
||||
#if NETFRAMEWORK
|
||||
var configurationPath = settings.StaticContext.LocalConfigurationPath ?? settings.StaticContext.ConfigurationPath;
|
||||
#else
|
||||
var configurationPath = settings.StaticContext.ConfigurationPath;
|
||||
#endif
|
||||
TryLoadAppSettingsJson(configurationPath);
|
||||
|
||||
if (settings.StaticContext.ConfigurationName != null)
|
||||
return new object[] { settings.StaticContext.ConfigurationName };
|
||||
|
||||
return base.GetContextConstructorArguments(cxInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetContextConstructorArguments));
|
||||
return Array.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<string> GetNamespacesToAdd(IConnectionInfo cxInfo) => DriverHelper.DefaultImports;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearConnectionPools(IConnectionInfo cxInfo) => DriverHelper.ClearConnectionPools(cxInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
|
||||
{
|
||||
_mappingSchema = DriverHelper.InitializeContext(cxInfo, (DataConnection)context, executionManager);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager, object[] constructorArguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (context is IDisposable ctx)
|
||||
ctx.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(TearDownContext));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PreprocessObjectToWrite(ref object objectToWrite, ObjectGraphInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
objectToWrite = ValueFormatter.Format(objectToWrite);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(PreprocessObjectToWrite));
|
||||
}
|
||||
}
|
||||
|
||||
private void TryLoadAppSettingsJson(string? appConfigPath)
|
||||
{
|
||||
if (appConfigPath?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) == true)
|
||||
DataConnection.DefaultSettings = AppConfig.LoadJson(appConfigPath);
|
||||
#if !NETFRAMEWORK
|
||||
if (appConfigPath?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) == true)
|
||||
DataConnection.DefaultSettings = AppConfig.LoadAppConfig(appConfigPath);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IDbConnection GetIDbConnection(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatabaseProviders.CreateConnection(ConnectionSettings.Load(cxInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetIDbConnection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DbProviderFactory GetProviderFactory(IConnectionInfo cxInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatabaseProviders.GetProviderFactory(ConnectionSettings.Load(cxInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DriverHelper.HandleException(ex, nameof(GetProviderFactory));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
using System.Collections;
|
||||
using System.Text;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Extensions;
|
||||
using LinqToDB.Mapping;
|
||||
using LinqToDB.Reflection;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Generates schema tree structure for static context using reflection.
|
||||
/// </summary>
|
||||
internal static class StaticSchemaGenerator
|
||||
{
|
||||
private sealed class TableInfo
|
||||
{
|
||||
public TableInfo(PropertyInfo propertyInfo)
|
||||
{
|
||||
Name = propertyInfo.Name;
|
||||
Type = propertyInfo.PropertyType.GetItemType()!;
|
||||
TypeAccessor = TypeAccessor.GetAccessor(Type);
|
||||
|
||||
var tableAttr = Type.GetCustomAttribute<TableAttribute>();
|
||||
|
||||
if (tableAttr != null)
|
||||
{
|
||||
IsColumnAttributeRequired = tableAttr.IsColumnAttributeRequired;
|
||||
IsView = tableAttr.IsView;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Table accessor property name.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
/// <summary>
|
||||
/// Table mapping class type.
|
||||
/// </summary>
|
||||
public readonly Type Type;
|
||||
/// <summary>
|
||||
/// Value of <see cref="TableAttribute.IsColumnAttributeRequired"/> mapping property for mapping.
|
||||
/// </summary>
|
||||
public readonly bool IsColumnAttributeRequired;
|
||||
/// <summary>
|
||||
/// Table mapping <see cref="TypeAccessor"/> instance.
|
||||
/// </summary>
|
||||
public readonly TypeAccessor TypeAccessor;
|
||||
/// <summary>
|
||||
/// <see cref="TableAttribute.IsView"/> value for mapping.
|
||||
/// </summary>
|
||||
public readonly bool IsView;
|
||||
}
|
||||
|
||||
public static List<ExplorerItem> GetSchema(Type customContextType)
|
||||
{
|
||||
var items = new List<ExplorerItem>();
|
||||
|
||||
List<ExplorerItem>? tableItems = null;
|
||||
List<ExplorerItem>? viewItems = null;
|
||||
|
||||
// tables discovered using table access properties in context:
|
||||
// ITable<TableRecord> Prop or // IQueryable<TableRecord> Prop
|
||||
var tables = customContextType.GetProperties()
|
||||
.Where(static p => p.GetCustomAttribute<ObsoleteAttribute>() == null && typeof(IQueryable<>).IsSameOrParentOf(p.PropertyType))
|
||||
.OrderBy(static p => p.Name)
|
||||
.Select(static p => new TableInfo(p));
|
||||
|
||||
var lookup = new Dictionary<Type, ExplorerItem>();
|
||||
|
||||
foreach (var table in tables)
|
||||
{
|
||||
var list = table.IsView ? (viewItems ??= new()) : (tableItems ??= new());
|
||||
|
||||
var item = GetTable(table.IsView ? ExplorerIcon.View : ExplorerIcon.Table, table);
|
||||
list.Add(item);
|
||||
lookup.Add(table.Type, item);
|
||||
|
||||
// add association nodes
|
||||
foreach (var ma in table.TypeAccessor.Members)
|
||||
{
|
||||
var aa = ma.MemberInfo.GetCustomAttribute<AssociationAttribute>();
|
||||
|
||||
if (aa != null)
|
||||
{
|
||||
var isToMany = ma.Type is IEnumerable;
|
||||
// TODO: try to infer this information?
|
||||
var backToMany = true;
|
||||
|
||||
var otherType = isToMany ? ma.Type.GetItemType()! : ma.Type;
|
||||
lookup.TryGetValue(otherType, out var otherItem);
|
||||
|
||||
item.Children.Add(
|
||||
new ExplorerItem(
|
||||
ma.Name,
|
||||
isToMany
|
||||
? ExplorerItemKind.CollectionLink
|
||||
: ExplorerItemKind.ReferenceLink,
|
||||
isToMany
|
||||
? ExplorerIcon.OneToMany
|
||||
: backToMany
|
||||
? ExplorerIcon.ManyToOne
|
||||
: ExplorerIcon.OneToOne)
|
||||
{
|
||||
DragText = ma.Name,
|
||||
ToolTipText = GetTypeName(ma.Type),
|
||||
IsEnumerable = isToMany,
|
||||
HyperlinkTarget = otherItem
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tableItems != null)
|
||||
items.Add(new ExplorerItem("Tables", ExplorerItemKind.Category, ExplorerIcon.Table)
|
||||
{
|
||||
Children = tableItems
|
||||
});
|
||||
|
||||
if (viewItems != null)
|
||||
items.Add(new ExplorerItem("Views", ExplorerItemKind.Category, ExplorerIcon.View)
|
||||
{
|
||||
Children = viewItems
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static ExplorerItem GetTable(ExplorerIcon icon, TableInfo table)
|
||||
{
|
||||
var columns =
|
||||
(
|
||||
from ma in table.TypeAccessor.Members
|
||||
let aa = ma.MemberInfo.GetCustomAttribute<AssociationAttribute>()
|
||||
where aa == null
|
||||
let ca = ma.MemberInfo.GetCustomAttribute<ColumnAttribute>()
|
||||
let id = ma.MemberInfo.GetCustomAttribute<IdentityAttribute>()
|
||||
let pk = ma.MemberInfo.GetCustomAttribute<PrimaryKeyAttribute>()
|
||||
orderby
|
||||
ca == null ? 1 : ca.Order >= 0 ? 0 : 2,
|
||||
ca?.Order,
|
||||
ma.Name
|
||||
where
|
||||
ca != null && ca.IsColumn ||
|
||||
pk != null ||
|
||||
id != null ||
|
||||
ca == null && !table.IsColumnAttributeRequired && MappingSchema.Default.IsScalarType(ma.Type)
|
||||
select new ExplorerItem(
|
||||
ma.Name,
|
||||
ExplorerItemKind.Property,
|
||||
pk != null || ca != null && ca.IsPrimaryKey ? ExplorerIcon.Key : ExplorerIcon.Column)
|
||||
{
|
||||
Text = $"{ma.Name} : {GetTypeName(ma.Type)}",
|
||||
DragText = ma.Name,
|
||||
}
|
||||
).ToList();
|
||||
|
||||
var ret = new ExplorerItem(table.Name, ExplorerItemKind.QueryableObject, icon)
|
||||
{
|
||||
DragText = table.Name,
|
||||
IsEnumerable = true,
|
||||
Children = columns
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static string GetTypeName(Type type)
|
||||
{
|
||||
switch (type.FullName)
|
||||
{
|
||||
case "System.Boolean" : return "bool";
|
||||
case "System.Byte" : return "byte";
|
||||
case "System.SByte" : return "sbyte";
|
||||
case "System.Int16" : return "short";
|
||||
case "System.Int32" : return "int";
|
||||
case "System.Int64" : return "long";
|
||||
case "System.UInt16" : return "ushort";
|
||||
case "System.UInt32" : return "uint";
|
||||
case "System.UInt64" : return "ulong";
|
||||
case "System.Decimal" : return "decimal";
|
||||
case "System.Single" : return "float";
|
||||
case "System.Double" : return "double";
|
||||
case "System.String" : return "string";
|
||||
case "System.Char" : return "char";
|
||||
case "System.Object" : return "object";
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
return GetTypeName(type.GetElementType()!) + "[]";
|
||||
|
||||
if (type.IsNullable())
|
||||
return GetTypeName(type.ToNullableUnderlying()) + '?';
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var typeName = new StringBuilder();
|
||||
typeName
|
||||
.Append(type.Name)
|
||||
.Append('<');
|
||||
|
||||
var first = true;
|
||||
foreach (var param in type.GetGenericArguments())
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
typeName.Append(", ");
|
||||
|
||||
typeName.Append(GetTypeName(param));
|
||||
}
|
||||
|
||||
return typeName.Append('>').ToString();
|
||||
}
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using AdoNetCore.AseClient;
|
||||
using ClickHouse.Client.Numerics;
|
||||
using FirebirdSql.Data.Types;
|
||||
using IBM.Data.DB2Types;
|
||||
using LINQPad;
|
||||
using Microsoft.SqlServer.Types;
|
||||
using MySqlConnector;
|
||||
using NpgsqlTypes;
|
||||
using Oracle.ManagedDataAccess.Types;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal static class ValueFormatter
|
||||
{
|
||||
private static readonly object _null = Util.RawHtml(new XElement("span", new XAttribute("style", "text-align:center;"), new XElement("i", new XAttribute("style", "font-style: italic"), "null")));
|
||||
|
||||
// don't use IDatabaseProvider interface as:
|
||||
// 1. some providers used by multiple databases
|
||||
// 2. user could use those types with any database
|
||||
private static readonly IReadOnlyDictionary<Type, Func<object, object>> _typeConverters;
|
||||
private static readonly IReadOnlyDictionary<Type, Func<object, object>> _baseTypeConverters;
|
||||
private static readonly IReadOnlyDictionary<string, Func<object, object>> _byTypeNameConverters;
|
||||
|
||||
static ValueFormatter()
|
||||
{
|
||||
var typeConverters = new Dictionary<Type, Func<object, object>>();
|
||||
var baseTypeConverters = new Dictionary<Type, Func<object, object>>();
|
||||
var byTypeNameConverters = new Dictionary<string, Func<object, object>>();
|
||||
_typeConverters = typeConverters;
|
||||
_baseTypeConverters = baseTypeConverters;
|
||||
_byTypeNameConverters = byTypeNameConverters;
|
||||
|
||||
// generic types
|
||||
typeConverters.Add(typeof(BigInteger) , ConvertToString);
|
||||
typeConverters.Add(typeof(BitArray) , ConvertBitArray);
|
||||
typeConverters.Add(typeof(BitVector32) , ConvertToString);
|
||||
typeConverters.Add(typeof(PhysicalAddress), ConvertToString);
|
||||
|
||||
// base generic types
|
||||
baseTypeConverters.Add(typeof(IPAddress), ConvertToString);
|
||||
|
||||
// provider-specific types
|
||||
|
||||
// SQLCE/SQLSERVER types
|
||||
typeConverters.Add(typeof(SqlXml) , ConvertSqlXml);
|
||||
typeConverters.Add(typeof(SqlChars) , ConvertSqlChars);
|
||||
typeConverters.Add(typeof(SqlBytes) , ConvertSqlBytes);
|
||||
typeConverters.Add(typeof(SqlBinary), ConvertSqlBinary);
|
||||
|
||||
// ClickHouse.Client
|
||||
typeConverters.Add(typeof(ClickHouseDecimal), ConvertToString);
|
||||
|
||||
// Firebird
|
||||
typeConverters.Add(typeof(FbZonedTime) , ConvertToString);
|
||||
typeConverters.Add(typeof(FbZonedDateTime), ConvertToString);
|
||||
typeConverters.Add(typeof(FbDecFloat) , ConvertFbDecFloat);
|
||||
|
||||
// Sybase ASE
|
||||
typeConverters.Add(typeof(AseDecimal), ConvertToString);
|
||||
|
||||
// MySqlConnector
|
||||
typeConverters.Add(typeof(MySqlDateTime), ConvertToString);
|
||||
typeConverters.Add(typeof(MySqlDecimal) , ConvertToString);
|
||||
typeConverters.Add(typeof(MySqlGeometry), ConvertMySqlGeometry);
|
||||
|
||||
// sql server spatial types
|
||||
typeConverters.Add(typeof(SqlGeography), ConvertToString);
|
||||
typeConverters.Add(typeof(SqlGeometry) , ConvertToString);
|
||||
|
||||
// npgsql
|
||||
baseTypeConverters.Add(typeof(NpgsqlTsQuery) , ConvertToString);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
typeConverters.Add(typeof(NpgsqlInet) , ConvertToString);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
typeConverters.Add(typeof(NpgsqlInterval) , ConvertNpgsqlInterval);
|
||||
typeConverters.Add(typeof(NpgsqlLogSequenceNumber), ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlTid) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlTsVector) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlLine) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlCircle) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlPolygon) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlPath) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlBox) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlLSeg) , ConvertToString);
|
||||
typeConverters.Add(typeof(NpgsqlPoint) , ConvertToString);
|
||||
|
||||
|
||||
// oracle
|
||||
typeConverters.Add(typeof(OracleClob) , ConvertOracleClob);
|
||||
typeConverters.Add(typeof(OracleBinary) , ConvertOracleBinary);
|
||||
typeConverters.Add(typeof(OracleBoolean) , ConvertOracleBoolean);
|
||||
typeConverters.Add(typeof(OracleDate) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleDecimal) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleIntervalDS) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleIntervalYM) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleString) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleTimeStamp) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleTimeStampLTZ), ConvertToString);
|
||||
typeConverters.Add(typeof(OracleTimeStampTZ) , ConvertToString);
|
||||
typeConverters.Add(typeof(OracleBlob) , ConvertOracleBlob);
|
||||
typeConverters.Add(typeof(OracleBFile) , ConvertOracleBFile);
|
||||
typeConverters.Add(typeof(OracleXmlType) , ConvertOracleXmlType);
|
||||
|
||||
// sap hana
|
||||
byTypeNameConverters.Add("Sap.Data.Hana.HanaDecimal", ConvertToString);
|
||||
|
||||
// db2
|
||||
// use strings to avoid exceptions when DB2 provider loaded from process with wrong bitness
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Binary" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Blob" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Clob" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Date" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2DateTime" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Decimal" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2DecimalFloat" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Double" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Int16" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Int32" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Int64" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Real" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Real370" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2RowId" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2String" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Time" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2TimeStamp" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2TimeStampOffset", ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2XsrObjectId" , ConvertToString);
|
||||
byTypeNameConverters.Add("IBM.Data.DB2Types.DB2Xml" , ConvertDB2Xml);
|
||||
}
|
||||
|
||||
public static object Format(object value)
|
||||
{
|
||||
// handle special NULL values
|
||||
if (IsNull(value))
|
||||
return _null;
|
||||
|
||||
// convert specialized type to simple value (e.g. string)
|
||||
var valueType = value.GetType();
|
||||
if (_typeConverters.TryGetValue(valueType, out var converter))
|
||||
value = converter(value);
|
||||
else if (_byTypeNameConverters.TryGetValue(valueType.FullName!, out converter))
|
||||
value = converter(value);
|
||||
else
|
||||
{
|
||||
foreach (var type in _baseTypeConverters.Keys)
|
||||
if (type.IsAssignableFrom(valueType))
|
||||
{
|
||||
value = _baseTypeConverters[type](value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// apply simple values formatting
|
||||
if (value is string strVal)
|
||||
return Format(strVal);
|
||||
|
||||
if (value is bool boolVal)
|
||||
return Format(boolVal);
|
||||
|
||||
if (value is char[] chars)
|
||||
return Format(chars);
|
||||
|
||||
if (value is byte[] binary)
|
||||
return Format(binary);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool IsNull(object value)
|
||||
{
|
||||
// note that linqpad will call formatter only for non-primitive values.
|
||||
// It will not call it for null and DBNull values so we cannot change their formatting (technically we can do it by formatting owner object, but it doesn't make sense)
|
||||
|
||||
// INullable implemented by System.Data.SqlTypes.Sql* types
|
||||
return (value is System.Data.SqlTypes.INullable nullable && nullable.IsNull)
|
||||
|| (value is Oracle.ManagedDataAccess.Types.INullable onull && onull.IsNull)
|
||||
|| (value.GetType().FullName!.StartsWith("IBM.Data.DB2Types.") && IsDB2Null(value));
|
||||
|
||||
// moved to function to avoid assembly load errors when loaded with wrong process bitness
|
||||
static bool IsDB2Null(object value) => value is IBM.Data.DB2Types.INullable db2null && db2null.IsNull;
|
||||
}
|
||||
|
||||
#region Final formatters
|
||||
private static object Format(string str)
|
||||
{
|
||||
var components = new List<object>();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// encode invalid characters as C# escape sequence
|
||||
foreach (var chr in str)
|
||||
{
|
||||
var formattedChar = Format(chr);
|
||||
if (formattedChar is string chrStr)
|
||||
sb.Append(chrStr);
|
||||
else
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
components.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
}
|
||||
|
||||
components.Add(formattedChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
components.Add(sb.ToString());
|
||||
|
||||
return Util.RawHtml(new XElement("span", components.ToArray()));
|
||||
}
|
||||
|
||||
private static object Format(char[] chars)
|
||||
{
|
||||
var components = new List<object>();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// encode invalid characters as C# escape sequence
|
||||
var first = true;
|
||||
foreach (var chr in chars)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
sb.Append(' ');
|
||||
|
||||
var formattedChar = Format(chr);
|
||||
if (formattedChar is string chrStr)
|
||||
sb.Append(chrStr);
|
||||
else
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
components.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
}
|
||||
|
||||
components.Add(formattedChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
components.Add(sb.ToString());
|
||||
|
||||
return Util.RawHtml(new XElement("span", components.ToArray()));
|
||||
}
|
||||
|
||||
private static object Format(byte[] value)
|
||||
{
|
||||
var sb = new StringBuilder($" Len:{value.Length} ");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < value.Length && i < 10; i++)
|
||||
sb.Append($"{value[i]:X2}:");
|
||||
|
||||
if (i > 0)
|
||||
sb.Length--;
|
||||
|
||||
if (i < value.Length)
|
||||
sb.Append("...");
|
||||
|
||||
return Util.RawHtml(new XElement("span", sb.ToString()));
|
||||
}
|
||||
|
||||
private static object Format(char chr)
|
||||
{
|
||||
if (!XmlConvert.IsXmlChar(chr) && !char.IsHighSurrogate(chr) && !char.IsLowSurrogate(chr))
|
||||
return new XElement("span", new XElement("i", new XAttribute("style", "font-style: italic"), chr <= 255 ? $"\\x{((short)chr):X2}" : $"\\u{((short)chr):X4}"));
|
||||
else
|
||||
return chr.ToString();
|
||||
}
|
||||
|
||||
private static object Format(bool value) => Util.RawHtml(new XElement("span", value.ToString()));
|
||||
#endregion
|
||||
|
||||
|
||||
#region Primitives (final types)
|
||||
|
||||
// for types that already implement rendering of all data using ToString
|
||||
private static object ConvertToString(object value) => value.ToString()!;
|
||||
|
||||
#region Runtime
|
||||
private static object ConvertBitArray(object value)
|
||||
{
|
||||
var val = (BitArray)value;
|
||||
var sb = new StringBuilder($" Len:{val.Length} 0b");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < val.Length && i < 64; i++)
|
||||
sb.Append(val[i] ? '1' : '0');
|
||||
|
||||
if (i < val.Length)
|
||||
sb.Append("...");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Npgsql
|
||||
private static object ConvertNpgsqlInterval(object value)
|
||||
{
|
||||
var val = (NpgsqlInterval)value;
|
||||
// let's use ISO8601 duration format
|
||||
// Time is microseconds
|
||||
return $"P{val.Months}M{val.Days}DT{((decimal)val.Time) / 1_000_000}S";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MySqlConnector
|
||||
private static object ConvertMySqlGeometry(object value)
|
||||
{
|
||||
var val = (MySqlGeometry)value;
|
||||
return new { SRID = val.SRID, WKB = val.Value.Skip(4) };
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Firebird
|
||||
private static object ConvertFbDecFloat(object value)
|
||||
{
|
||||
// type reders as {Coefficient}E{Exponent} which is not very noice
|
||||
var typedValue = (FbDecFloat)value!;
|
||||
var isNegative = typedValue.Coefficient < 0;
|
||||
var strValue = (isNegative ? BigInteger.Negate(typedValue.Coefficient) : typedValue.Coefficient).ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// semi-localized rendering...
|
||||
if (typedValue.Exponent < 0)
|
||||
{
|
||||
var exp = -typedValue.Exponent;
|
||||
if (exp < strValue.Length)
|
||||
strValue = strValue.Insert(strValue.Length - exp, CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
|
||||
else if (exp == strValue.Length)
|
||||
strValue = $"0{CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator}{strValue}";
|
||||
else // Exponent > len(Coefficient)
|
||||
strValue = $"0{CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator}{new string('0', exp - strValue.Length)}{strValue}";
|
||||
}
|
||||
else if (typedValue.Exponent > 0)
|
||||
strValue = $"{strValue}{new string('0', typedValue.Exponent)}";
|
||||
|
||||
return isNegative ? $"-{strValue}" : strValue;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Sql*
|
||||
private static object ConvertSqlXml (object value) => ((SqlXml)value).Value;
|
||||
private static object ConvertSqlChars (object value) => ((SqlChars)value).Value;
|
||||
private static object ConvertSqlBytes (object value) => ((SqlBytes)value).Value;
|
||||
private static object ConvertSqlBinary(object value) => ((SqlBinary)value).Value;
|
||||
#endregion
|
||||
|
||||
#region Oracle
|
||||
private static object ConvertOracleClob (object value) => $"OracleClob(Length = {((OracleClob)value).Length})";
|
||||
private static object ConvertOracleBlob (object value) => $"OracleBlob(Length = {((OracleBlob)value).Length})";
|
||||
private static object ConvertOracleBFile (object value) => $"OracleBFile(Directory = {((OracleBFile)value).DirectoryName}, FileName = {((OracleBFile)value).FileName})";
|
||||
private static object ConvertOracleBinary (object value) => ((OracleBinary)value).Value;
|
||||
private static object ConvertOracleBoolean(object value) => ((OracleBoolean)value).Value;
|
||||
private static object ConvertOracleXmlType(object value) => ((OracleXmlType)value).Value;
|
||||
#endregion
|
||||
|
||||
#region DB2
|
||||
private static object ConvertDB2Xml(object value) => ((DB2Xml)value).GetString();
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
static class Extensions
|
||||
{
|
||||
public static bool MaybeEqualTo(this Type type1, Type type2)
|
||||
{
|
||||
return type1.FullName == type2.FullName;
|
||||
}
|
||||
|
||||
public static bool MaybeChildOf(this Type type, Type parent)
|
||||
{
|
||||
Type? currentType = type;
|
||||
do
|
||||
{
|
||||
if (currentType.IsGenericType)
|
||||
{
|
||||
var gtype = currentType.GetGenericTypeDefinition();
|
||||
|
||||
if (gtype.MaybeEqualTo(parent))
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var inf in currentType.GetInterfaces())
|
||||
if (inf.MaybeChildOf(parent))
|
||||
return true;
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
|
||||
} while(currentType != null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static dynamic? GetCustomAttributeLike<T>(this MemberInfo memberInfo)
|
||||
{
|
||||
return memberInfo.GetCustomAttributes().FirstOrDefault(a => a.GetType().MaybeEqualTo(typeof(T)));
|
||||
}
|
||||
|
||||
public static bool HasProperty(dynamic obj, string name)
|
||||
{
|
||||
Type objType = obj.GetType();
|
||||
|
||||
if (objType == typeof (ExpandoObject))
|
||||
return ((IDictionary<string, object>)obj).ContainsKey(name);
|
||||
|
||||
return objType.GetProperty(name) != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +1,32 @@
|
|||
using CodeJam.Strings;
|
||||
using CodeJam.Xml;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for generated contexts and context for direct use.
|
||||
/// </summary>
|
||||
public class LINQPadDataConnection : DataConnection
|
||||
{
|
||||
public class LINQPadDataConnection : DataConnection
|
||||
/// <summary>
|
||||
/// Constructor for inherited context.
|
||||
/// </summary>
|
||||
protected LINQPadDataConnection(string? providerName, string? providerPath, string? connectionString)
|
||||
: base(
|
||||
DatabaseProviders.GetDataProvider(providerName, connectionString, providerPath),
|
||||
connectionString ?? throw new LinqToDBLinqPadException("Connection string missing"))
|
||||
{
|
||||
public LINQPadDataConnection()
|
||||
{
|
||||
Init();
|
||||
InitMappingSchema();
|
||||
}
|
||||
}
|
||||
|
||||
public LINQPadDataConnection(string? providerName, string? providerPath, string connectionString)
|
||||
: base(ProviderHelper.GetProvider(providerName, providerPath).GetDataProvider(connectionString), connectionString)
|
||||
{
|
||||
Init();
|
||||
InitMappingSchema();
|
||||
}
|
||||
|
||||
public LINQPadDataConnection(IConnectionInfo cxInfo)
|
||||
: this(
|
||||
(string?)cxInfo.DriverData.Element(CX.ProviderName),
|
||||
(string?)cxInfo.DriverData.Element(CX.ProviderPath),
|
||||
cxInfo.DatabaseInfo.CustomCxString)
|
||||
{
|
||||
CommandTimeout = cxInfo.DriverData.ElementValueOrDefault(CX.CommandTimeout, str => str.ToInt32() ?? 0, 0);
|
||||
}
|
||||
|
||||
protected virtual void InitMappingSchema()
|
||||
{
|
||||
}
|
||||
|
||||
static void Init()
|
||||
{
|
||||
TurnTraceSwitchOn();
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructor for use from driver code directly.
|
||||
/// </summary>
|
||||
internal LINQPadDataConnection(ConnectionSettings settings)
|
||||
: this(
|
||||
settings.Connection.Provider,
|
||||
settings.Connection.ProviderPath,
|
||||
settings.Connection.ConnectionString)
|
||||
{
|
||||
if (settings.Connection.CommandTimeout != null)
|
||||
CommandTimeout = settings.Connection.CommandTimeout.Value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,261 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.Mapping;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.SqlServer.Types;
|
||||
using System.Numerics;
|
||||
#if !NETCORE
|
||||
using System.Net.NetworkInformation;
|
||||
#else
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
public class LinqToDBDriver : DynamicDataContextDriver
|
||||
{
|
||||
#if NETCORE
|
||||
private static readonly Regex _runtimeTokenExtractor = new (@"^.+\\(?<token>[^\\]+)\\[^\\]+$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
#endif
|
||||
|
||||
public override string Name => "LINQ to DB";
|
||||
public override string Author => DriverHelper.Author;
|
||||
|
||||
static LinqToDBDriver() => DriverHelper.Init();
|
||||
|
||||
public override string GetConnectionDescription(IConnectionInfo cxInfo) => DriverHelper.GetConnectionDescription(cxInfo);
|
||||
|
||||
public override DateTime? GetLastSchemaUpdate(IConnectionInfo cxInfo)
|
||||
{
|
||||
var providerName = (string?)cxInfo.DriverData.Element(CX.ProviderName);
|
||||
|
||||
if (providerName == ProviderName.SqlServer)
|
||||
using (var db = new LINQPadDataConnection(cxInfo))
|
||||
return db.Query<DateTime?>("select max(modify_date) from sys.objects").FirstOrDefault();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[Obsolete("base method obsoleted")]
|
||||
public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) => DriverHelper.ShowConnectionDialog(cxInfo, isNewConnection, true);
|
||||
|
||||
#if NETCORE
|
||||
public IEnumerable<string> GetFallbackTokens(string forToken)
|
||||
{
|
||||
switch (forToken)
|
||||
{
|
||||
case "net6.0":
|
||||
yield return "net6.0";
|
||||
yield return "net5.0";
|
||||
yield return "netcoreapp3.1";
|
||||
yield return "netstandard2.1";
|
||||
yield return "netstandard2.0";
|
||||
yield break;
|
||||
case "net5.0":
|
||||
yield return "net5.0";
|
||||
yield return "netcoreapp3.1";
|
||||
yield return "netstandard2.1";
|
||||
yield return "netstandard2.0";
|
||||
yield break;
|
||||
case "netcoreapp3.1":
|
||||
yield return "netcoreapp3.1";
|
||||
yield return "netstandard2.1";
|
||||
yield return "netstandard2.0";
|
||||
yield break;
|
||||
case "netstandard2.1":
|
||||
yield return "netstandard2.1";
|
||||
yield return "netstandard2.0";
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return forToken;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override List<ExplorerItem> GetSchemaAndBuildAssembly(
|
||||
IConnectionInfo cxInfo, AssemblyName assemblyToBuild, ref string nameSpace, ref string typeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var gen = new SchemaAndCodeGenerator(cxInfo);
|
||||
var items = gen.GetItemsAndCode(nameSpace, typeName).ToList();
|
||||
var text = gen.Code.ToString();
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(text);
|
||||
|
||||
var references = new List<MetadataReference>
|
||||
{
|
||||
#if !NETCORE
|
||||
MetadataReference.CreateFromFile(typeof(object). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(Enumerable). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(IDbConnection). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(PhysicalAddress) .Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(BigInteger) .Assembly.Location),
|
||||
#endif
|
||||
MetadataReference.CreateFromFile(typeof(DataConnection). Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(LINQPadDataConnection).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(SqlHierarchyId) .Assembly.Location),
|
||||
};
|
||||
|
||||
#if NETCORE
|
||||
// TODO: find better way to do it
|
||||
// hack to overwrite provider assembly references that target wrong runtime
|
||||
// e.g. gen.References contains path to net5 MySqlConnector
|
||||
// but GetCoreFxReferenceAssemblies returns netcoreapp3.1 runtime references
|
||||
var coreAssemblies = GetCoreFxReferenceAssemblies(cxInfo);
|
||||
var runtimeToken = _runtimeTokenExtractor.Match(coreAssemblies[0]).Groups["token"].Value;
|
||||
references.AddRange(coreAssemblies.Select(path => MetadataReference.CreateFromFile(path)));
|
||||
|
||||
foreach (var reference in gen.References)
|
||||
{
|
||||
var found = false;
|
||||
var token = _runtimeTokenExtractor.Match(reference).Groups["token"].Value;
|
||||
foreach (var fallback in GetFallbackTokens(runtimeToken))
|
||||
{
|
||||
if (token == fallback)
|
||||
{
|
||||
found = true;
|
||||
references.Add(MetadataReference.CreateFromFile(reference));
|
||||
break;
|
||||
}
|
||||
|
||||
var newReference = reference.Replace($"\\{token}\\", $"\\{fallback}\\");
|
||||
if (File.Exists(newReference))
|
||||
{
|
||||
references.Add(MetadataReference.CreateFromFile(newReference));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
references.Add(MetadataReference.CreateFromFile(reference));
|
||||
}
|
||||
#else
|
||||
references.AddRange(gen.References.Select(r => MetadataReference.CreateFromFile(r)));
|
||||
#endif
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
assemblyToBuild.Name!,
|
||||
syntaxTrees : new[] { syntaxTree },
|
||||
references : references,
|
||||
options : new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
using (var stream = new FileStream(assemblyToBuild.CodeBase!, FileMode.Create))
|
||||
{
|
||||
var result = compilation.Emit(stream);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
var failures = result.Diagnostics.Where(diagnostic =>
|
||||
diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
|
||||
|
||||
foreach (var diagnostic in failures)
|
||||
throw new LinqToDBLinqPadException(diagnostic.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"{ex}\n{ex.StackTrace}", "Schema Build Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Debug.WriteLine($"{ex}\n{ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ParameterDescriptor("provider", typeof(string).FullName),
|
||||
new ParameterDescriptor("providerPath", typeof(string).FullName),
|
||||
new ParameterDescriptor("connectionString", typeof(string).FullName),
|
||||
};
|
||||
}
|
||||
|
||||
public override object?[] GetContextConstructorArguments(IConnectionInfo cxInfo)
|
||||
{
|
||||
return new object?[]
|
||||
{
|
||||
(string?)cxInfo.DriverData.Element(CX.ProviderName),
|
||||
(string?)cxInfo.DriverData.Element(CX.ProviderPath),
|
||||
cxInfo.DatabaseInfo.CustomCxString,
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetAssembliesToAdd(IConnectionInfo cxInfo)
|
||||
{
|
||||
yield return typeof(DataConnection). Assembly.Location;
|
||||
yield return typeof(LINQPadDataConnection).Assembly.Location;
|
||||
|
||||
var providerName = (string?)cxInfo.DriverData.Element(CX.ProviderName);
|
||||
var providerPath = (string?)cxInfo.DriverData.Element(CX.ProviderPath);
|
||||
|
||||
foreach (var location in ProviderHelper.GetProvider(providerName, providerPath).GetAssemblyLocation(cxInfo.DatabaseInfo.CustomCxString))
|
||||
{
|
||||
yield return location;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetNamespacesToAdd(IConnectionInfo cxInfo)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
"LinqToDB",
|
||||
"LinqToDB.Data",
|
||||
"LinqToDB.Mapping"
|
||||
};
|
||||
}
|
||||
|
||||
public override void ClearConnectionPools(IConnectionInfo cxInfo) => DriverHelper.ClearConnectionPools(cxInfo);
|
||||
|
||||
MappingSchema? _mappingSchema;
|
||||
bool _useCustomFormatter;
|
||||
bool _optimizeJoins;
|
||||
|
||||
public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
|
||||
{
|
||||
var conn = (DataConnection)context;
|
||||
|
||||
_mappingSchema = conn.MappingSchema;
|
||||
_useCustomFormatter = cxInfo.DriverData.Element(CX.UseCustomFormatter)?.Value.ToLower() == "true";
|
||||
|
||||
_optimizeJoins = cxInfo.DriverData.Element(CX.OptimizeJoins) == null || cxInfo.DriverData.Element(CX.OptimizeJoins) ?.Value.ToLower() == "true";
|
||||
|
||||
Common.Configuration.Linq.OptimizeJoins = _optimizeJoins;
|
||||
|
||||
conn.OnTraceConnection = DriverHelper.GetOnTraceConnection(executionManager);
|
||||
}
|
||||
|
||||
public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager, object[] constructorArguments)
|
||||
{
|
||||
((DataConnection)context).Dispose();
|
||||
}
|
||||
|
||||
public override IDbConnection? GetIDbConnection(IConnectionInfo cxInfo)
|
||||
{
|
||||
using var conn = new LINQPadDataConnection(cxInfo);
|
||||
if (conn.ConnectionString == null)
|
||||
return null;
|
||||
|
||||
return conn.DataProvider.CreateConnection(conn.ConnectionString);
|
||||
}
|
||||
|
||||
public override void PreprocessObjectToWrite (ref object? objectToWrite, ObjectGraphInfo info)
|
||||
{
|
||||
objectToWrite = _useCustomFormatter
|
||||
? XmlFormatter.Format(_mappingSchema!, objectToWrite)
|
||||
: XmlFormatter.FormatValue(objectToWrite);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
internal sealed class LinqToDBLinqPadException : Exception
|
||||
{
|
||||
internal class LinqToDBLinqPadException : Exception
|
||||
public LinqToDBLinqPadException(string message) : base(message)
|
||||
{
|
||||
public LinqToDBLinqPadException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
public class LinqToDBStaticDriver : StaticDataContextDriver
|
||||
{
|
||||
public override string Name => "LINQ to DB (DataConnection)";
|
||||
public override string Author => DriverHelper.Author;
|
||||
|
||||
static LinqToDBStaticDriver()
|
||||
{
|
||||
DriverHelper.Init();
|
||||
}
|
||||
|
||||
public override string GetConnectionDescription(IConnectionInfo cxInfo) => DriverHelper.GetConnectionDescription(cxInfo);
|
||||
|
||||
[Obsolete("base method obsoleted")]
|
||||
public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) => DriverHelper.ShowConnectionDialog(cxInfo, isNewConnection, false);
|
||||
|
||||
public override List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type customType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new SchemaGenerator(cxInfo, customType).GetSchema().ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"{ex}\n{ex.StackTrace}", "Schema Build Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
|
||||
{
|
||||
var configuration = cxInfo.DriverData.Element(CX.CustomConfiguration)?.Value;
|
||||
|
||||
if (configuration != null)
|
||||
return new[] { new ParameterDescriptor("configuration", typeof(string).FullName) };
|
||||
|
||||
return base.GetContextConstructorParameters(cxInfo);
|
||||
}
|
||||
|
||||
public override object[] GetContextConstructorArguments(IConnectionInfo cxInfo)
|
||||
{
|
||||
TryLoadAppSettingsJson(cxInfo);
|
||||
|
||||
var configuration = cxInfo.DriverData.Element(CX.CustomConfiguration)?.Value;
|
||||
|
||||
if (configuration != null)
|
||||
return new object[] { configuration };
|
||||
|
||||
return base.GetContextConstructorArguments(cxInfo);
|
||||
}
|
||||
|
||||
public override void ClearConnectionPools(IConnectionInfo cxInfo) => DriverHelper.ClearConnectionPools(cxInfo);
|
||||
|
||||
public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
|
||||
{
|
||||
var optimizeJoins = cxInfo.DriverData.Element(CX.OptimizeJoins) == null || cxInfo.DriverData.Element(CX.OptimizeJoins) ?.Value.ToLower() == "true";
|
||||
|
||||
Common.Configuration.Linq.OptimizeJoins = optimizeJoins;
|
||||
|
||||
dynamic ctx = context;
|
||||
|
||||
if (Extensions.HasProperty(ctx, nameof(DataConnection.OnTraceConnection)))
|
||||
{
|
||||
ctx.OnTraceConnection = DriverHelper.GetOnTraceConnection(executionManager);
|
||||
DataConnection.TurnTraceSwitchOn();
|
||||
}
|
||||
}
|
||||
|
||||
public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager,
|
||||
object[] constructorArguments)
|
||||
{
|
||||
dynamic ctx = context;
|
||||
ctx.Dispose();
|
||||
}
|
||||
|
||||
private void TryLoadAppSettingsJson(IConnectionInfo cxInfo)
|
||||
{
|
||||
#if NETCORE
|
||||
if (cxInfo.AppConfigPath?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) == true)
|
||||
DataConnection.DefaultSettings = AppJsonConfig.Load(cxInfo.AppConfigPath!);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
||||
// https://raw.githubusercontent.com/dotnet/runtime/master/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
#if !NETCORE
|
||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
||||
sealed class AllowNullAttribute : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
||||
sealed class DisallowNullAttribute : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||
sealed class MaybeNullAttribute : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||
sealed class NotNullAttribute : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
sealed class MaybeNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
||||
/// </param>
|
||||
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
|
||||
sealed class NotNullIfNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
||||
/// <param name="parameterName">
|
||||
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
||||
/// </param>
|
||||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
|
||||
|
||||
/// <summary>Gets the associated parameter name.</summary>
|
||||
public string ParameterName { get; }
|
||||
}
|
||||
|
||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
sealed class DoesNotReturnAttribute : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
sealed class DoesNotReturnIfAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
||||
/// <param name="parameterValue">
|
||||
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
||||
/// the associated parameter matches this value.
|
||||
/// </param>
|
||||
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
|
||||
|
||||
/// <summary>Gets the condition parameter value.</summary>
|
||||
public bool ParameterValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||
sealed class MemberNotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
/// <param name="member">
|
||||
/// The field or property member that is promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullWhenAttribute(bool returnValue, string member)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
Members = new[] { member };
|
||||
}
|
||||
|
||||
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
/// <param name="members">
|
||||
/// The list of field and property members that are promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
Members = members;
|
||||
}
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
|
||||
/// <summary>Gets field or property member names.</summary>
|
||||
public string[] Members { get; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||
sealed class MemberNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with a field or property member.</summary>
|
||||
/// <param name="member">
|
||||
/// The field or property member that is promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullAttribute(string member) => Members = new[] { member };
|
||||
|
||||
/// <summary>Initializes the attribute with the list of field and property members.</summary>
|
||||
/// <param name="members">
|
||||
/// The list of field and property members that are promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullAttribute(params string[] members)
|
||||
=> Members = members;
|
||||
|
||||
/// <summary>Gets field or property member names.</summary>
|
||||
public string[] Members { get; }
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
@echo off
|
||||
echo.
|
||||
echo Packing %2
|
||||
echo.
|
||||
@del linq2db.LINQPad.%2
|
||||
@del linq2db.LINQPad.%2.zip
|
||||
|
||||
IF %2 EQU lpx (
|
||||
rem xcopy /s /y ..\Redist\IBM\*.dll %1
|
||||
rem remove resource satellite assemblies
|
||||
rd /S /Q %1\cs
|
||||
rd /S /Q %1\de
|
||||
rd /S /Q %1\es
|
||||
rd /S /Q %1\fr
|
||||
rd /S /Q %1\it
|
||||
rd /S /Q %1\ja
|
||||
rd /S /Q %1\ko
|
||||
rd /S /Q %1\pl
|
||||
rd /S /Q %1\pt-BR
|
||||
rd /S /Q %1\ru
|
||||
rd /S /Q %1\tr
|
||||
rd /S /Q %1\zh-Hans
|
||||
rd /S /Q %1\zh-Hant
|
||||
del /Q %1\*.pdb
|
||||
"C:\Program Files\7-Zip\7z.exe" -r a linq2db.LINQPad.%2.zip %1\*.*
|
||||
)
|
||||
IF %2 EQU lpx6 ("C:\Program Files\7-Zip\7z.exe" a linq2db.LINQPad.%2.zip %1\linq2db.LINQPad.dll %1\Connection.png %1\FailedConnection.png %1\linq2db.LINQPad.deps.json)
|
||||
|
||||
|
||||
ren linq2db.LINQPad.%2.zip linq2db.LINQPad.%2
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
using System;
|
||||
|
||||
using CodeJam.Strings;
|
||||
using Humanizer;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
static class Pluralization
|
||||
{
|
||||
static string GetLastWord(string str)
|
||||
{
|
||||
if (str.IsNullOrWhiteSpace())
|
||||
return string.Empty;
|
||||
|
||||
var i = str.Length - 1;
|
||||
var isLower = char.IsLower(str[i]);
|
||||
|
||||
while (i > 0 && char.IsLower(str[i - 1]) == isLower)
|
||||
i--;
|
||||
|
||||
return str.Substring(isLower && i > 0 ? i - 1 : i);
|
||||
}
|
||||
|
||||
public static string ToPlural(string str)
|
||||
{
|
||||
var word = GetLastWord(str);
|
||||
var newWord = word.Pluralize();
|
||||
|
||||
if (!string.Equals(word, newWord, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (char.IsUpper(word[0]))
|
||||
newWord = char.ToUpper(newWord[0]) + newWord.Substring(1, newWord.Length - 1);
|
||||
|
||||
#if NETCORE
|
||||
return word == str ? newWord : string.Concat(str.AsSpan(0, str.Length - word.Length), newWord);
|
||||
#else
|
||||
return word == str ? newWord : str.Substring(0, str.Length - word.Length) + newWord;
|
||||
#endif
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ToSingular(string str)
|
||||
{
|
||||
var word = GetLastWord(str);
|
||||
var newWord = word.Singularize();
|
||||
|
||||
if (!string.Equals(word, newWord, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (char.IsUpper(word[0]))
|
||||
newWord = char.ToUpper(newWord[0]) + newWord.Substring(1, newWord.Length - 1);
|
||||
|
||||
#if NETCORE
|
||||
return word == str ? newWord : string.Concat(str.AsSpan(0, str.Length - word.Length), newWord);
|
||||
#else
|
||||
return word == str ? newWord : str.Substring(0, str.Length - word.Length) + newWord;
|
||||
#endif
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
[module: SkipLocalsInit]
|
||||
|
|
|
@ -1,266 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LinqToDB.Configuration;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider;
|
||||
#if NETCORE
|
||||
using System.Data.Common;
|
||||
#endif
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
internal class ProviderHelper
|
||||
{
|
||||
static readonly Dictionary<string, DynamicProviderRecord> _dynamicProviders = new ();
|
||||
|
||||
public static DynamicProviderRecord[] DynamicProviders => _dynamicProviders.Values.ToArray();
|
||||
|
||||
static readonly Dictionary<string, LoadProviderInfo> LoadedProviders = new ();
|
||||
|
||||
static void AddDataProvider(DynamicProviderRecord providerInfo)
|
||||
{
|
||||
if (providerInfo == null) throw new ArgumentNullException(nameof(providerInfo));
|
||||
_dynamicProviders.Add(providerInfo.Name, providerInfo);
|
||||
}
|
||||
|
||||
static ProviderHelper()
|
||||
{
|
||||
InitializeDataProviders();
|
||||
}
|
||||
|
||||
static void InitializeDataProviders()
|
||||
{
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.Access , ProviderName.Access , "Microsoft Access (OleDb)" , "System.Data.OleDb.OleDbConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.AccessOdbc , ProviderName.AccessOdbc , "Microsoft Access (ODBC)" , "System.Data.Odbc.OdbcConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.Firebird , ProviderName.Firebird , "Firebird" , "FirebirdSql.Data.FirebirdClient.FbConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.MySqlConnector, ProviderName.MySqlConnector, "MySql" , "MySql.Data.MySqlClient.MySqlConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.PostgreSQL , ProviderName.PostgreSQL , "PostgreSQL" , "Npgsql.NpgsqlConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SybaseManaged , ProviderName.SybaseManaged , "SAP/Sybase ASE" , "AdoNetCore.AseClient.AseConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SQLiteClassic , ProviderName.SQLiteClassic , "SQLite" , "System.Data.SQLite.SQLiteConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SqlCe , ProviderName.SqlCe , "Microsoft SQL Server Compact" , "System.Data.SqlServerCe.SqlCeConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.DB2LUW , ProviderName.DB2LUW , "DB2 for Linux, UNIX and Windows", "IBM.Data.DB2.DB2Connection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.DB2zOS , ProviderName.DB2zOS , "DB2 for z/OS" , "IBM.Data.DB2.DB2Connection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.InformixDB2 , ProviderName.InformixDB2 , "Informix (IDS)" , "IBM.Data.DB2.DB2Connection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SapHanaNative , ProviderName.SapHanaNative , "SAP HANA (Native)" , "Sap.Data.Hana.HanaConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SapHanaOdbc , ProviderName.SapHanaOdbc , "SAP HANA (ODBC)" , "System.Data.Odbc.OdbcConnection"));
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.OracleManaged , ProviderName.OracleManaged , "Oracle (Managed)" , "Oracle.ManagedDataAccess.Client.OracleConnection"));
|
||||
|
||||
// we use System.Data.SqlClient to be able to use Microsoft.SqlServer.Types
|
||||
AddDataProvider(new DynamicProviderRecord(ProviderName.SqlServer , "System.Data.SqlClient" , "Microsoft SQL Server" , "System.Data.SqlClient.SqlConnection"));
|
||||
}
|
||||
|
||||
public class DynamicProviderRecord
|
||||
{
|
||||
public string Name { get; }
|
||||
public string ProviderName { get; }
|
||||
public string Description { get; }
|
||||
public string ConnectionTypeName { get; }
|
||||
public IReadOnlyCollection<string> Libraries { get; }
|
||||
|
||||
public DynamicProviderRecord(string name, string providerName, string description, string connectionTypeName, params string[] providerLibraries)
|
||||
{
|
||||
Name = name;
|
||||
ProviderName = providerName;
|
||||
Description = description;
|
||||
ConnectionTypeName = connectionTypeName;
|
||||
Libraries = providerLibraries ?? Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public class LoadProviderInfo
|
||||
{
|
||||
public LoadProviderInfo(DynamicProviderRecord provider)
|
||||
{
|
||||
Provider = provider;
|
||||
}
|
||||
|
||||
public DynamicProviderRecord Provider { get; }
|
||||
public bool IsLoaded { get; private set; }
|
||||
public Assembly[]? LoadedAssemblies { get; private set; }
|
||||
public Exception? LoadException { get; private set; }
|
||||
|
||||
public void Load(string connectionString)
|
||||
{
|
||||
if (IsLoaded)
|
||||
return;
|
||||
|
||||
if (LoadException != null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IEnumerable<Assembly> loadLibraries = Provider.Libraries
|
||||
.Select(l =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return Assembly.Load(Path.GetFileNameWithoutExtension(l));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
)
|
||||
.Where(l => l != null)!;
|
||||
|
||||
var providerLibraries = loadLibraries
|
||||
.Concat(new[] { typeof(DataConnection).Assembly })
|
||||
.ToArray();
|
||||
|
||||
var provider = ProviderHelper.GetDataProvider(Provider.ProviderName, connectionString);
|
||||
|
||||
var connectionAssemblies = new List<Assembly>() { provider.GetType().Assembly };
|
||||
try
|
||||
{
|
||||
using var connection = provider.CreateConnection(connectionString);
|
||||
connectionAssemblies.Add(connection.GetType().Assembly);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
LoadedAssemblies = connectionAssemblies
|
||||
.Concat(providerLibraries)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
IsLoaded = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoadException = e;
|
||||
IsLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAssemblyLocation(string connectionString)
|
||||
{
|
||||
Load(connectionString);
|
||||
|
||||
if (LoadedAssemblies != null)
|
||||
return LoadedAssemblies.Select(a => a.Location);
|
||||
|
||||
var directory = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location);
|
||||
|
||||
if (directory == null)
|
||||
return Provider.Libraries;
|
||||
|
||||
return Provider.Libraries.Select(l => Path.Combine(directory, l));
|
||||
}
|
||||
|
||||
public string? GetConnectionNamespace()
|
||||
{
|
||||
var ns = Provider?.ConnectionTypeName?.Split(',').Last().TrimStart();
|
||||
if (!string.IsNullOrEmpty(ns))
|
||||
return ns!;
|
||||
|
||||
var path = Provider?.ConnectionTypeName?.Split('.').ToArray();
|
||||
if (path?.Length > 1)
|
||||
{
|
||||
return string.Join(".", path.Take(path.Length - 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IDataProvider GetDataProvider(string connectionString)
|
||||
{
|
||||
Load(connectionString);
|
||||
return ProviderHelper.GetDataProvider(Provider.ProviderName, connectionString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static LoadProviderInfo GetProvider(string? providerName, string? providerPath)
|
||||
{
|
||||
#if NETCORE
|
||||
RegisterProviderFactory(providerName, providerPath);
|
||||
#endif
|
||||
|
||||
if (providerName == null)
|
||||
throw new ArgumentNullException(nameof(providerName), $"Provider name missing");
|
||||
|
||||
if (LoadedProviders.TryGetValue(providerName, out var info))
|
||||
return info;
|
||||
|
||||
if (!_dynamicProviders.TryGetValue(providerName, out var providerRecord))
|
||||
throw new ArgumentException($"Unknown provider: {providerName}");
|
||||
|
||||
info = new LoadProviderInfo(providerRecord);
|
||||
LoadedProviders.Add(providerName, info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static IDataProvider GetDataProvider(string providerName, string connectionString)
|
||||
{
|
||||
var provider = DataConnection.GetDataProvider(providerName, connectionString);
|
||||
if (provider == null)
|
||||
throw new LinqToDBLinqPadException($"Can not activate provider \"{providerName}\"");
|
||||
return provider;
|
||||
}
|
||||
|
||||
#if NETCORE
|
||||
static void RegisterProviderFactory(string? providerName, string? providerPath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(providerPath))
|
||||
{
|
||||
switch (providerName)
|
||||
{
|
||||
case ProviderName.SqlCe:
|
||||
RegisterSqlCEFactory(providerPath);
|
||||
break;
|
||||
case ProviderName.SapHanaNative:
|
||||
RegisterSapHanaFactory(providerPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _sqlceLoaded;
|
||||
static void RegisterSqlCEFactory(string providerPath)
|
||||
{
|
||||
if (_sqlceLoaded)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.LoadFrom(providerPath);
|
||||
DbProviderFactories.RegisterFactory("System.Data.SqlServerCe.4.0", assembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory")!);
|
||||
_sqlceLoaded = true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static bool _sapHanaLoaded;
|
||||
static void RegisterSapHanaFactory(string providerPath)
|
||||
{
|
||||
if (_sapHanaLoaded)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var targetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, Path.GetFileName(providerPath));
|
||||
if (File.Exists(providerPath))
|
||||
{
|
||||
// original path contains spaces which breaks broken native dlls discovery logic in SAP provider
|
||||
// (at least SPS04 040)
|
||||
// if you run tests from path with spaces - it will not help you
|
||||
File.Copy(providerPath, targetPath, true);
|
||||
var sapHanaAssembly = Assembly.LoadFrom(targetPath);
|
||||
DbProviderFactories.RegisterFactory("Sap.Data.Hana", sapHanaAssembly.GetType("Sap.Data.Hana.HanaFactory")!);
|
||||
_sapHanaLoaded = true;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
using LinqToDB.SchemaProvider;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
partial class SchemaAndCodeGenerator
|
||||
{
|
||||
private void PreprocessPostgreSQLSchema()
|
||||
{
|
||||
PgSqlFixTableFunctions();
|
||||
PgSqlFixRecordResultFunctions();
|
||||
PgSqlFixVoidFunctions();
|
||||
PgSqlFixFunctionNames();
|
||||
}
|
||||
|
||||
void PgSqlFixRecordResultFunctions()
|
||||
{
|
||||
var mappings = new List<(string, IList<string>)>();
|
||||
|
||||
foreach (var proc in _schema!.Procedures
|
||||
.Where(p => p.IsFunction && !p.IsAggregateFunction && !p.IsTableFunction && p.Parameters.Any(pr => pr.IsOut)))
|
||||
{
|
||||
if (proc.Parameters.Count(pr => pr.IsOut) > 1)
|
||||
{
|
||||
var result = new TableSchema()
|
||||
{
|
||||
TypeName = SchemaProviderBase.ToValidName(proc.ProcedureName + "Result"),
|
||||
Columns = new List<ColumnSchema>(),
|
||||
IsProcedureResult = true,
|
||||
ForeignKeys = new List<ForeignKeySchema>()
|
||||
};
|
||||
|
||||
_schema.Tables.Add(result);
|
||||
proc.ResultTable = result;
|
||||
|
||||
proc.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
IsResult = true,
|
||||
ParameterType = result.TypeName
|
||||
});
|
||||
|
||||
var resultMappings = new List<string>();
|
||||
mappings.Add((result.TypeName, resultMappings));
|
||||
|
||||
foreach (var outParam in proc.Parameters.Where(_ => _.IsOut))
|
||||
{
|
||||
result.Columns.Add(new ColumnSchema()
|
||||
{
|
||||
MemberType = outParam.ParameterType,
|
||||
MemberName = outParam.ParameterName
|
||||
});
|
||||
|
||||
resultMappings.Add($"{outParam.ParameterName} = ({outParam.ParameterType})tuple[{resultMappings.Count}]");
|
||||
|
||||
if (outParam.IsIn)
|
||||
outParam.IsOut = false;
|
||||
}
|
||||
|
||||
proc.Parameters = proc.Parameters.Where(_ => !_.IsOut).ToList();
|
||||
|
||||
}
|
||||
else // one parameter
|
||||
{
|
||||
var param = proc.Parameters.Single(_ => _.IsOut);
|
||||
proc.Parameters.Remove(param);
|
||||
proc.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
IsResult = true,
|
||||
ParameterType = param.ParameterType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mappings.Count > 0)
|
||||
{
|
||||
Code
|
||||
.AppendLine(" protected override void InitMappingSchema()")
|
||||
.AppendLine(" {");
|
||||
|
||||
foreach (var (typeName, valueMappings) in mappings)
|
||||
{
|
||||
Code.AppendLine($" MappingSchema.SetConvertExpression<object[], {typeName}>(tuple => new {typeName}() {{ {string.Join(", ", valueMappings)} }});");
|
||||
}
|
||||
|
||||
Code
|
||||
.AppendLine(" }");
|
||||
}
|
||||
}
|
||||
|
||||
void PgSqlFixFunctionNames()
|
||||
{
|
||||
foreach (var proc in _schema!.Procedures)
|
||||
{
|
||||
if (proc.ProcedureName.Any(char.IsUpper))
|
||||
proc.ProcedureName = "\"" + proc.ProcedureName + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
void PgSqlFixTableFunctions()
|
||||
{
|
||||
foreach (var proc in _schema!.Procedures
|
||||
.Where(p => p.IsTableFunction && p.Parameters.Any(pr => pr.IsOut)))
|
||||
{
|
||||
proc.Parameters = proc.Parameters.Where(pr => !pr.IsOut).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
void PgSqlFixVoidFunctions()
|
||||
{
|
||||
// generated functions should return object for void-typed functions
|
||||
foreach (var proc in _schema!.Procedures
|
||||
.Where(p => p.IsFunction && !p.IsTableFunction && !p.Parameters.Any(pr => pr.IsResult)))
|
||||
{
|
||||
proc.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
IsResult = true,
|
||||
ParameterType = "object",
|
||||
SystemType = typeof(object)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,925 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CodeJam.Strings;
|
||||
using CodeJam.Xml;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider;
|
||||
using LinqToDB.SchemaProvider;
|
||||
using LinqToDB.SqlProvider;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
partial class SchemaAndCodeGenerator
|
||||
{
|
||||
public SchemaAndCodeGenerator(IConnectionInfo cxInfo)
|
||||
{
|
||||
_cxInfo = cxInfo;
|
||||
|
||||
UseProviderSpecificTypes = ((string?)_cxInfo.DriverData.Element(CX.UseProviderSpecificTypes))?.ToLower() == "true";
|
||||
NormalizeJoins = ((string?)_cxInfo.DriverData.Element(CX.NormalizeNames)) ?.ToLower() == "true";
|
||||
ProviderName = (string?)_cxInfo.DriverData.Element(CX.ProviderName);
|
||||
ProviderPath = (string?)_cxInfo.DriverData.Element(CX.ProviderPath);
|
||||
CommandTimeout = _cxInfo.DriverData.ElementValueOrDefault(CX.CommandTimeout, str => str.ToInt32() ?? 0, 0);
|
||||
}
|
||||
|
||||
public readonly int CommandTimeout;
|
||||
public readonly bool UseProviderSpecificTypes;
|
||||
public readonly string? ProviderName;
|
||||
public readonly string? ProviderPath;
|
||||
public readonly bool NormalizeJoins;
|
||||
public readonly List<string> References = new ();
|
||||
|
||||
readonly IConnectionInfo _cxInfo;
|
||||
readonly StringBuilder _classCode = new ();
|
||||
|
||||
DatabaseSchema? _schema;
|
||||
IDataProvider? _dataProvider;
|
||||
ISqlBuilder? _sqlBuilder;
|
||||
|
||||
public readonly StringBuilder Code = new ();
|
||||
|
||||
private readonly HashSet<string> _existingMemberNames = new (StringComparer.InvariantCulture);
|
||||
|
||||
public IEnumerable<ExplorerItem> GetItemsAndCode(string nameSpace, string typeName)
|
||||
{
|
||||
typeName = ConvertToCompilable(typeName, false);
|
||||
|
||||
var connectionString = _cxInfo.DatabaseInfo.CustomCxString;
|
||||
var providerInfo = ProviderHelper.GetProvider(ProviderName, ProviderPath);
|
||||
var provider = providerInfo.GetDataProvider(connectionString);
|
||||
|
||||
using (var db = new DataConnection(provider, connectionString))
|
||||
{
|
||||
db.CommandTimeout = CommandTimeout;
|
||||
|
||||
_dataProvider = db.DataProvider;
|
||||
_sqlBuilder = _dataProvider.CreateSqlBuilder(_dataProvider.MappingSchema);
|
||||
|
||||
var options = new GetSchemaOptions();
|
||||
|
||||
var includeSchemas = (string?)_cxInfo.DriverData.Element(CX.IncludeSchemas);
|
||||
if (includeSchemas != null) options.IncludedSchemas = includeSchemas.Split(',', ';');
|
||||
|
||||
var excludeSchemas = (string?)_cxInfo.DriverData.Element(CX.ExcludeSchemas);
|
||||
if (excludeSchemas != null) options.ExcludedSchemas = excludeSchemas.Split(',', ';');
|
||||
|
||||
var includeCatalogs = (string?)_cxInfo.DriverData.Element(CX.IncludeCatalogs);
|
||||
if (includeCatalogs != null) options.IncludedCatalogs = includeCatalogs.Split(',', ';');
|
||||
|
||||
var excludeCatalogs = (string?)_cxInfo.DriverData.Element(CX.ExcludeCatalogs);
|
||||
if (excludeCatalogs != null) options.ExcludedCatalogs = excludeCatalogs.Split(',', ';');
|
||||
|
||||
options.GetProcedures = (string?)_cxInfo.DriverData.Element(CX.ExcludeRoutines) == "false";
|
||||
options.GetForeignKeys = (string?)_cxInfo.DriverData.Element(CX.ExcludeFKs) != "true";
|
||||
|
||||
_schema = _dataProvider.GetSchemaProvider().GetSchema(db, options);
|
||||
|
||||
ConvertSchema(typeName);
|
||||
}
|
||||
|
||||
Code
|
||||
.AppendLine("using System;")
|
||||
.AppendLine("using System.Collections;")
|
||||
.AppendLine("using System.Collections.Generic;")
|
||||
.AppendLine("using System.Data;")
|
||||
.AppendLine("using System.Reflection;")
|
||||
.AppendLine("using System.Linq;")
|
||||
.AppendLine("using LinqToDB;")
|
||||
.AppendLine("using LinqToDB.Common;")
|
||||
.AppendLine("using LinqToDB.Data;")
|
||||
.AppendLine("using LinqToDB.Mapping;")
|
||||
.AppendLine("using System.Net;")
|
||||
.AppendLine("using System.Numerics;")
|
||||
.AppendLine("using System.Net.NetworkInformation;")
|
||||
.AppendLine("using Microsoft.SqlServer.Types;")
|
||||
;
|
||||
|
||||
// TODO: temporary, remove after update to linq2db 3.4.0
|
||||
if (ProviderName == LinqToDB.ProviderName.Firebird)
|
||||
Code.AppendLine("using FirebirdSql.Data.Types;");
|
||||
|
||||
if (_schema.Procedures.Any(_ => _.IsAggregateFunction))
|
||||
Code
|
||||
.AppendLine("using System.Linq.Expressions;")
|
||||
;
|
||||
|
||||
if (_schema.ProviderSpecificTypeNamespace.NotNullNorWhiteSpace())
|
||||
Code.AppendLine($"using {_schema.ProviderSpecificTypeNamespace};");
|
||||
|
||||
References.AddRange(providerInfo.GetAssemblyLocation(connectionString));
|
||||
|
||||
Code
|
||||
.AppendLine($"namespace {nameSpace}")
|
||||
.AppendLine( "{")
|
||||
.AppendLine($" public class {typeName} : LinqToDB.LINQPad.LINQPadDataConnection")
|
||||
.AppendLine( " {")
|
||||
.AppendLine($" public {typeName}(string provider, string providerPath, string connectionString)")
|
||||
.AppendLine(" : base(provider, providerPath, connectionString)")
|
||||
.AppendLine( " {")
|
||||
.AppendLine($" CommandTimeout = {CommandTimeout};")
|
||||
.AppendLine( " }")
|
||||
.AppendLine($" public {typeName}()")
|
||||
.AppendLine($" : base({CSharpTools.ToStringLiteral(ProviderName)}, {CSharpTools.ToStringLiteral(ProviderPath)}, {CSharpTools.ToStringLiteral(connectionString)})")
|
||||
.AppendLine( " {")
|
||||
.AppendLine($" CommandTimeout = {CommandTimeout};")
|
||||
.AppendLine( " }")
|
||||
;
|
||||
|
||||
if (ProviderName == LinqToDB.ProviderName.PostgreSQL)
|
||||
PreprocessPostgreSQLSchema();
|
||||
|
||||
var schemas =
|
||||
(
|
||||
from t in
|
||||
(
|
||||
from t in _schema.Tables.Where(t => !t.IsProcedureResult && t.Columns.Count > 0)
|
||||
select new { t.IsDefaultSchema, SchemaName = t.SchemaName.IsNullOrEmpty() ? null : t.SchemaName, Table = t, Procedure = (ProcedureSchema?)null }
|
||||
)
|
||||
.Union
|
||||
(
|
||||
from p in _schema.Procedures
|
||||
select new { p.IsDefaultSchema, SchemaName = p.SchemaName.IsNullOrEmpty() ? null : p.SchemaName, Table = (TableSchema?)null, Procedure = p }
|
||||
)
|
||||
group t by new { t.IsDefaultSchema, t.SchemaName } into gr
|
||||
orderby !gr.Key.IsDefaultSchema, gr.Key.SchemaName
|
||||
select new
|
||||
{
|
||||
gr.Key,
|
||||
Tables = gr.Where(t => t.Table != null).Select(t => t.Table). ToList(),
|
||||
Procedures = gr.Where(t => t.Procedure != null).Select(t => t.Procedure).ToList(),
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
|
||||
var nonDefaultSchemas = new Dictionary<string, ExplorerItem>();
|
||||
|
||||
var hasDefaultSchema = false;
|
||||
string? defaultSchemaName = null;
|
||||
|
||||
foreach (var s in schemas)
|
||||
{
|
||||
if (s.Key.IsDefaultSchema)
|
||||
{
|
||||
hasDefaultSchema = true;
|
||||
defaultSchemaName ??= s.Key.SchemaName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = s.Key.SchemaName.IsNullOrEmpty() ? "empty" : s.Key.SchemaName;
|
||||
nonDefaultSchemas.Add(name, new ExplorerItem(name, ExplorerItemKind.Schema, ExplorerIcon.Schema) { Children = new List<ExplorerItem>() });
|
||||
}
|
||||
}
|
||||
|
||||
var useSchemaNode = nonDefaultSchemas.Count > 1 || nonDefaultSchemas.Count == 1 && hasDefaultSchema;
|
||||
var defaultSchema = useSchemaNode ? new ExplorerItem(defaultSchemaName ?? "(default)", ExplorerItemKind.Schema, ExplorerIcon.Schema) { Children = new List<ExplorerItem>() } : null;
|
||||
|
||||
foreach (var s in schemas)
|
||||
{
|
||||
var items = new List<ExplorerItem>();
|
||||
|
||||
if (s.Tables.Any(t => !t.IsView))
|
||||
items.Add(GetTables("Tables", ExplorerIcon.Table, s.Tables.Where(t => !t.IsView)));
|
||||
|
||||
if (s.Tables.Any(t => t.IsView))
|
||||
items.Add(GetTables("Views", ExplorerIcon.View, s.Tables.Where(t => t.IsView)));
|
||||
|
||||
if (!_cxInfo.DynamicSchemaOptions.ExcludeRoutines && s.Procedures.Any(p => p.IsLoaded && !p.IsFunction))
|
||||
items.Add(GetProcedures(
|
||||
"Stored Procedures",
|
||||
ExplorerIcon.StoredProc,
|
||||
s.Procedures.Where(p => p.IsLoaded && !p.IsFunction).ToList()));
|
||||
|
||||
if (s.Procedures.Any(p => p.IsLoaded && p.IsTableFunction))
|
||||
items.Add(GetProcedures(
|
||||
"Table Functions",
|
||||
ExplorerIcon.TableFunction,
|
||||
s.Procedures.Where(p => p.IsLoaded && p.IsTableFunction).ToList()));
|
||||
|
||||
if (s.Procedures.Any(p => p.IsFunction && !p.IsTableFunction))
|
||||
items.Add(GetProcedures(
|
||||
"Scalar Functions",
|
||||
ExplorerIcon.ScalarFunction,
|
||||
s.Procedures.Where(p => p.IsFunction && !p.IsTableFunction).ToList()));
|
||||
|
||||
if (!useSchemaNode)
|
||||
{
|
||||
foreach (var item in items)
|
||||
yield return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.Key.IsDefaultSchema)
|
||||
defaultSchema!.Children.AddRange(items);
|
||||
else
|
||||
nonDefaultSchemas[s.Key.SchemaName ?? "empty"].Children.AddRange(items);
|
||||
}
|
||||
}
|
||||
|
||||
if (useSchemaNode)
|
||||
{
|
||||
if (defaultSchema != null)
|
||||
yield return defaultSchema;
|
||||
|
||||
foreach (var schemaNode in nonDefaultSchemas.Values)
|
||||
yield return schemaNode;
|
||||
}
|
||||
|
||||
Code
|
||||
.AppendLine(" }")
|
||||
.AppendLine(_classCode.ToString())
|
||||
.AppendLine("}")
|
||||
;
|
||||
}
|
||||
|
||||
void CodeProcedure(StringBuilder code, ProcedureSchema p, string sprocSqlName)
|
||||
{
|
||||
code.AppendLine();
|
||||
|
||||
var spName = CSharpTools.ToStringLiteral(sprocSqlName);
|
||||
|
||||
if (p.IsTableFunction)
|
||||
{
|
||||
code.Append($" [Sql.TableFunction(Name={CSharpTools.ToStringLiteral(p.ProcedureName)}");
|
||||
|
||||
if (p.PackageName != null)
|
||||
code.Append($", Package={CSharpTools.ToStringLiteral(p.PackageName)})");
|
||||
if (p.SchemaName != null)
|
||||
code.Append($", Schema={CSharpTools.ToStringLiteral(p.SchemaName)})");
|
||||
|
||||
code
|
||||
.AppendLine("]")
|
||||
.Append($" public ITable<{p.ResultTable?.TypeName}>")
|
||||
;
|
||||
}
|
||||
else if (p.IsAggregateFunction)
|
||||
{
|
||||
var inputs = p.Parameters.Where(pr => !pr.IsResult).ToArray();
|
||||
p.Parameters.RemoveAll(parameter => !parameter.IsResult);
|
||||
|
||||
p.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
ParameterType = "IEnumerable<TSource>",
|
||||
ParameterName = "src"
|
||||
});
|
||||
|
||||
foreach (var input in inputs.Where(pr => !pr.IsResult))
|
||||
p.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
ParameterType = $"Expression<Func<TSource, {input.ParameterType}>>",
|
||||
ParameterName = $"{input.ParameterName}"
|
||||
});
|
||||
|
||||
p.MemberName += "<TSource>";
|
||||
|
||||
code
|
||||
.Append($" [Sql.Function(Name={spName}, ServerSideOnly=true, IsAggregate = true")
|
||||
.Append(inputs.Length > 0 ? $", ArgIndices = new[] {{ {string.Join(", ", Enumerable.Range(0, inputs.Length))} }}" : string.Empty)
|
||||
.AppendLine(")]")
|
||||
.Append($" public static {p.Parameters.Single(pr => pr.IsResult).ParameterType}");
|
||||
}
|
||||
else if (p.IsFunction)
|
||||
{
|
||||
code
|
||||
.AppendLine($" [Sql.Function(Name={spName}, ServerSideOnly=true)]")
|
||||
.Append ($" public static {p.Parameters.Single(pr => pr.IsResult).ParameterType}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = p.ResultTable == null ? "int" : $"IEnumerable<{p.ResultTable.TypeName}>";
|
||||
|
||||
code.Append($" public {type}");
|
||||
}
|
||||
|
||||
code
|
||||
.Append($" {p.MemberName}(")
|
||||
.Append(p.Parameters
|
||||
.Where (pr => !pr.IsResult || !p.IsFunction)
|
||||
.Select(pr => $"{(pr.IsOut || pr.IsResult ? pr.IsIn ? "ref " : "out " : "")}{pr.ParameterType} {pr.ParameterName}")
|
||||
.Join(", "))
|
||||
.AppendLine(")")
|
||||
.AppendLine(" {")
|
||||
;
|
||||
|
||||
if (p.IsTableFunction)
|
||||
{
|
||||
code
|
||||
.Append ($" return this.GetTable<{p.ResultTable?.TypeName}>(this, (MethodInfo)MethodBase.GetCurrentMethod()")
|
||||
.AppendLine(p.Parameters.Count == 0 ? ");" : ",")
|
||||
;
|
||||
|
||||
for (var i = 0; i < p.Parameters.Count; i++)
|
||||
code.AppendLine($" {p.Parameters[i].ParameterName}{(i + 1 == p.Parameters.Count ? ");" : ",")}");
|
||||
}
|
||||
else if (p.IsFunction)
|
||||
{
|
||||
// aggregate and scalar functions branch
|
||||
code.AppendLine(" throw new InvalidOperationException();");
|
||||
}
|
||||
else
|
||||
{
|
||||
var inputParameters = p.Parameters.Where(pp => pp.IsIn) .ToList();
|
||||
var outputParameters = p.Parameters.Where(pp => pp.IsOut || pp.IsResult) .ToList();
|
||||
var inOrOutputParameters = p.Parameters.Where(pp => pp.IsIn || pp.IsOut || pp.IsResult).ToList();
|
||||
|
||||
if (ProviderName == LinqToDB.ProviderName.AccessOdbc)
|
||||
{
|
||||
// Access ODBC has special CALL syntax
|
||||
// also name shouldn't be escaped with []
|
||||
var paramTokens = string.Join(", ", Enumerable.Range(0, inputParameters.Count).Select(_ => "?"));
|
||||
spName = CSharpTools.ToStringLiteral($"{{ CALL {p.ProcedureName}({paramTokens}) }}");
|
||||
}
|
||||
|
||||
var retName = "__ret__";
|
||||
var retNo = 0;
|
||||
|
||||
while (p.Parameters.Any(pp => pp.ParameterName == retName))
|
||||
retName = "__ret__" + ++retNo;
|
||||
|
||||
var hasOut = outputParameters.Any(pr => pr.IsOut || pr.IsResult);
|
||||
var prefix = hasOut ? $"var {retName} =" : "return";
|
||||
|
||||
var cnt = 0;
|
||||
var parametersVarName = "parameters";
|
||||
while (p.Parameters.Where(par => !par.IsResult || !p.IsFunction).Any(par => par.ParameterName == parametersVarName))
|
||||
parametersVarName = string.Format("parameters{0}", cnt++);
|
||||
|
||||
if (inOrOutputParameters.Count > 0)
|
||||
{
|
||||
code.AppendLine($" var {parametersVarName} = new[]");
|
||||
code.AppendLine(" {");
|
||||
|
||||
for (var i = 0; i < inOrOutputParameters.Count; i++)
|
||||
{
|
||||
var pr = inOrOutputParameters[i];
|
||||
var hasInputValue = pr.IsIn || (pr.IsOut && pr.IsResult);
|
||||
|
||||
var extraInitializers = new List<(string, string)>();
|
||||
extraInitializers.Add(("DbType", CSharpTools.ToStringLiteral(pr.SchemaType)));
|
||||
|
||||
if (pr.IsOut || pr.IsResult)
|
||||
extraInitializers.Add(("Direction", pr.IsIn ? "ParameterDirection.InputOutput" : pr.IsResult ? "ParameterDirection.ReturnValue" : "ParameterDirection.Output"));
|
||||
|
||||
if (pr.Size != null && pr.Size.Value != 0 && pr.Size.Value >= int.MinValue && pr.Size.Value <= int.MaxValue)
|
||||
extraInitializers.Add(("Size", pr.Size.Value.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var endLine = i < inOrOutputParameters.Count - 1 && extraInitializers.Count == 0 ? "," : "";
|
||||
|
||||
if (hasInputValue)
|
||||
{
|
||||
code.AppendLine(string.Format(
|
||||
"\tnew DataParameter({0}, {1}, {2}){3}",
|
||||
CSharpTools.ToStringLiteral(pr.SchemaName),
|
||||
pr.ParameterName,
|
||||
"LinqToDB.DataType." + pr.DataType,
|
||||
endLine));
|
||||
}
|
||||
else
|
||||
{
|
||||
code.AppendLine(string.Format(
|
||||
"\tnew DataParameter({0}, null, {1}){2}",
|
||||
CSharpTools.ToStringLiteral(pr.SchemaName),
|
||||
"LinqToDB.DataType." + pr.DataType,
|
||||
endLine));
|
||||
}
|
||||
|
||||
if (extraInitializers.Count > 0)
|
||||
{
|
||||
code.AppendLine("\t{");
|
||||
|
||||
for (var j = 0; j < extraInitializers.Count; j++)
|
||||
code.AppendLine(string.Format(
|
||||
"\t\t{0} = {1},",
|
||||
extraInitializers[j].Item1,
|
||||
extraInitializers[j].Item2));
|
||||
|
||||
code.AppendLine("\t},");
|
||||
}
|
||||
}
|
||||
|
||||
code.AppendLine("};");
|
||||
code.AppendLine("");
|
||||
}
|
||||
|
||||
// we need to call ToList(), because otherwise output parameters will not be updated
|
||||
// with values. See https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/ms971497(v=msdn.10)#capturing-the-gazoutas
|
||||
var terminator = outputParameters.Count > 0 && p.ResultTable != null ? ").ToList();" : ");";
|
||||
|
||||
if (inOrOutputParameters.Count > 0)
|
||||
terminator = string.Format(", {0}{1}", parametersVarName, terminator);
|
||||
|
||||
if (p.ResultTable == null)
|
||||
{
|
||||
code.Append($" {prefix} this.ExecuteProc({spName}{terminator}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var hashSet = new HashSet<string>();
|
||||
var hasDuplicate = false;
|
||||
|
||||
foreach (var column in p.ResultTable.Columns)
|
||||
{
|
||||
if (hashSet.Contains(column.ColumnName))
|
||||
{
|
||||
hasDuplicate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
hashSet.Add(column.ColumnName);
|
||||
}
|
||||
|
||||
if (hasDuplicate)
|
||||
{
|
||||
code.AppendLine( " var ms = this.MappingSchema;");
|
||||
code.AppendLine($" {prefix} this.QueryProc(dataReader =>");
|
||||
code.AppendLine($" new {p.ResultTable.TypeName}");
|
||||
code.AppendLine( " {");
|
||||
|
||||
var n = 0;
|
||||
|
||||
foreach (var c in p.ResultTable.Columns)
|
||||
{
|
||||
code.AppendLine($" {c.MemberName} = Converter.ChangeTypeTo<{c.MemberType}>(dataReader.GetValue({n++}), ms),");
|
||||
}
|
||||
|
||||
code.AppendLine( " },");
|
||||
code.AppendLine($" {spName}{terminator}");
|
||||
}
|
||||
else
|
||||
{
|
||||
code.AppendLine($" {prefix} this.QueryProc<{p.ResultTable.TypeName}>({spName}{terminator}");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOut)
|
||||
{
|
||||
code.AppendLine();
|
||||
|
||||
foreach (var pr in p.Parameters.Where(_ => _.IsOut || _.IsResult))
|
||||
{
|
||||
var str = $" {pr.ParameterName} = Converter.ChangeTypeTo<{pr.ParameterType}>({parametersVarName}[{inOrOutputParameters.IndexOf(pr)}].Value);";
|
||||
code.AppendLine(str);
|
||||
}
|
||||
|
||||
code
|
||||
.AppendLine()
|
||||
.AppendLine($" return {retName};")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
code.AppendLine(" }");
|
||||
}
|
||||
|
||||
ExplorerItem GetColumnItem(ColumnSchema column)
|
||||
{
|
||||
var memberType = GetMemberType(column);
|
||||
var sqlName = _sqlBuilder!.ConvertInline(column.ColumnName ?? "unspecified", ConvertType.NameToQueryField);
|
||||
|
||||
return new ExplorerItem(
|
||||
column.MemberName,
|
||||
ExplorerItemKind.Property,
|
||||
column.IsPrimaryKey ? ExplorerIcon.Key : ExplorerIcon.Column)
|
||||
{
|
||||
Text = $"{column.MemberName} : {memberType}",
|
||||
ToolTipText = $"{sqlName} {column.ColumnType} {(column.IsNullable ? "NULL" : "NOT NULL")}{(column.IsIdentity ? " IDENTITY" : "")}",
|
||||
DragText = column.MemberName,
|
||||
SqlName = sqlName,
|
||||
SqlTypeDeclaration = $"{column.ColumnType} {(column.IsNullable ? "NULL" : "NOT NULL")}{(column.IsIdentity ? " IDENTITY" : "")}",
|
||||
};
|
||||
}
|
||||
|
||||
ExplorerItem GetProcedures(string header, ExplorerIcon icon, List<ProcedureSchema> procedures)
|
||||
{
|
||||
var results = new HashSet<TableSchema>();
|
||||
|
||||
var items = new ExplorerItem(header, ExplorerItemKind.Category, icon)
|
||||
{
|
||||
Children = procedures
|
||||
.Select(p =>
|
||||
{
|
||||
var sprocSqlName = _sqlBuilder!.BuildObjectName(
|
||||
new StringBuilder(),
|
||||
new SqlQuery.SqlObjectName(
|
||||
Name: p.ProcedureName,
|
||||
Schema: p.SchemaName),
|
||||
tableOptions: TableOptions.NotSet).ToString();
|
||||
|
||||
var memberName = p.MemberName;
|
||||
|
||||
if (p.IsFunction && !p.IsTableFunction)
|
||||
{
|
||||
var res = p.Parameters.FirstOrDefault(pr => pr.IsResult);
|
||||
|
||||
if (res != null)
|
||||
memberName += $" -> {res.ParameterType}";
|
||||
}
|
||||
|
||||
var ret = new ExplorerItem(memberName, ExplorerItemKind.QueryableObject, icon)
|
||||
{
|
||||
DragText = $"{p.MemberName}(" +
|
||||
p.Parameters
|
||||
.Where (pr => !pr.IsResult || !p.IsFunction)
|
||||
.Select(pr => $"{(pr.IsOut || pr.IsResult ? (pr.IsIn ? "ref " : "out ") : "")}{pr.ParameterName}")
|
||||
.Join(", ") +
|
||||
")",
|
||||
SqlName = sprocSqlName,
|
||||
IsEnumerable = p.ResultTable != null,
|
||||
Children = p.Parameters
|
||||
.Where (pr => !pr.IsResult || !p.IsFunction)
|
||||
.Select(pr =>
|
||||
new ExplorerItem(
|
||||
$"{pr.ParameterName} ({pr.ParameterType})",
|
||||
ExplorerItemKind.Parameter,
|
||||
ExplorerIcon.Parameter))
|
||||
.Union(p.ResultTable?.Columns.Select(GetColumnItem) ?? Array.Empty<ExplorerItem>())
|
||||
.ToList(),
|
||||
};
|
||||
|
||||
if (p.ResultTable != null && !results.Contains(p.ResultTable))
|
||||
{
|
||||
results.Add(p.ResultTable);
|
||||
|
||||
CodeTable(Code, p.ResultTable, false);
|
||||
}
|
||||
|
||||
CodeProcedure(Code, p, sprocSqlName);
|
||||
|
||||
return ret;
|
||||
})
|
||||
.OrderBy(p => p.Text)
|
||||
.ToList(),
|
||||
};
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
void CodeTable(StringBuilder classCode, TableSchema table, bool addTableAttribute)
|
||||
{
|
||||
classCode.AppendLine();
|
||||
|
||||
table.TypeName = GetName(_existingMemberNames, table.TypeName);
|
||||
|
||||
if (addTableAttribute)
|
||||
{
|
||||
classCode.Append($" [Table(Name={CSharpTools.ToStringLiteral(table.TableName)}");
|
||||
|
||||
if (table.GroupName.NotNullNorEmpty())
|
||||
classCode.Append($", Schema={CSharpTools.ToStringLiteral(table.GroupName)}");
|
||||
else if (table.SchemaName.NotNullNorEmpty())
|
||||
classCode.Append($", Schema={CSharpTools.ToStringLiteral(table.SchemaName)}");
|
||||
|
||||
classCode.AppendLine(")]");
|
||||
}
|
||||
|
||||
classCode
|
||||
.AppendLine($" public class {table.TypeName}")
|
||||
.AppendLine( " {")
|
||||
;
|
||||
|
||||
foreach (var c in table.Columns)
|
||||
{
|
||||
classCode
|
||||
.Append($" [Column({CSharpTools.ToStringLiteral(c.ColumnName)}), ")
|
||||
.Append(c.IsNullable ? "Nullable" : "NotNull");
|
||||
|
||||
if (c.IsPrimaryKey) classCode.Append($", PrimaryKey({c.PrimaryKeyOrder})");
|
||||
if (c.IsIdentity) classCode.Append(", Identity");
|
||||
|
||||
classCode.AppendLine("]");
|
||||
|
||||
var memberType = GetMemberType(c);
|
||||
|
||||
classCode.AppendLine($" public {memberType} {c.MemberName} {{ get; set; }}");
|
||||
}
|
||||
|
||||
foreach (var key in table.ForeignKeys)
|
||||
{
|
||||
classCode
|
||||
.Append( " [Association(")
|
||||
.Append($"ThisKey={CSharpTools.ToStringLiteral((key.ThisColumns.Select(c => c.MemberName)).Join(", "))}")
|
||||
.Append($", OtherKey={CSharpTools.ToStringLiteral((key.OtherColumns.Select(c => c.MemberName)).Join(", "))}")
|
||||
.Append($", CanBeNull={(key.CanBeNull ? "true" : "false")}")
|
||||
;
|
||||
|
||||
if (key.BackReference != null)
|
||||
{
|
||||
if (key.KeyName.NotNullNorEmpty())
|
||||
classCode.Append($", KeyName={CSharpTools.ToStringLiteral(key.KeyName)}");
|
||||
if (key.BackReference.KeyName.NotNullNorEmpty())
|
||||
classCode.Append($", BackReferenceName={CSharpTools.ToStringLiteral(key.BackReference.MemberName)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
classCode.Append(", IsBackReference=true");
|
||||
}
|
||||
|
||||
classCode.AppendLine(")]");
|
||||
|
||||
var typeName = key.AssociationType == AssociationType.OneToMany
|
||||
? $"List<{key.OtherTable.TypeName}>"
|
||||
: key.OtherTable.TypeName;
|
||||
|
||||
classCode.AppendLine($" public {typeName} {key.MemberName} {{ get; set; }}");
|
||||
}
|
||||
|
||||
classCode.AppendLine(" }");
|
||||
}
|
||||
|
||||
private string GetMemberType(ColumnSchema c)
|
||||
{
|
||||
if (UseProviderSpecificTypes)
|
||||
{
|
||||
return c.ProviderSpecificType switch
|
||||
{
|
||||
// ignore some types for various reasons
|
||||
|
||||
var t when
|
||||
// DB2:those IBM.Data.DB2Types.* types cannot return value (without linq2db changes)
|
||||
t == "DB2Clob" ||
|
||||
t == "DB2Blob" ||
|
||||
t == "DB2Xml" ||
|
||||
// Oracle: temporary ignore types, as linq2db schema provider doesn't resolve them properly
|
||||
t == "OracleIntervalDS" ||
|
||||
t == "OracleIntervalYM" ||
|
||||
t == "OracleTimeStamp" ||
|
||||
t == "OracleTimeStampLTZ" ||
|
||||
t == "OracleTimeStampTZ" ||
|
||||
// MySql: doesn't make sense to expose as value has same byte[] type
|
||||
t == "MySqlGeometry" => c.MemberType,
|
||||
|
||||
// temporary fix nullability for provider-specific struct types (should be done in linq2db)
|
||||
var t when
|
||||
// DB2/Informix
|
||||
t == "DB2Time" ||
|
||||
t == "DB2RowId" ||
|
||||
t == "DB2Binary" ||
|
||||
t == "DB2String" ||
|
||||
t == "DB2TimeStamp" ||
|
||||
t == "DB2Date" ||
|
||||
t == "DB2DateTime" ||
|
||||
t == "DB2Int16" ||
|
||||
t == "DB2Int32" ||
|
||||
t == "DB2Int64" ||
|
||||
t == "DB2Decimal" ||
|
||||
t == "DB2DecimalFloat" ||
|
||||
t == "DB2Real" ||
|
||||
t == "DB2Double" ||
|
||||
// NPGSQL
|
||||
t == "NpgsqlInet" ||
|
||||
t == "NpgsqlPoint" ||
|
||||
t == "NpgsqlLine" ||
|
||||
t == "NpgsqlLSeg" ||
|
||||
t == "NpgsqlBox" ||
|
||||
t == "NpgsqlPath" ||
|
||||
t == "NpgsqlPolygon" ||
|
||||
t == "NpgsqlCircle" ||
|
||||
t == "NpgsqlDate" ||
|
||||
t == "NpgsqlTimeSpan" ||
|
||||
t == "NpgsqlDateTime" ||
|
||||
// Oracle
|
||||
t == "OracleBinary" ||
|
||||
t == "OracleDate" ||
|
||||
t == "OracleDecimal" ||
|
||||
t == "OracleIntervalDS" ||
|
||||
t == "OracleIntervalYM" ||
|
||||
t == "OracleString" ||
|
||||
t == "OracleTimeStamp" ||
|
||||
t == "OracleTimeStampLTZ" ||
|
||||
t == "OracleTimeStampTZ" ||
|
||||
// SQLCE/SQL Server
|
||||
t == "SqlByte" ||
|
||||
t == "SqlInt16" ||
|
||||
t == "SqlInt32" ||
|
||||
t == "SqlInt64" ||
|
||||
t == "SqlDecimal" ||
|
||||
t == "SqlMoney" ||
|
||||
t == "SqlSingle" ||
|
||||
t == "SqlDouble" ||
|
||||
t == "SqlBoolean" ||
|
||||
t == "SqlString" ||
|
||||
t == "SqlDateTime" ||
|
||||
t == "SqlBinary" ||
|
||||
t == "SqlGuid" ||
|
||||
// sql server
|
||||
t == "Microsoft.SqlServer.Types.SqlHierarchyId" ||
|
||||
// MYSQL
|
||||
t == "MySqlDateTime" => c.IsNullable && !c.ProviderSpecificType!.EndsWith("?") ? c.ProviderSpecificType + "?" : c.ProviderSpecificType!,
|
||||
|
||||
null => c.MemberType,
|
||||
_ => c.ProviderSpecificType
|
||||
};
|
||||
}
|
||||
|
||||
return c.MemberType;
|
||||
}
|
||||
|
||||
ExplorerItem GetTables(string header, ExplorerIcon icon, IEnumerable<TableSchema> tableSource)
|
||||
{
|
||||
var tables = tableSource.ToList();
|
||||
var dic = new Dictionary<TableSchema,ExplorerItem>();
|
||||
|
||||
var items = new ExplorerItem(header, ExplorerItemKind.Category, icon)
|
||||
{
|
||||
Children = tables
|
||||
.Select(t =>
|
||||
{
|
||||
var memberName = _contextMembers[t];
|
||||
|
||||
Code.AppendLine($" public ITable<{t.TypeName}> {memberName} {{ get {{ return this.GetTable<{t.TypeName}>(); }} }}");
|
||||
|
||||
CodeTable(_classCode, t, true);
|
||||
|
||||
var tableSqlName = _sqlBuilder!.BuildObjectName(
|
||||
new StringBuilder(),
|
||||
new SqlQuery.SqlObjectName(
|
||||
Name: t.TableName!,
|
||||
Schema: t.SchemaName),
|
||||
tableOptions: TableOptions.NotSet).ToString();
|
||||
|
||||
//Debug.WriteLine($"Table: [{t.SchemaName}].[{t.TableName}] - ${tableSqlName}");
|
||||
|
||||
var ret = new ExplorerItem(memberName, ExplorerItemKind.QueryableObject, icon)
|
||||
{
|
||||
DragText = memberName,
|
||||
ToolTipText = $"ITable<{t.TypeName}>",
|
||||
SqlName = tableSqlName,
|
||||
IsEnumerable = true,
|
||||
Children = t.Columns.Select(GetColumnItem).ToList()
|
||||
};
|
||||
|
||||
dic[t] = ret;
|
||||
|
||||
return ret;
|
||||
})
|
||||
.OrderBy(t => t.Text)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
foreach (var table in tables.Where(t => dic.ContainsKey(t)))
|
||||
{
|
||||
var entry = dic[table];
|
||||
|
||||
foreach (var key in table.ForeignKeys)
|
||||
{
|
||||
var typeName = key.AssociationType == AssociationType.OneToMany
|
||||
? $"List<{key.OtherTable.TypeName}>"
|
||||
: key.OtherTable.TypeName;
|
||||
|
||||
entry.Children.Add(
|
||||
new ExplorerItem(
|
||||
key.MemberName,
|
||||
key.AssociationType == AssociationType.OneToMany
|
||||
? ExplorerItemKind.CollectionLink
|
||||
: ExplorerItemKind.ReferenceLink,
|
||||
key.AssociationType == AssociationType.OneToMany
|
||||
? ExplorerIcon.OneToMany
|
||||
: key.AssociationType == AssociationType.ManyToOne
|
||||
? ExplorerIcon.ManyToOne
|
||||
: ExplorerIcon.OneToOne)
|
||||
{
|
||||
DragText = key.MemberName,
|
||||
ToolTipText = typeName + (key.BackReference == null ? " // Back Reference" : ""),
|
||||
SqlName = key.KeyName,
|
||||
IsEnumerable = key.AssociationType == AssociationType.OneToMany,
|
||||
HyperlinkTarget = dic.ContainsKey(key.OtherTable) ? dic[key.OtherTable] : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static string GetUniqueName(HashSet<string> names, string proposedName)
|
||||
{
|
||||
var name = proposedName;
|
||||
var n = 0;
|
||||
|
||||
while (names.Contains(name))
|
||||
name = proposedName + ++n;
|
||||
|
||||
names.Add(name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static string GetName(HashSet<string> names, string proposedName)
|
||||
{
|
||||
return GetName(names, proposedName, false);
|
||||
}
|
||||
|
||||
static string GetName(HashSet<string> names, string proposedName, bool capitalize)
|
||||
{
|
||||
return GetUniqueName(names, ConvertToCompilable(proposedName, capitalize));
|
||||
}
|
||||
|
||||
readonly Dictionary<TableSchema,string> _contextMembers = new ();
|
||||
|
||||
void ConvertSchema(string typeName)
|
||||
{
|
||||
var typeNames = new HashSet<string> { typeName };
|
||||
var contextMemberNames = new HashSet<string> { typeName };
|
||||
|
||||
foreach (var table in _schema!.Tables)
|
||||
{
|
||||
table.TypeName = GetName(typeNames, table.TypeName);
|
||||
|
||||
{
|
||||
var contextMemberName = table.TypeName;
|
||||
|
||||
if (!_cxInfo.DynamicSchemaOptions.NoPluralization)
|
||||
contextMemberName = Pluralization.ToPlural(contextMemberName);
|
||||
|
||||
_contextMembers[table] = GetName(contextMemberNames, contextMemberName);
|
||||
}
|
||||
|
||||
var classMemberNames = new HashSet<string> { table.TypeName };
|
||||
|
||||
foreach (var column in table.Columns)
|
||||
{
|
||||
//Debug.WriteLine($"{table.TypeName}.{column.MemberName}");
|
||||
|
||||
column.MemberName = GetName(classMemberNames, column.MemberName, !_cxInfo.DynamicSchemaOptions.NoCapitalization);
|
||||
}
|
||||
|
||||
foreach (var key in table.ForeignKeys)
|
||||
{
|
||||
if (!_cxInfo.DynamicSchemaOptions.NoPluralization)
|
||||
key.MemberName = key.AssociationType == AssociationType.OneToMany
|
||||
? Pluralization.ToPlural (key.MemberName)
|
||||
: Pluralization.ToSingular(key.MemberName);
|
||||
|
||||
key.MemberName = GetName(classMemberNames, key.MemberName);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var procedure in _schema.Procedures)
|
||||
{
|
||||
// migrate https://github.com/linq2db/linq2db/pull/1905
|
||||
if (!procedure.IsFunction && ProviderName == LinqToDB.ProviderName.SqlServer)
|
||||
{
|
||||
// sql server procedures always have integer return parameter
|
||||
var name = "@returnValue";
|
||||
var cnt = 0;
|
||||
while (procedure.Parameters.Any(_ => _.ParameterName == name))
|
||||
name = $"@returnValue{cnt++}";
|
||||
|
||||
procedure.Parameters.Add(new ParameterSchema()
|
||||
{
|
||||
SchemaName = name,
|
||||
ParameterName = name,
|
||||
IsResult = true,
|
||||
DataType = DataType.Int32,
|
||||
SystemType = typeof(int),
|
||||
SchemaType = "int",
|
||||
ParameterType = "int",
|
||||
ProviderSpecificType = "int"
|
||||
});
|
||||
}
|
||||
|
||||
procedure.MemberName = GetName(typeNames, procedure.MemberName);
|
||||
|
||||
if (procedure.ResultTable != null && !_contextMembers.ContainsKey(procedure.ResultTable))
|
||||
{
|
||||
procedure.ResultTable.TypeName = GetName(typeNames, procedure.ResultTable.TypeName);
|
||||
|
||||
var classMemberNames = new HashSet<string> { procedure.ResultTable.TypeName };
|
||||
|
||||
foreach (var column in procedure.ResultTable.Columns)
|
||||
{
|
||||
var memberName = column.MemberName.IsNullOrWhiteSpace() ? "Column" : column.MemberName;
|
||||
column.MemberName = GetName(classMemberNames, memberName);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parameter in procedure.Parameters)
|
||||
{
|
||||
parameter.ParameterName = ConvertToCompilable(parameter.ParameterName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string ConvertToCompilable(string name, bool capitalize)
|
||||
{
|
||||
if (capitalize)
|
||||
{
|
||||
var sb = new StringBuilder(name);
|
||||
for (int i = 0; i < sb.Length; i++)
|
||||
{
|
||||
if (char.IsLetter(sb[i]))
|
||||
{
|
||||
sb[i] = sb[i].ToUpper();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
name = sb.ToString();
|
||||
}
|
||||
|
||||
return CSharpTools.ToValidIdentifier(name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using LinqToDB.Extensions;
|
||||
using LinqToDB.Mapping;
|
||||
using LinqToDB.Reflection;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
class SchemaGenerator
|
||||
{
|
||||
public SchemaGenerator(IConnectionInfo cxInfo, Type customType)
|
||||
{
|
||||
_cxInfo = cxInfo;
|
||||
_customType = customType;
|
||||
}
|
||||
|
||||
readonly IConnectionInfo _cxInfo;
|
||||
readonly Type _customType;
|
||||
|
||||
class TableInfo
|
||||
{
|
||||
public TableInfo(PropertyInfo propertyInfo)
|
||||
{
|
||||
PropertyInfo = propertyInfo;
|
||||
Name = propertyInfo.Name;
|
||||
Type = propertyInfo.PropertyType.GetItemType()!;
|
||||
TypeAccessor = TypeAccessor.GetAccessor(Type);
|
||||
|
||||
var tableAttr = Type.GetCustomAttributeLike<TableAttribute>();
|
||||
|
||||
if (tableAttr != null)
|
||||
{
|
||||
IsColumnAttributeRequired = tableAttr.IsColumnAttributeRequired;
|
||||
|
||||
if (Extensions.HasProperty(tableAttr, "IsView"))
|
||||
IsView = tableAttr.IsView;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly PropertyInfo PropertyInfo;
|
||||
public readonly string Name;
|
||||
public readonly Type Type;
|
||||
public readonly bool IsColumnAttributeRequired;
|
||||
public readonly TypeAccessor TypeAccessor;
|
||||
public readonly bool IsView;
|
||||
}
|
||||
|
||||
public IEnumerable<ExplorerItem> GetSchema()
|
||||
{
|
||||
var tables = _customType.GetProperties()
|
||||
.Where(p =>
|
||||
p.GetCustomAttributeLike<ObsoleteAttribute>() == null &&
|
||||
p.PropertyType.MaybeChildOf(typeof(IQueryable<>)))
|
||||
.OrderBy(p => p.Name)
|
||||
.Select(p => new TableInfo(p))
|
||||
.ToList();
|
||||
|
||||
var items = new List<ExplorerItem>();
|
||||
|
||||
if (tables.Any(t => !t.IsView)) items.Add(GetTables("Tables", ExplorerIcon.Table, tables.Where(t => !t.IsView)));
|
||||
if (tables.Any(t => t.IsView)) items.Add(GetTables("Views", ExplorerIcon.View, tables.Where(t => t.IsView)));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
ExplorerItem GetTables(string header, ExplorerIcon icon, IEnumerable<TableInfo> tableSource)
|
||||
{
|
||||
var tables = tableSource.ToList();
|
||||
var dic = tables.ToDictionary(t => t.Type, t => new { table = t, item = GetTable(icon, t) });
|
||||
|
||||
var items = new ExplorerItem(header, ExplorerItemKind.Category, icon)
|
||||
{
|
||||
Children = dic.Values.OrderBy(t => t.table.Name).Select(t => t.item).ToList()
|
||||
};
|
||||
|
||||
foreach (var table in dic.Values)
|
||||
{
|
||||
var entry = table.item;
|
||||
var typeAccessor = table.table.TypeAccessor;
|
||||
|
||||
foreach (var ma in typeAccessor.Members)
|
||||
{
|
||||
var aa = ma.MemberInfo.GetCustomAttributeLike<AssociationAttribute>();
|
||||
|
||||
if (aa != null)
|
||||
{
|
||||
var relationship = Extensions.HasProperty(aa, "Relationship") ? aa.Relationship : Relationship.OneToOne;
|
||||
var otherType = relationship == Relationship.OneToMany ? ma.Type.GetItemType()! : ma.Type;
|
||||
var otherTable = dic.ContainsKey(otherType) ? dic[otherType] : null;
|
||||
var typeName = relationship == Relationship.OneToMany ? $"List<{otherType.Name}>" : otherType.Name;
|
||||
|
||||
entry.Children.Add(
|
||||
new ExplorerItem(
|
||||
ma.Name,
|
||||
relationship == Relationship.OneToMany
|
||||
? ExplorerItemKind.CollectionLink
|
||||
: ExplorerItemKind.ReferenceLink,
|
||||
relationship == Relationship.OneToMany
|
||||
? ExplorerIcon.OneToMany
|
||||
: relationship == Relationship.ManyToOne
|
||||
? ExplorerIcon.ManyToOne
|
||||
: ExplorerIcon.OneToOne)
|
||||
{
|
||||
DragText = ma.Name,
|
||||
ToolTipText = typeName + (aa.IsBackReference == true ? " // Back Reference" : ""),
|
||||
SqlName = aa.KeyName,
|
||||
IsEnumerable = ma.Type.MaybeChildOf(typeof(IEnumerable<>)) && !ma.Type.MaybeEqualTo(typeof(string)),
|
||||
HyperlinkTarget = otherTable?.item,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
ExplorerItem GetTable(ExplorerIcon icon, TableInfo table)
|
||||
{
|
||||
var columns =
|
||||
(
|
||||
from ma in table.TypeAccessor.Members
|
||||
let aa = ma.MemberInfo.GetCustomAttributeLike<AssociationAttribute>()
|
||||
where aa == null
|
||||
let ca = ma.MemberInfo.GetCustomAttributeLike<ColumnAttribute>() as ColumnAttribute
|
||||
let id = ma.MemberInfo.GetCustomAttributeLike<IdentityAttribute>()
|
||||
let pk = ma.MemberInfo.GetCustomAttributeLike<PrimaryKeyAttribute>()
|
||||
orderby
|
||||
ca == null ? 1 : ca.Order >= 0 ? 0 : 2,
|
||||
ca?.Order,
|
||||
ma.Name
|
||||
where
|
||||
ca != null && ca.IsColumn ||
|
||||
pk != null ||
|
||||
id != null ||
|
||||
ca == null && !table.IsColumnAttributeRequired && MappingSchema.Default.IsScalarType(ma.Type)
|
||||
select new ExplorerItem(
|
||||
ma.Name,
|
||||
ExplorerItemKind.Property,
|
||||
pk != null || ca != null && ca.IsPrimaryKey ? ExplorerIcon.Key : ExplorerIcon.Column)
|
||||
{
|
||||
Text = $"{ma.Name} : {GetTypeName(ma.Type)}",
|
||||
// ToolTipText = $"{sqlName} {column.ColumnType} {(column.IsNullable ? "NULL" : "NOT NULL")}{(column.IsIdentity ? " IDENTITY" : "")}",
|
||||
DragText = ma.Name,
|
||||
// SqlName = sqlName,
|
||||
// SqlTypeDeclaration = $"{column.ColumnType} {(column.IsNullable ? "NULL" : "NOT NULL")}{(column.IsIdentity ? " IDENTITY" : "")}",
|
||||
}
|
||||
).ToList();
|
||||
|
||||
var ret = new ExplorerItem(table.Name, ExplorerItemKind.QueryableObject, icon)
|
||||
{
|
||||
DragText = table.Name,
|
||||
// ToolTipText = $"ITable<{t.TypeName}>",
|
||||
IsEnumerable = true,
|
||||
Children = columns.ToList(),
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
string GetTypeName(Type type)
|
||||
{
|
||||
switch (type.FullName)
|
||||
{
|
||||
case "System.Boolean" : return "bool";
|
||||
case "System.Byte" : return "byte";
|
||||
case "System.SByte" : return "sbyte";
|
||||
case "System.Int16" : return "short";
|
||||
case "System.Int32" : return "int";
|
||||
case "System.Int64" : return "long";
|
||||
case "System.UInt16" : return "ushort";
|
||||
case "System.UInt32" : return "uint";
|
||||
case "System.UInt64" : return "ulong";
|
||||
case "System.Decimal" : return "decimal";
|
||||
case "System.Single" : return "float";
|
||||
case "System.Double" : return "double";
|
||||
case "System.String" : return "string";
|
||||
case "System.Char" : return "char";
|
||||
case "System.Object" : return "object";
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
return GetTypeName(type.GetElementType()!) + "[]";
|
||||
|
||||
if (type.IsNullable())
|
||||
return GetTypeName(type.ToNullableUnderlying()) + '?';
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad
|
||||
{
|
||||
public class StringToIntegerConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null)
|
||||
return int.Parse(value.ToString()!);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null)
|
||||
return value.ToString();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class AboutModel
|
||||
{
|
||||
public static AboutModel Instance { get; } = new AboutModel();
|
||||
|
||||
private AboutModel()
|
||||
{
|
||||
// avoid Uri constructor crash on pack:// scheme in runtime due to initialization order
|
||||
// Application constructor will register schema handler
|
||||
if (!UriParser.IsKnownScheme("pack"))
|
||||
new Application();
|
||||
|
||||
var assembly = typeof(AboutModel).Assembly;
|
||||
Logo = new BitmapImage(new Uri($"pack://application:,,,/{assembly.FullName};component/resources/logo.png"));
|
||||
Project = $"Linq To DB LINQPad Driver v{assembly.GetName().Version!.ToString(3)}";
|
||||
Copyright = assembly.GetCustomAttribute<AssemblyCopyrightAttribute>()!.Copyright;
|
||||
}
|
||||
|
||||
public BitmapImage Logo { get; }
|
||||
public string Project { get; }
|
||||
public string Copyright { get; }
|
||||
public Uri RepositoryUri { get; } = new("https://github.com/linq2db/linq2db.LINQPad");
|
||||
public Uri ReportsUri { get; } = new("https://github.com/linq2db/linq2db.LINQPad/issues/new");
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal abstract class ConnectionModelBase : OptionalTabModelBase
|
||||
{
|
||||
protected ConnectionModelBase(ConnectionSettings settings, bool enabled)
|
||||
: base(settings, enabled)
|
||||
{
|
||||
IsSelected = enabled;
|
||||
}
|
||||
|
||||
public string? Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.Connection.DisplayName))
|
||||
return null;
|
||||
|
||||
return Settings.Connection.DisplayName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.Connection.DisplayName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool Persistent
|
||||
{
|
||||
get => Settings.Connection.Persistent;
|
||||
set => Settings.Connection.Persistent = value;
|
||||
}
|
||||
|
||||
public bool Production
|
||||
{
|
||||
get => Settings.Connection.IsProduction;
|
||||
set => Settings.Connection.IsProduction = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class DynamicConnectionModel : ConnectionModelBase, INotifyPropertyChanged
|
||||
{
|
||||
public DynamicConnectionModel(ConnectionSettings settings, bool enabled)
|
||||
: base(settings, enabled)
|
||||
{
|
||||
foreach (var db in DatabaseProviders.Providers.Values.OrderBy(static db => db.Description))
|
||||
Databases.Add(db);
|
||||
|
||||
UpdateProviders();
|
||||
UpdateProviderPathVisibility();
|
||||
UpdateSecondaryConnection();
|
||||
UpdateProviderDownloadUrl();
|
||||
}
|
||||
|
||||
private void UpdateProviders()
|
||||
{
|
||||
var db = Database;
|
||||
|
||||
if (db != null)
|
||||
{
|
||||
Providers.Clear();
|
||||
|
||||
foreach (var provider in db.Providers)
|
||||
Providers.Add(provider);
|
||||
|
||||
if (db.Providers.Count == 1)
|
||||
Settings.Connection.Provider = db.Providers[0].Name;
|
||||
}
|
||||
|
||||
var old = ProviderVisibility;
|
||||
ProviderVisibility = db?.Providers.Count > 1 && db?.AutomaticProviderSelection == false ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (ProviderVisibility != old)
|
||||
OnPropertyChanged(_providerVisibilityChangedEventArgs);
|
||||
}
|
||||
|
||||
private void UpdateProviderPathVisibility()
|
||||
{
|
||||
var db = Database;
|
||||
var provider = Provider;
|
||||
|
||||
if (db == null || provider == null || !db.IsProviderPathSupported(provider.Name))
|
||||
{
|
||||
ProviderPathVisibility = Visibility.Collapsed;
|
||||
ProviderPathLabel = null;
|
||||
ProviderPath = null;
|
||||
|
||||
OnPropertyChanged(_providerPathVisibilityChangedEventArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
var assemblyName = db.GetProviderAssemblyName(provider.Name);
|
||||
ProviderPathVisibility = Visibility.Visible;
|
||||
ProviderPath = Settings.Connection.ProviderPath ?? db.TryGetDefaultPath(provider.Name);
|
||||
ProviderPathLabel = $"Specify path to {assemblyName}";
|
||||
|
||||
OnPropertyChanged(_providerPathVisibilityChangedEventArgs);
|
||||
OnPropertyChanged(_providerPathLabelChangedEventArgs);
|
||||
OnPropertyChanged(_providerPathChangedEventArgs);
|
||||
}
|
||||
|
||||
private void UpdateProviderDownloadUrl()
|
||||
{
|
||||
var db = Database;
|
||||
var provider = Provider;
|
||||
|
||||
if (db == null)
|
||||
{
|
||||
ProviderDownloadUrlVisibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProviderDownloadUrl = db.GetProviderDownloadUrl(provider?.Name);
|
||||
ProviderDownloadUrlVisibility = ProviderDownloadUrl != null ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
OnPropertyChanged(_providerDownloadUrlChangedEventArgs);
|
||||
OnPropertyChanged(_providerDownloadUrlVisibilityChangedEventArgs);
|
||||
}
|
||||
|
||||
private void UpdateSecondaryConnection()
|
||||
{
|
||||
SecondaryConnectionStringVisibility = Database?.SupportsSecondaryConnection == true ? Visibility.Visible : Visibility.Collapsed;
|
||||
OnPropertyChanged(_secondaryConnectionStringVisibilityChangedEventArgs);
|
||||
}
|
||||
|
||||
public ObservableCollection<IDatabaseProvider> Databases { get; } = new();
|
||||
|
||||
private static readonly PropertyChangedEventArgs _databaseChangedEventArgs = new (nameof(Database));
|
||||
public IDatabaseProvider? Database
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.Connection.Database))
|
||||
return null;
|
||||
|
||||
return DatabaseProviders.GetProvider(Settings.Connection.Database);
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings.Connection.Database = value?.Database;
|
||||
UpdateProviders();
|
||||
UpdateSecondaryConnection();
|
||||
UpdateProviderDownloadUrl();
|
||||
OnPropertyChanged(_databaseChangedEventArgs);
|
||||
Provider = GetCurrentProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerVisibilityChangedEventArgs = new (nameof(ProviderVisibility));
|
||||
public Visibility ProviderVisibility { get; set; }
|
||||
|
||||
public ObservableCollection<ProviderInfo> Providers { get; } = new();
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerChangedEventArgs = new (nameof(Provider));
|
||||
public ProviderInfo? Provider
|
||||
{
|
||||
get => GetCurrentProvider();
|
||||
set
|
||||
{
|
||||
Settings.Connection.Provider = value?.Name;
|
||||
UpdateProviderPathVisibility();
|
||||
UpdateProviderDownloadUrl();
|
||||
OnPropertyChanged(_providerChangedEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private ProviderInfo? GetCurrentProvider()
|
||||
{
|
||||
if (Database == null)
|
||||
return null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Settings.Connection.Provider))
|
||||
{
|
||||
foreach (var provider in Database.Providers)
|
||||
if (provider.Name == Settings.Connection.Provider)
|
||||
return provider;
|
||||
}
|
||||
|
||||
foreach (var provider in Database.Providers)
|
||||
if (provider.IsDefault)
|
||||
return provider;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerPathVisibilityChangedEventArgs = new (nameof(ProviderPathVisibility));
|
||||
public Visibility ProviderPathVisibility { get; set; }
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerPathChangedEventArgs = new (nameof(ProviderPath));
|
||||
public string? ProviderPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.Connection.ProviderPath))
|
||||
return null;
|
||||
|
||||
return Settings.Connection.ProviderPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.Connection.ProviderPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerPathLabelChangedEventArgs = new (nameof(ProviderPathLabel));
|
||||
public string? ProviderPathLabel { get; set; }
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerDownloadUrlVisibilityChangedEventArgs = new (nameof(ProviderDownloadUrlVisibility));
|
||||
public Visibility ProviderDownloadUrlVisibility { get; set; }
|
||||
|
||||
private static readonly PropertyChangedEventArgs _providerDownloadUrlChangedEventArgs = new (nameof(ProviderDownloadUrl));
|
||||
public string? ProviderDownloadUrl { get; set; }
|
||||
|
||||
public string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.Connection.ConnectionString))
|
||||
return null;
|
||||
|
||||
return Settings.Connection.ConnectionString;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.Connection.ConnectionString = value;
|
||||
|
||||
if (Database != null && value != null && Database.AutomaticProviderSelection)
|
||||
Provider = Database.GetProviderByConnectionString(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _secondaryConnectionStringVisibilityChangedEventArgs = new (nameof(SecondaryConnectionStringVisibility));
|
||||
public Visibility SecondaryConnectionStringVisibility { get; set; }
|
||||
|
||||
public string? SecondaryConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.Connection.SecondaryConnectionString))
|
||||
return null;
|
||||
|
||||
return Settings.Connection.SecondaryConnectionString;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.Connection.SecondaryConnectionString = value;
|
||||
|
||||
if (Database != null && value != null && Database.AutomaticProviderSelection)
|
||||
SecondaryProvider = Database.GetProviderByConnectionString(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ProviderInfo? SecondaryProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Database == null || string.IsNullOrWhiteSpace(Settings.Connection.SecondaryProvider))
|
||||
return null;
|
||||
|
||||
foreach (var provider in Database.Providers)
|
||||
if (provider.Name == Settings.Connection.SecondaryProvider)
|
||||
return provider;
|
||||
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings.Connection.SecondaryProvider = value?.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public bool EncryptConnectionString
|
||||
{
|
||||
get => Settings.Connection.EncryptConnectionString;
|
||||
set => Settings.Connection.EncryptConnectionString = value;
|
||||
}
|
||||
|
||||
public int? CommandTimeout
|
||||
{
|
||||
get => Settings.Connection.CommandTimeout;
|
||||
set => Settings.Connection.CommandTimeout = value;
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class LinqToDBModel : TabModelBase
|
||||
{
|
||||
public LinqToDBModel(ConnectionSettings settings)
|
||||
: base(settings)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OptimizeJoins
|
||||
{
|
||||
get => Settings.LinqToDB.OptimizeJoins;
|
||||
set => Settings.LinqToDB.OptimizeJoins = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal abstract partial class ModelBase : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
|
||||
|
||||
#region Sample Simple Property
|
||||
|
||||
private string? _name;
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(_nameChangedEventArgs);
|
||||
AfterNameChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _nameChangedEventArgs = new (nameof(Name));
|
||||
|
||||
partial void AfterNameChanged();
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.Windows;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal abstract class OptionalTabModelBase : TabModelBase
|
||||
{
|
||||
protected OptionalTabModelBase(ConnectionSettings settings, bool enabled)
|
||||
: base(settings)
|
||||
{
|
||||
Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public Visibility Visibility { get; }
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class ScaffoldModel : OptionalTabModelBase, INotifyPropertyChanged
|
||||
{
|
||||
public ScaffoldModel(ConnectionSettings settings, bool enabled)
|
||||
: base(settings, enabled)
|
||||
{
|
||||
UpdateClickHouseVisibility();
|
||||
}
|
||||
|
||||
public bool Capitalize
|
||||
{
|
||||
get => Settings.Scaffold.Capitalize;
|
||||
set => Settings.Scaffold.Capitalize = value;
|
||||
}
|
||||
|
||||
public bool Pluralize
|
||||
{
|
||||
get => Settings.Scaffold.Pluralize;
|
||||
set => Settings.Scaffold.Pluralize = value;
|
||||
}
|
||||
|
||||
public bool UseProviderTypes
|
||||
{
|
||||
get => Settings.Scaffold.UseProviderTypes;
|
||||
set => Settings.Scaffold.UseProviderTypes = value;
|
||||
}
|
||||
|
||||
public bool ClickHouseUseStrings
|
||||
{
|
||||
get => Settings.Scaffold.ClickHouseFixedStringAsString;
|
||||
set => Settings.Scaffold.ClickHouseFixedStringAsString = value;
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _clickHouseVisibilityChangedEventArgs = new (nameof(ClickHouseVisibility));
|
||||
public Visibility ClickHouseVisibility { get; set; }
|
||||
|
||||
internal void UpdateClickHouseVisibility()
|
||||
{
|
||||
ClickHouseVisibility = Settings.Connection.Database == ProviderName.ClickHouse ? Visibility.Visible : Visibility.Collapsed;
|
||||
OnPropertyChanged(_clickHouseVisibilityChangedEventArgs);
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class SchemaModel : OptionalTabModelBase
|
||||
{
|
||||
public SchemaModel(ConnectionSettings settings, bool enabled)
|
||||
: base(settings, enabled)
|
||||
{
|
||||
Schemas = new UniqueStringListModel()
|
||||
{
|
||||
Title = "Include/Exclude Schemas (Users)",
|
||||
ToolTip = "Include or exclude objects from specified schemas",
|
||||
Include = Settings.Schema.IncludeSchemas,
|
||||
};
|
||||
|
||||
if (Settings.Schema.Schemas != null)
|
||||
foreach (var schema in Settings.Schema.Schemas)
|
||||
Schemas.Items.Add(schema);
|
||||
|
||||
Catalogs = new UniqueStringListModel()
|
||||
{
|
||||
Title = "Include/Exclude Catalogs (Databases)",
|
||||
ToolTip = "Include or exclude objects from specified catalogs",
|
||||
Include = Settings.Schema.IncludeSchemas,
|
||||
};
|
||||
|
||||
if (Settings.Schema.Catalogs != null)
|
||||
foreach (var catalog in Settings.Schema.Catalogs)
|
||||
Catalogs.Items.Add(catalog);
|
||||
}
|
||||
|
||||
public UniqueStringListModel Schemas { get; }
|
||||
public UniqueStringListModel Catalogs { get; }
|
||||
|
||||
public bool LoadForeignKeys
|
||||
{
|
||||
get => Settings.Schema.LoadForeignKeys;
|
||||
set => Settings.Schema.LoadForeignKeys = value;
|
||||
}
|
||||
|
||||
public bool LoadProcedures
|
||||
{
|
||||
get => Settings.Schema.LoadProcedures;
|
||||
set => Settings.Schema.LoadProcedures = value;
|
||||
}
|
||||
|
||||
public bool LoadTableFunctions
|
||||
{
|
||||
get => Settings.Schema.LoadTableFunctions;
|
||||
set => Settings.Schema.LoadTableFunctions = value;
|
||||
}
|
||||
|
||||
public bool LoadScalarFunctions
|
||||
{
|
||||
get => Settings.Schema.LoadScalarFunctions;
|
||||
set => Settings.Schema.LoadScalarFunctions = value;
|
||||
}
|
||||
|
||||
public bool LoadAggregateFunctions
|
||||
{
|
||||
get => Settings.Schema.LoadAggregateFunctions;
|
||||
set => Settings.Schema.LoadAggregateFunctions = value;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Settings.Schema.IncludeSchemas = Schemas .Include;
|
||||
Settings.Schema.IncludeCatalogs = Catalogs.Include;
|
||||
|
||||
Settings.Schema.Schemas = Schemas .Items.Count == 0 ? null : new HashSet<string>(Schemas .Items).AsReadOnly();
|
||||
Settings.Schema.Catalogs = Catalogs.Items.Count == 0 ? null : new HashSet<string>(Catalogs.Items).AsReadOnly();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class SettingsModel
|
||||
{
|
||||
// Don't remove. Design-time .ctor
|
||||
public SettingsModel()
|
||||
: this(new ConnectionSettings(), false)
|
||||
{
|
||||
}
|
||||
|
||||
public SettingsModel(ConnectionSettings settings, bool staticConnection)
|
||||
{
|
||||
StaticConnection = new StaticConnectionModel (settings, staticConnection );
|
||||
DynamicConnection = new DynamicConnectionModel(settings, !staticConnection);
|
||||
Scaffold = new ScaffoldModel (settings, !staticConnection);
|
||||
Schema = new SchemaModel (settings, !staticConnection);
|
||||
LinqToDB = new LinqToDBModel (settings );
|
||||
}
|
||||
|
||||
public StaticConnectionModel StaticConnection { get; }
|
||||
public DynamicConnectionModel DynamicConnection { get; }
|
||||
public ScaffoldModel Scaffold { get; }
|
||||
public SchemaModel Schema { get; }
|
||||
public LinqToDBModel LinqToDB { get; }
|
||||
public AboutModel About => AboutModel.Instance;
|
||||
|
||||
public void Save()
|
||||
{
|
||||
// save settings that is not saved automatically by tab models
|
||||
Schema.Save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class StaticConnectionModel : ConnectionModelBase, INotifyPropertyChanged
|
||||
{
|
||||
public StaticConnectionModel(ConnectionSettings settings, bool enabled)
|
||||
: base(settings, enabled)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _contextAssemblyPathChangedEventArgs = new (nameof(ContextAssemblyPath));
|
||||
public string? ContextAssemblyPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.StaticContext.ContextAssemblyPath))
|
||||
return null;
|
||||
|
||||
return Settings.StaticContext.ContextAssemblyPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
else
|
||||
value = value!.Trim();
|
||||
|
||||
if (Settings.StaticContext.ContextAssemblyPath != value)
|
||||
{
|
||||
Settings.StaticContext.ContextAssemblyPath = value;
|
||||
OnPropertyChanged(_contextAssemblyPathChangedEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? ContextTypeName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.StaticContext.ContextTypeName))
|
||||
return null;
|
||||
|
||||
return Settings.StaticContext.ContextTypeName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.StaticContext.ContextTypeName = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly PropertyChangedEventArgs _configurationPathChangedEventArgs = new (nameof(ConfigurationPath));
|
||||
public string? ConfigurationPath
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
if (!string.IsNullOrWhiteSpace(Settings.StaticContext.LocalConfigurationPath))
|
||||
return Settings.StaticContext.LocalConfigurationPath;
|
||||
#endif
|
||||
if (string.IsNullOrWhiteSpace(Settings.StaticContext.ConfigurationPath))
|
||||
return null;
|
||||
|
||||
return Settings.StaticContext.ConfigurationPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
else
|
||||
value = value!.Trim();
|
||||
|
||||
#if NETFRAMEWORK
|
||||
Settings.StaticContext.ConfigurationPath = null;
|
||||
Settings.StaticContext.LocalConfigurationPath = null;
|
||||
|
||||
if (value != null && value.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Settings.StaticContext.LocalConfigurationPath != value)
|
||||
{
|
||||
Settings.StaticContext.LocalConfigurationPath = value;
|
||||
OnPropertyChanged(_configurationPathChangedEventArgs);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (Settings.StaticContext.ConfigurationPath != value)
|
||||
{
|
||||
Settings.StaticContext.ConfigurationPath = value;
|
||||
OnPropertyChanged(_configurationPathChangedEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? ConfigurationName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Settings.StaticContext.ConfigurationName))
|
||||
return null;
|
||||
|
||||
return Settings.StaticContext.ConfigurationName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = null;
|
||||
|
||||
Settings.StaticContext.ConfigurationName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<string> ContextTypes { get; } = new();
|
||||
|
||||
public ObservableCollection<string> Configurations { get; } = new();
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal abstract class TabModelBase
|
||||
{
|
||||
protected readonly ConnectionSettings Settings;
|
||||
|
||||
protected TabModelBase(ConnectionSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed class UniqueStringListModel
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public string? ToolTip { get; set; }
|
||||
public bool Include { get; set; }
|
||||
public ObservableCollection<string> Items { get; } = new();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System.Windows;
|
||||
|
||||
namespace LinqToDB.LINQPad;
|
||||
|
||||
internal static class Notification
|
||||
{
|
||||
public static void Error(string message, string title = "Error")
|
||||
{
|
||||
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
public static void Error(Window owner, string message, string title = "Error")
|
||||
{
|
||||
MessageBox.Show(owner, message, title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
public static void Warning(Window owner, string message, string title = "Warning")
|
||||
{
|
||||
MessageBox.Show(owner, message, title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static void Info(Window owner, string message, string title = "Information")
|
||||
{
|
||||
MessageBox.Show(owner, message, title, MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
public static bool YesNo(Window owner, string message, string title = "Information", MessageBoxImage icon = MessageBoxImage.Question)
|
||||
{
|
||||
return MessageBox.Show(owner, message, title, MessageBoxButton.YesNo, icon) == MessageBoxResult.Yes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.AboutTab"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:AboutModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="100" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="80" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<Label Margin="0 20 0 0" HorizontalAlignment="Center" Content="{Binding Project}" />
|
||||
<Label HorizontalAlignment="Center" Content="{Binding Copyright}" />
|
||||
</StackPanel>
|
||||
|
||||
<Image Grid.Column="1" Margin="0 15" HorizontalAlignment="Center" VerticalAlignment="Top" Source="{Binding Logo}" Stretch="None" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1">
|
||||
<Label Margin="0 50 0 0" HorizontalAlignment="Center">Source code:</Label>
|
||||
<TextBlock HorizontalAlignment="Center">
|
||||
<Hyperlink NavigateUri="{Binding RepositoryUri}" RequestNavigate="Url_Click">
|
||||
<TextBlock Text="{Binding RepositoryUri}"/>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<Label Margin="0 20 0 0" HorizontalAlignment="Center">For issue reports/feature requests:</Label>
|
||||
<TextBlock HorizontalAlignment="Center">
|
||||
<Hyperlink NavigateUri="{Binding ReportsUri}" RequestNavigate="Url_Click">
|
||||
<TextBlock Text="{Binding ReportsUri}"/>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,22 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class AboutTab
|
||||
{
|
||||
public AboutTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Url_Click(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
sealed class CommandTimeoutConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is int intVal)
|
||||
return intVal.ToString(culture);
|
||||
|
||||
if (value is string strValue)
|
||||
return strValue;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is int intValue && intValue >= 0)
|
||||
return intValue;
|
||||
|
||||
if (value is string strValue && int.TryParse(strValue, NumberStyles.Integer, culture, out var intVal) && intVal >= 0)
|
||||
return intVal;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.DynamicConnectionTab"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
xmlns:main = "clr-namespace:LinqToDB.LINQPad"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:DynamicConnectionModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<StackPanel>
|
||||
<us:SharedConnectionOptions />
|
||||
|
||||
<GroupBox Header="Custom Connection">
|
||||
<StackPanel Margin="5">
|
||||
<DockPanel Margin="0 5 0 0" ToolTip="Select database">
|
||||
<Label>Database Type</Label>
|
||||
<ComboBox Width="380" Margin="10 0 0 0" ItemsSource="{Binding Databases}" SelectedValue="{Binding Database}" HorizontalAlignment="Right">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding (main:IDatabaseProvider.Description)}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel Margin="0 5 0 0" ToolTip="Select SQL dialect version or database provider" Visibility="{Binding ProviderVisibility}">
|
||||
<Label>Provider</Label>
|
||||
<ComboBox Width="380" Margin="10 0 0 0" ItemsSource="{Binding Providers}" SelectedValue="{Binding Provider}" DisplayMemberPath="DisplayName" HorizontalAlignment="Right" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Margin="0 5 0 0" Visibility="{Binding ProviderDownloadUrlVisibility}">You can download provider <Hyperlink NavigateUri="{Binding ProviderDownloadUrl}" RequestNavigate="Url_Click">here</Hyperlink>.</TextBlock>
|
||||
|
||||
<DockPanel Margin="0 5 0 0" ToolTip="Specify path to ADO.NET provider location" Visibility="{Binding ProviderPathVisibility}">
|
||||
<Button DockPanel.Dock="Right" Margin="5 0 0 0" Padding="10 5" Content="Select" Click="Click_SelectProvider" />
|
||||
<TextBox Padding="5" Text="{Binding ProviderPath}" />
|
||||
</DockPanel>
|
||||
|
||||
<StackPanel Margin="0 5 0 0" ToolTip="Specify database connection string">
|
||||
<Label>Connection string</Label>
|
||||
<TextBox Padding="5" Text="{Binding ConnectionString}" TextWrapping="Wrap" Height="60" VerticalScrollBarVisibility="Auto" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0 5 0 0"
|
||||
Visibility="{Binding SecondaryConnectionStringVisibility}"
|
||||
ToolTip="Specify secondary connection string for MS Access to improve generated schema. This connection string is used for database schema fetching only and must be ODBC connection string when primary connection string use OLE DB and vice versa.">
|
||||
<Label>Additional connection string</Label>
|
||||
<TextBox Padding="5" Text="{Binding SecondaryConnectionString}" TextWrapping="Wrap" Height="60" VerticalScrollBarVisibility="Auto" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Margin="0 5 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CheckBox Margin="5" Grid.Column="0" ToolTip="Store connection strings in LINQPad in encrypted form" Content="Encrypt connection strings" IsChecked="{Binding EncryptConnectionString}" />
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
ToolTip="Database command timeout in seconds. Use empty for default provider timeout.">
|
||||
<StackPanel.Resources>
|
||||
<us:CommandTimeoutConverter x:Key="Converter" />
|
||||
</StackPanel.Resources>
|
||||
<TextBox Padding="5" Text="{Binding CommandTimeout, Converter={StaticResource Converter}}" Width="50" />
|
||||
<Label Padding="5" Content="Command timeout"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -0,0 +1,55 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class DynamicConnectionTab
|
||||
{
|
||||
private DynamicConnectionModel Model => (DynamicConnectionModel)DataContext;
|
||||
|
||||
public DynamicConnectionTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Url_Click(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
void Click_SelectProvider(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model == null)
|
||||
return;
|
||||
|
||||
var provider = Model.Database;
|
||||
|
||||
if (provider == null || Model.Provider == null || provider.IsProviderPathSupported(Model.Provider.Name))
|
||||
return;
|
||||
|
||||
var assemblyName = provider.GetProviderAssemblyName(Model.Provider.Name);
|
||||
var defaultPath = provider.TryGetDefaultPath(Model.Provider.Name);
|
||||
var startPath = Model.ProviderPath ?? defaultPath;
|
||||
|
||||
var dialog = new OpenFileDialog()
|
||||
{
|
||||
Title = $"Choose {assemblyName} provider assembly",
|
||||
DefaultExt = Path.GetExtension(assemblyName),
|
||||
FileName = Model.ProviderPath,
|
||||
CheckPathExists = true,
|
||||
Filter = $"{assemblyName}|{assemblyName}|All Files(*.*)|*.*",
|
||||
InitialDirectory = startPath == null ? null : Path.GetDirectoryName(startPath)
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.ProviderPath = dialog.FileName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.LinqToDBTab"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:LinqToDBModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<GroupBox Header="Linq To DB Options">
|
||||
<StackPanel Margin="5">
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding OptimizeJoins}"
|
||||
ToolTip="Allows Linq To DB to remove unnecessary JOINs from LINQ queries" >
|
||||
<TextBlock>Enable JOINs optimization in LINQ queries (<Hyperlink
|
||||
RequestNavigate="Url_Click"
|
||||
NavigateUri="https://linq2db.github.io/api/LinqToDB.Common.Configuration.Linq.html#LinqToDB_Common_Configuration_Linq_OptimizeJoins">
|
||||
<TextBlock Text="details"/>
|
||||
</Hyperlink>)
|
||||
</TextBlock>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</UserControl>
|
|
@ -0,0 +1,22 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class LinqToDBTab
|
||||
{
|
||||
public LinqToDBTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Url_Click(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.ScaffoldTab"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:ScaffoldModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<GroupBox Header="Scaffold Options">
|
||||
<StackPanel Margin="5">
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding Capitalize}"
|
||||
Content="Convert column property name to PascalCase"
|
||||
ToolTip="When option is not set property name generated as-is based on column name. Otherwise we will try to generate name in pascal case, e.g. for column with user_id name property name will be UserId when option enabled and user_id when disabled" />
|
||||
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding Pluralize}"
|
||||
Content="Pluralize table and one-to-many association property names in model"
|
||||
ToolTip="When option is set we will try to convert name to plural form when it has singular form" />
|
||||
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding UseProviderTypes}"
|
||||
Content="Use ADO.NET provider custom types"
|
||||
ToolTip="Some ADO.NET providers provide own types to store database values of some database types. Usually it is done when standard .NET type precision or range is not enough to store values from database without data loss or overflow." />
|
||||
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding ClickHouseUseStrings}"
|
||||
Visibility="{Binding ClickHouseVisibility}"
|
||||
Content="[ClickHouse] Use string for FixedString(X) database type"
|
||||
ToolTip="By default byte[] type used in model for columns of FixedString(X) database type." />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</UserControl>
|
|
@ -0,0 +1,9 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class ScaffoldTab
|
||||
{
|
||||
public ScaffoldTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.SchemaTab"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:SchemaModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<GroupBox Header="Database Schema Options">
|
||||
<StackPanel Margin="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<us:UniqueStringListControl DataContext="{Binding Schemas}" Grid.Column="0" Margin="0 0 5 0" />
|
||||
<us:UniqueStringListControl DataContext="{Binding Catalogs}" Grid.Column="1" Margin="5 0 0 0" />
|
||||
</Grid>
|
||||
|
||||
<CheckBox Margin="5" Content="Include associations" IsChecked="{Binding LoadForeignKeys}" ToolTip="Enables generation of association properties based on database foreign keys" />
|
||||
<CheckBox Margin="5" Content="Include stored procedures" IsChecked="{Binding LoadProcedures}" ToolTip="Enables generation of methods for stored procedures" Click="ProcLoad_Click" />
|
||||
<CheckBox Margin="5" Content="Include table functions" IsChecked="{Binding LoadTableFunctions}" ToolTip="Enables generation of methods for table functions" Click="ProcLoad_Click" />
|
||||
<CheckBox Margin="5" Content="Include scalar functions" IsChecked="{Binding LoadScalarFunctions}" ToolTip="Enables generation of methods for scalar functions" />
|
||||
<CheckBox Margin="5" Content="Include aggregate functions" IsChecked="{Binding LoadAggregateFunctions}" ToolTip="Enables generation of methods for aggregate functions" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</UserControl>
|
|
@ -0,0 +1,22 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class SchemaTab
|
||||
{
|
||||
public SchemaTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ProcLoad_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (((CheckBox)sender).IsChecked == true)
|
||||
{
|
||||
Notification.Warning(
|
||||
Window.GetWindow(this),
|
||||
"Including Stored Procedures or Table Functions may be dangerous if they contain non-transactional logic because driver needs to execute them for returned table schema population.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<Window
|
||||
x:Class = "LinqToDB.LINQPad.UI.SettingsDialog"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
Title = "LINQ to DB Connection"
|
||||
mc:Ignorable = "d"
|
||||
Width = "550"
|
||||
Height = "600"
|
||||
ResizeMode = "NoResize"
|
||||
WindowStartupLocation = "CenterScreen"
|
||||
FontSize = "14"
|
||||
d:DataContext = "{d:DesignInstance us:SettingsModel, IsDesignTimeCreatable=true}"
|
||||
Icon = "{Binding About.Logo}"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TabControl Grid.Row="0" SelectionChanged="TabControl_SelectionChanged" Name="_tabControl" Padding="10 5">
|
||||
<TabItem Header = "Connection" Visibility="{Binding StaticConnection.Visibility}" IsSelected="{Binding StaticConnection.IsSelected}">
|
||||
<us:StaticConnectionTab DataContext="{Binding StaticConnection}" />
|
||||
</TabItem>
|
||||
<TabItem Header = "Connection" Visibility="{Binding DynamicConnection.Visibility}" IsSelected="{Binding DynamicConnection.IsSelected}">
|
||||
<us:DynamicConnectionTab DataContext="{Binding DynamicConnection}" />
|
||||
</TabItem>
|
||||
<TabItem Header = "Schema" Visibility="{Binding Schema.Visibility}">
|
||||
<us:SchemaTab DataContext="{Binding Schema}" />
|
||||
</TabItem>
|
||||
<TabItem Header = "Scaffold" Visibility="{Binding Scaffold.Visibility}">
|
||||
<us:ScaffoldTab DataContext="{Binding Scaffold}" />
|
||||
</TabItem>
|
||||
<TabItem Header = "Linq To DB">
|
||||
<us:LinqToDBTab DataContext="{Binding LinqToDB}" />
|
||||
</TabItem>
|
||||
<TabItem Header = "About">
|
||||
<us:AboutTab DataContext="{Binding About}" />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<Grid Grid.Row="1" VerticalAlignment="Bottom" Margin="10 0 10 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="80" />
|
||||
<ColumnDefinition Width="80" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0" Width="65" HorizontalAlignment="Left" Content="Test" Click="Click_Test" Name="_testButton" Visibility="Hidden" />
|
||||
<Button Grid.Column="1" Width="65" HorizontalAlignment="Right" Content="OK" IsDefault="True" Click="Click_Save" />
|
||||
<Button Grid.Column="2" Width="65" HorizontalAlignment="Right" Content="Cancel" IsCancel="True" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -0,0 +1,100 @@
|
|||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class SettingsDialog
|
||||
{
|
||||
private readonly Func<SettingsModel, Exception?>? _connectionTester;
|
||||
private readonly string? _testErrorMessage;
|
||||
|
||||
public SettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private SettingsModel Model => (SettingsModel)DataContext;
|
||||
|
||||
SettingsDialog(SettingsModel model, Func<SettingsModel, Exception?> connectionTester, string testErrorMessage)
|
||||
: this()
|
||||
{
|
||||
DataContext = model;
|
||||
_connectionTester = connectionTester;
|
||||
_testErrorMessage = testErrorMessage;
|
||||
|
||||
model.DynamicConnection.PropertyChanged += DynamicConnection_PropertyChanged;
|
||||
}
|
||||
|
||||
private void DynamicConnection_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(Model.DynamicConnection.Database):
|
||||
Model.Scaffold.UpdateClickHouseVisibility();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Show(SettingsModel model, Func<SettingsModel, Exception?> connectionTester, string testErrorMessage)
|
||||
{
|
||||
return new SettingsDialog(model, connectionTester, testErrorMessage).ShowDialog() == true;
|
||||
}
|
||||
|
||||
private void Click_Save(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_connectionTester == null)
|
||||
{
|
||||
DialogResult = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// test configured connection and ask for confirmation on error
|
||||
Exception? ex;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
ex = _connectionTester(Model);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
if (ex == null
|
||||
|| Notification.YesNo(this, $"{_testErrorMessage ?? "Connection to database failed"} Save anyway?\r\n\r\n{ex.Message}", "Error", icon: MessageBoxImage.Stop))
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Click_Test(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_connectionTester != null)
|
||||
{
|
||||
Exception? ex;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
ex = _connectionTester(Model);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
if (ex == null)
|
||||
Notification.Info(this, "Successful!", "Connection Test");
|
||||
else
|
||||
Notification.Error(this, ex.Message, "Connection Test Error");
|
||||
}
|
||||
}
|
||||
|
||||
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
_testButton.Visibility = _tabControl.SelectedItem is TabItem ti && ti.Content is DynamicConnectionTab or StaticConnectionTab ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.SharedConnectionOptions"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:ConnectionModelBase, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<GroupBox Header="Connection Options">
|
||||
<StackPanel Margin="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="Name" ToolTip="Connection name (optional)"/>
|
||||
<TextBox Padding="5" Grid.Column="1" Text="{Binding Name}" ToolTip="Connection name (optional)" />
|
||||
</Grid>
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CheckBox Grid.Column="0" Content="Save connection" IsChecked="{Binding Persistent}" ToolTip="When not checked connection settings will be lost after LINQPad closed" />
|
||||
<CheckBox Grid.Column="1" Content="Production data" IsChecked="{Binding Production}" ToolTip="LINQPad will show 'Production' label in tree and query window as warning" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</UserControl>
|
|
@ -0,0 +1,9 @@
|
|||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class SharedConnectionOptions
|
||||
{
|
||||
public SharedConnectionOptions()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<UserControl
|
||||
x:Class ="LinqToDB.LINQPad.UI.StaticConnectionTab"
|
||||
x:ClassModifier ="internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:StaticConnectionModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
|
||||
<StackPanel>
|
||||
<us:SharedConnectionOptions />
|
||||
|
||||
<GroupBox Header="Precompiled Model Options">
|
||||
<StackPanel Margin="5">
|
||||
<Label>Assembly path</Label>
|
||||
<DockPanel ToolTip="Path to assembly with custom data model">
|
||||
<Button DockPanel.Dock="Right" Margin="5 0 0 0" Padding="10 5" Content="Select" Click="Click_SelectAssembly" />
|
||||
<TextBox Padding="5" Text="{Binding ContextAssemblyPath}" />
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel Margin="0 5 0 0" ToolTip="Full name of data model context class. Class must be public class, based on LinqToDB.Data.DataConnection, LinqToDB.DataContext or any other class implementing LinqToDB.IDataContext with default constructor or constructor that accepts configuration name">
|
||||
<Label>Context</Label>
|
||||
<ComboBox Width="380" Margin="10 0 0 0" ItemsSource="{Binding ContextTypes}" SelectedItem="{Binding ContextTypeName}" HorizontalAlignment="Right" />
|
||||
</DockPanel>
|
||||
|
||||
<Label>
|
||||
<TextBlock>
|
||||
Configuration file in
|
||||
<Hyperlink NavigateUri="https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings-and-configuration-files#the-connectionstrings-section" RequestNavigate="Url_Click">app.config</Hyperlink> or
|
||||
<Hyperlink NavigateUri="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#appsettingsjson" RequestNavigate="Url_Click">appsettings.json</Hyperlink> format
|
||||
</TextBlock>
|
||||
</Label>
|
||||
<DockPanel ToolTip="Path to app.config or config.json file with connection strings">
|
||||
<Button DockPanel.Dock="Right" Margin="5 0 0 0" Padding="10 5" Content="Select" Click="Click_SelectConfig" />
|
||||
<TextBox Padding="5" Text="{Binding ConfigurationPath}" />
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel Margin="0 5 0 0" ToolTip="Name of connection string from configuration file to pass to context class constructor">
|
||||
<Label>Configuration</Label>
|
||||
<ComboBox Width="380" Margin="10 0 0 0" ItemsSource="{Binding Configurations}" SelectedItem="{Binding ConfigurationName}" HorizontalAlignment="Right" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -0,0 +1,218 @@
|
|||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Navigation;
|
||||
using LINQPad.Extensibility.DataContext;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LinqToDB.LINQPad.UI;
|
||||
|
||||
internal sealed partial class StaticConnectionTab
|
||||
{
|
||||
private const string IDATACONTEXT_NAME = $"{nameof(LinqToDB)}.{nameof(IDataContext)}";
|
||||
|
||||
private StaticConnectionModel Model => (StaticConnectionModel)DataContext;
|
||||
|
||||
public StaticConnectionTab()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
DataContextChanged += StaticConnectionTab_DataContextChanged;
|
||||
}
|
||||
|
||||
private void StaticConnectionTab_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
// delayed init
|
||||
LoadContextTypes();
|
||||
LoadConfigurations();
|
||||
Model.PropertyChanged += Model_PropertyChanged;
|
||||
}
|
||||
|
||||
private void Model_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(Model.ContextAssemblyPath):
|
||||
LoadContextTypes();
|
||||
LoadConfigurations();
|
||||
break;
|
||||
case nameof(Model.ConfigurationPath):
|
||||
LoadConfigurations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadContextTypes()
|
||||
{
|
||||
Model.ContextTypes.Clear();
|
||||
|
||||
if (Model.ContextAssemblyPath != null)
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var assembly = DataContextDriver.LoadAssemblySafely(Model.ContextAssemblyPath);
|
||||
// as referenced linq2db assembly from context could be different version than
|
||||
// linq2db assembly from current process
|
||||
// we cannot compare types directly and should use by-name comparison
|
||||
foreach (var type in assembly.GetExportedTypes())
|
||||
{
|
||||
foreach (var iface in type.GetInterfaces())
|
||||
{
|
||||
if (iface.FullName == IDATACONTEXT_NAME)
|
||||
Model.ContextTypes.Add(type.FullName!);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error(Window.GetWindow(this), ex.Message, "Context assembly load error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadConfigurations()
|
||||
{
|
||||
Model.Configurations.Clear();
|
||||
|
||||
// try to load appsettings.json
|
||||
if (Model.ConfigurationPath != null
|
||||
&& Model.ConfigurationPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var config = AppConfig.LoadJson(Model.ConfigurationPath!);
|
||||
|
||||
if (config.ConnectionStrings.Any())
|
||||
foreach (var cs in config.ConnectionStrings)
|
||||
Model.Configurations.Add(cs.Name);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error(Window.GetWindow(this), ex.Message, "JSON configuration file read error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
|
||||
// try to load custom app.config
|
||||
else if (Model.ConfigurationPath != null)
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var configMap = new ExeConfigurationFileMap();
|
||||
configMap.ExeConfigFilename = Model.ConfigurationPath;
|
||||
var config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
|
||||
|
||||
foreach (var cs in config.ConnectionStrings.ConnectionStrings.Cast<ConnectionStringSettings>())
|
||||
Model.Configurations.Add(cs.Name);
|
||||
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error(Window.GetWindow(this), ex.Message, "Custom app.config file read error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
|
||||
// try to load default app.config
|
||||
else if (Model.ContextAssemblyPath != null)
|
||||
{
|
||||
var oldCursor = Cursor;
|
||||
|
||||
try
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var config = ConfigurationManager.OpenExeConfiguration(Model.ContextAssemblyPath);
|
||||
|
||||
foreach (var cs in config.ConnectionStrings.ConnectionStrings.Cast<ConnectionStringSettings>())
|
||||
Model.Configurations.Add(cs.Name);
|
||||
|
||||
Model.ConfigurationPath = config.FilePath;
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Error(Window.GetWindow(this), ex.Message, "Default app.config file read error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mouse.OverrideCursor = oldCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Click_SelectAssembly(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model == null)
|
||||
return;
|
||||
|
||||
var dialog = new OpenFileDialog()
|
||||
{
|
||||
Title = "Choose assembly with data model",
|
||||
DefaultExt = ".dll",
|
||||
FileName = Model.ContextAssemblyPath,
|
||||
CheckPathExists = true,
|
||||
Filter = "Assembly files (*.dll, *.exe)|*.dll;*.exe|All Files(*.*)|*.*",
|
||||
InitialDirectory = Model.ContextAssemblyPath == null ? null : Path.GetDirectoryName(Model.ContextAssemblyPath)
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.ContextAssemblyPath = dialog.FileName;
|
||||
}
|
||||
|
||||
void Click_SelectConfig(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Model != null)
|
||||
{
|
||||
var dialog = new OpenFileDialog()
|
||||
{
|
||||
Title = "Choose application config file",
|
||||
DefaultExt = ".config",
|
||||
FileName = Model.ConfigurationPath,
|
||||
CheckPathExists = true,
|
||||
Filter = "Configuration files (*.json, *.config)|*.json;*.config|All Files(*.*)|*.*",
|
||||
InitialDirectory = Model.ConfigurationPath == null ? null : Path.GetDirectoryName(Model.ConfigurationPath)
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
Model.ConfigurationPath = dialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
private void Url_Click(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<UserControl
|
||||
x:Class = "LinqToDB.LINQPad.UI.UniqueStringListControl"
|
||||
x:ClassModifier = "internal"
|
||||
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:us = "clr-namespace:LinqToDB.LINQPad.UI"
|
||||
mc:Ignorable = "d"
|
||||
d:DataContext = "{d:DesignInstance us:UniqueStringListModel, IsDesignTimeCreatable=true}"
|
||||
>
|
||||
|
||||
<GroupBox Header="{Binding Title}">
|
||||
<StackPanel Margin="5" ToolTip="{Binding ToolTip}">
|
||||
<CheckBox Margin="5" Content="Include" IsChecked="{Binding Include}" />
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="80" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Padding="5" Name="_textBox" Grid.Column="0" KeyDown="KeyDown_TextBox" GotFocus="GotFocus_TextBox" />
|
||||
<Button Margin="10 0 0 0" Name="_button" Grid.Column="1" Content="Add" Click="Click_Button" />
|
||||
</Grid>
|
||||
<ListBox Margin="5" Height="150" Name="_listBox" ItemsSource="{Binding Items}" KeyDown="KeyDown_ListBox" SelectionChanged="SelectionChanged_ListBox" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</UserControl>
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче