From 9b5e34032c0e988fe8633eeb192b8df3d80f35ea Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Tue, 18 Dec 2018 20:09:52 +0800 Subject: [PATCH] =?UTF-8?q?pgsql/mysql/sqlserver=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 63 ++ .gitignore | 246 +++++++ Docs/Generator.md | 92 +++ Docs/codefirst.md | 135 ++++ Docs/dbfirst.md | 112 +++ Docs/delete.md | 80 +++ Docs/insert.md | 77 +++ Docs/select.md | 48 ++ Docs/update.md | 127 ++++ FreeSql.Tests/Class1.cs | 126 ++++ .../Extensions/StringExtensionsTest.cs | 65 ++ FreeSql.Tests/FreeSql.Tests.csproj | 19 + .../Generator/MySqlTemplateGeneratorTest.cs | 27 + FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs | 72 ++ FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs | 85 +++ FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs | 501 ++++++++++++++ FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs | 107 +++ FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs | 54 ++ FreeSql.Tests/MySql/MySqlCodeFirstTest.cs | 350 ++++++++++ FreeSql.Tests/MySql/MySqlDbFirstTest.cs | 21 + FreeSql.Tests/MySql/MySqlExpressionTest.cs | 82 +++ FreeSql.Tests/UnitTest1.cs | 150 ++++ FreeSql.Tests/g.cs | 15 + FreeSql.sln | 76 +++ FreeSql/DataAnnotations/ColumnAttribute.cs | 32 + FreeSql/DataAnnotations/TableAttribute.cs | 19 + FreeSql/DatabaseModel/DBColumnInfo.cs | 52 ++ FreeSql/DatabaseModel/DBTableInfo.cs | 52 ++ FreeSql/DatabaseModel/DbForeignInfo.cs | 13 + FreeSql/Extensions/NpgsqlTypesExtensions.cs | 39 ++ FreeSql/Extensions/StringExtensions.cs | 31 + FreeSql/FreeSql.csproj | 21 + FreeSql/FreeSqlBuilder.cs | 66 ++ FreeSql/FreeUtil.cs | 34 + FreeSql/Generator/TemplateEngin.cs | 640 ++++++++++++++++++ FreeSql/Generator/TemplateGenerator.cs | 71 ++ FreeSql/Interface/Curd/IDelete.cs | 57 ++ FreeSql/Interface/Curd/IInsert.cs | 55 ++ FreeSql/Interface/Curd/ISelect/ISelect0.cs | 198 ++++++ FreeSql/Interface/Curd/ISelect/ISelect1.cs | 230 +++++++ FreeSql/Interface/Curd/ISelect/ISelect10.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect2.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect3.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect4.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect5.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect6.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect7.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect8.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelect9.cs | 26 + FreeSql/Interface/Curd/ISelect/ISelectFrom.cs | 64 ++ FreeSql/Interface/Curd/IUpdate.cs | 100 +++ FreeSql/Interface/IAdo.cs | 138 ++++ FreeSql/Interface/ICache.cs | 118 ++++ FreeSql/Interface/ICodeFirst.cs | 43 ++ FreeSql/Interface/IDasql.cs | 100 +++ FreeSql/Interface/iDbFirst.cs | 70 ++ FreeSql/Internal/CommonExpression.cs | 256 +++++++ .../CommonProvider/AdoProvider/AdoProvider.cs | 267 ++++++++ .../AdoProvider/AdoProviderAsync.cs | 215 ++++++ .../AdoProvider/AdoProviderTransaction.cs | 152 +++++ .../AdoProvider/AdoProviderUtils.cs | 19 + .../Internal/CommonProvider/CacheProvider.cs | 176 +++++ .../Internal/CommonProvider/DeleteProvider.cs | 52 ++ .../Internal/CommonProvider/InsertProvider.cs | 90 +++ .../SelectProvider/Select0Provider.cs | 263 +++++++ .../SelectProvider/Select10Provider.cs | 47 ++ .../SelectProvider/Select1Provider.cs | 114 ++++ .../SelectProvider/Select2Provider.cs | 39 ++ .../SelectProvider/Select3Provider.cs | 40 ++ .../SelectProvider/Select4Provider.cs | 41 ++ .../SelectProvider/Select5Provider.cs | 42 ++ .../SelectProvider/Select6Provider.cs | 43 ++ .../SelectProvider/Select7Provider.cs | 44 ++ .../SelectProvider/Select8Provider.cs | 45 ++ .../SelectProvider/Select9Provider.cs | 46 ++ .../Internal/CommonProvider/UpdateProvider.cs | 172 +++++ FreeSql/Internal/CommonUtils.cs | 119 ++++ FreeSql/Internal/Model/ColumnInfo.cs | 11 + .../Internal/Model/ReadAnonymousTypeInfo.cs | 14 + FreeSql/Internal/Model/SelectColumnInfo.cs | 10 + FreeSql/Internal/Model/SelectTableInfo.cs | 9 + FreeSql/Internal/Model/TableInfo.cs | 18 + FreeSql/Internal/Utils.cs | 144 ++++ FreeSql/MySql/Curd/MySqlDelete.cs | 25 + FreeSql/MySql/Curd/MySqlInsert.cs | 28 + FreeSql/MySql/Curd/MySqlSelect.cs | 114 ++++ FreeSql/MySql/Curd/MySqlUpdate.cs | 49 ++ FreeSql/MySql/MySqlAdo/MySqlAdo.cs | 63 ++ FreeSql/MySql/MySqlAdo/MySqlAdoExtensions.cs | 36 + FreeSql/MySql/MySqlAdo/MySqlConnectionPool.cs | 125 ++++ FreeSql/MySql/MySqlAdo/MygisTypes.cs | 308 +++++++++ FreeSql/MySql/MySqlCodeFirst.cs | 184 +++++ FreeSql/MySql/MySqlDbFirst.cs | 367 ++++++++++ FreeSql/MySql/MySqlExpression.cs | 159 +++++ FreeSql/MySql/MySqlProvider.cs | 50 ++ FreeSql/MySql/MySqlUtils.cs | 48 ++ FreeSql/PostgreSQL/Curd/PostgreSQLDelete.cs | 25 + FreeSql/PostgreSQL/Curd/PostgreSQLInsert.cs | 30 + FreeSql/PostgreSQL/Curd/PostgreSQLSelect.cs | 116 ++++ FreeSql/PostgreSQL/Curd/PostgreSQLUpdate.cs | 49 ++ .../PostgreSQL/PostgreSQLAdo/PostgreSQLAdo.cs | 61 ++ .../PostgreSQLAdo/PostgreSQLConnectionPool.cs | 153 +++++ .../PostgreSQLAdo/PostgreSQLTypesConverter.cs | 140 ++++ .../PostgreSQLTypesExtensions.cs | 18 + FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs | 188 +++++ FreeSql/PostgreSQL/PostgreSQLDbFirst.cs | 366 ++++++++++ FreeSql/PostgreSQL/PostgreSQLExpression.cs | 159 +++++ FreeSql/PostgreSQL/PostgreSQLProvider.cs | 50 ++ FreeSql/PostgreSQL/PostgreSQLUtils.cs | 48 ++ FreeSql/SqlServer/Curd/SqlServerDelete.cs | 34 + FreeSql/SqlServer/Curd/SqlServerInsert.cs | 37 + FreeSql/SqlServer/Curd/SqlServerSelect.cs | 126 ++++ FreeSql/SqlServer/Curd/SqlServerUpdate.cs | 58 ++ .../SqlServer/SqlServerAdo/SqlServerAdo.cs | 61 ++ .../SqlServerAdo/SqlServerConnectionPool.cs | 147 ++++ FreeSql/SqlServer/SqlServerCodeFirst.cs | 164 +++++ FreeSql/SqlServer/SqlServerDbFirst.cs | 383 +++++++++++ FreeSql/SqlServer/SqlServerExpression.cs | 82 +++ FreeSql/SqlServer/SqlServerProvider.cs | 50 ++ FreeSql/SqlServer/SqlServerUtils.cs | 49 ++ LICENSE | 21 + Templates/MySql/include/enumtype.tpl | 71 ++ .../rich-entity-navigation-object/Const.cs | 5 + .../Models/for-table.cs.freesql | 239 +++++++ .../rich-entity-navigation-object/readme.md | 0 .../Models/for-table.cs.freesql | 59 ++ .../simple-entity-navigation-object/readme.md | 0 .../simple-entity/Models/for-table.cs.freesql | 33 + Templates/MySql/simple-entity/readme.md | 0 readme.md | 10 + 130 files changed, 12283 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Docs/Generator.md create mode 100644 Docs/codefirst.md create mode 100644 Docs/dbfirst.md create mode 100644 Docs/delete.md create mode 100644 Docs/insert.md create mode 100644 Docs/select.md create mode 100644 Docs/update.md create mode 100644 FreeSql.Tests/Class1.cs create mode 100644 FreeSql.Tests/Extensions/StringExtensionsTest.cs create mode 100644 FreeSql.Tests/FreeSql.Tests.csproj create mode 100644 FreeSql.Tests/Generator/MySqlTemplateGeneratorTest.cs create mode 100644 FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs create mode 100644 FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs create mode 100644 FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs create mode 100644 FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs create mode 100644 FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs create mode 100644 FreeSql.Tests/MySql/MySqlCodeFirstTest.cs create mode 100644 FreeSql.Tests/MySql/MySqlDbFirstTest.cs create mode 100644 FreeSql.Tests/MySql/MySqlExpressionTest.cs create mode 100644 FreeSql.Tests/UnitTest1.cs create mode 100644 FreeSql.Tests/g.cs create mode 100644 FreeSql.sln create mode 100644 FreeSql/DataAnnotations/ColumnAttribute.cs create mode 100644 FreeSql/DataAnnotations/TableAttribute.cs create mode 100644 FreeSql/DatabaseModel/DBColumnInfo.cs create mode 100644 FreeSql/DatabaseModel/DBTableInfo.cs create mode 100644 FreeSql/DatabaseModel/DbForeignInfo.cs create mode 100644 FreeSql/Extensions/NpgsqlTypesExtensions.cs create mode 100644 FreeSql/Extensions/StringExtensions.cs create mode 100644 FreeSql/FreeSql.csproj create mode 100644 FreeSql/FreeSqlBuilder.cs create mode 100644 FreeSql/FreeUtil.cs create mode 100644 FreeSql/Generator/TemplateEngin.cs create mode 100644 FreeSql/Generator/TemplateGenerator.cs create mode 100644 FreeSql/Interface/Curd/IDelete.cs create mode 100644 FreeSql/Interface/Curd/IInsert.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect0.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect1.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect10.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect2.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect3.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect4.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect5.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect6.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect7.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect8.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelect9.cs create mode 100644 FreeSql/Interface/Curd/ISelect/ISelectFrom.cs create mode 100644 FreeSql/Interface/Curd/IUpdate.cs create mode 100644 FreeSql/Interface/IAdo.cs create mode 100644 FreeSql/Interface/ICache.cs create mode 100644 FreeSql/Interface/ICodeFirst.cs create mode 100644 FreeSql/Interface/IDasql.cs create mode 100644 FreeSql/Interface/iDbFirst.cs create mode 100644 FreeSql/Internal/CommonExpression.cs create mode 100644 FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs create mode 100644 FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs create mode 100644 FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs create mode 100644 FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs create mode 100644 FreeSql/Internal/CommonProvider/CacheProvider.cs create mode 100644 FreeSql/Internal/CommonProvider/DeleteProvider.cs create mode 100644 FreeSql/Internal/CommonProvider/InsertProvider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select10Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select2Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select3Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select4Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select5Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select6Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select7Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select8Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/SelectProvider/Select9Provider.cs create mode 100644 FreeSql/Internal/CommonProvider/UpdateProvider.cs create mode 100644 FreeSql/Internal/CommonUtils.cs create mode 100644 FreeSql/Internal/Model/ColumnInfo.cs create mode 100644 FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs create mode 100644 FreeSql/Internal/Model/SelectColumnInfo.cs create mode 100644 FreeSql/Internal/Model/SelectTableInfo.cs create mode 100644 FreeSql/Internal/Model/TableInfo.cs create mode 100644 FreeSql/Internal/Utils.cs create mode 100644 FreeSql/MySql/Curd/MySqlDelete.cs create mode 100644 FreeSql/MySql/Curd/MySqlInsert.cs create mode 100644 FreeSql/MySql/Curd/MySqlSelect.cs create mode 100644 FreeSql/MySql/Curd/MySqlUpdate.cs create mode 100644 FreeSql/MySql/MySqlAdo/MySqlAdo.cs create mode 100644 FreeSql/MySql/MySqlAdo/MySqlAdoExtensions.cs create mode 100644 FreeSql/MySql/MySqlAdo/MySqlConnectionPool.cs create mode 100644 FreeSql/MySql/MySqlAdo/MygisTypes.cs create mode 100644 FreeSql/MySql/MySqlCodeFirst.cs create mode 100644 FreeSql/MySql/MySqlDbFirst.cs create mode 100644 FreeSql/MySql/MySqlExpression.cs create mode 100644 FreeSql/MySql/MySqlProvider.cs create mode 100644 FreeSql/MySql/MySqlUtils.cs create mode 100644 FreeSql/PostgreSQL/Curd/PostgreSQLDelete.cs create mode 100644 FreeSql/PostgreSQL/Curd/PostgreSQLInsert.cs create mode 100644 FreeSql/PostgreSQL/Curd/PostgreSQLSelect.cs create mode 100644 FreeSql/PostgreSQL/Curd/PostgreSQLUpdate.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLAdo.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLConnectionPool.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesConverter.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesExtensions.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLDbFirst.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLExpression.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLProvider.cs create mode 100644 FreeSql/PostgreSQL/PostgreSQLUtils.cs create mode 100644 FreeSql/SqlServer/Curd/SqlServerDelete.cs create mode 100644 FreeSql/SqlServer/Curd/SqlServerInsert.cs create mode 100644 FreeSql/SqlServer/Curd/SqlServerSelect.cs create mode 100644 FreeSql/SqlServer/Curd/SqlServerUpdate.cs create mode 100644 FreeSql/SqlServer/SqlServerAdo/SqlServerAdo.cs create mode 100644 FreeSql/SqlServer/SqlServerAdo/SqlServerConnectionPool.cs create mode 100644 FreeSql/SqlServer/SqlServerCodeFirst.cs create mode 100644 FreeSql/SqlServer/SqlServerDbFirst.cs create mode 100644 FreeSql/SqlServer/SqlServerExpression.cs create mode 100644 FreeSql/SqlServer/SqlServerProvider.cs create mode 100644 FreeSql/SqlServer/SqlServerUtils.cs create mode 100644 LICENSE create mode 100644 Templates/MySql/include/enumtype.tpl create mode 100644 Templates/MySql/rich-entity-navigation-object/Const.cs create mode 100644 Templates/MySql/rich-entity-navigation-object/Models/for-table.cs.freesql create mode 100644 Templates/MySql/rich-entity-navigation-object/readme.md create mode 100644 Templates/MySql/simple-entity-navigation-object/Models/for-table.cs.freesql create mode 100644 Templates/MySql/simple-entity-navigation-object/readme.md create mode 100644 Templates/MySql/simple-entity/Models/for-table.cs.freesql create mode 100644 Templates/MySql/simple-entity/readme.md create mode 100644 readme.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..616428fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,246 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +package-lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ \ No newline at end of file diff --git a/Docs/Generator.md b/Docs/Generator.md new file mode 100644 index 00000000..7c21ead4 --- /dev/null +++ b/Docs/Generator.md @@ -0,0 +1,92 @@ +# 生成器 + +生成器是基于 dbfirst 开发的辅助工具,适用老项目一键生成实体。生成器采用模板的方式,作者实现了三种生成模板: + +| 模板名称 | 路径 | 类型映射 | 外键导航属性 | 缓存管理 | 失血 | 贫血 | 充血 | +| ------------- | - | - |- | - |- | - |- | +| simple-entity | ../Templates/MySql/simple-entity | √ | X | X | √ | X | X | +| simple-entity-navigation-object | ../Templates/MySql/simple-entity-navigation-object | √ | √ | X | √ | X | X | +| rich-entity-navigation-object | ../Templates/MySql/rich-entity-navigation-object | √ | √ | √ | X | √ | X | + +> 更多模板逐步开发中。。。 + +```csharp +//定义 mysql FreeSql +var mysql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10") + .Build(); + +//创建模板生成类现实 +var gen = new FreeSql.Generator.TemplateGenerator(); +gen.Build(mysql.DbFirst, + @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", //模板目录(事先下载) + @"C:\Users\28810\Desktop\新建文件夹 (9)", //生成后保存的目录 + "cccddd" //数据库 +); +``` + +## 模板语法 + +```html + + +{#title} + + + + +{#表达式} +{##表达式} 当表达式可能发生runtime错误时使用,性能没有上面的高 + + +{include ../header.html} +
+

aaa

+

bbb {#i}

+

ccc {#i}

+
+ + +{module module_name1 parms1, 2, 3...} +{/module} +{module module_name2 parms1, 2, 3...} +{/module} + + +{import ../module.html as myname} +{#myname.module_name(parms1, 2, 3...)} + + +{extends ../inc/layout.html} +{block body}{/block} + + +{% +for (var a = 0; a < 100; a++) + print(a); +%} + + +{if i === 50} +{elseif i > 60} +{else} +{/if} + + +{for i 1,101} 可自定义名 {for index2 表达式1 in 表达式2} + +{for item,index in items} 可选参数称 index + 可自定义名 {for item2, index99 in 数组表达式} + +{for key,item,index on json} 可选参数 item, index, + 可自定义名 {for key2, item2, index99 in 对象表达式} +{/for} + + +{miss} +此块内容不被bmw.js解析 +{/miss} + + + +``` \ No newline at end of file diff --git a/Docs/codefirst.md b/Docs/codefirst.md new file mode 100644 index 00000000..5ca33e0a --- /dev/null +++ b/Docs/codefirst.md @@ -0,0 +1,135 @@ +# CodeFirst + +### 支持的类型 + +bool, byte, short, int, long, byte, ushort, uint, ulong, double, float, decimal, int + +bool?, byte?, short?, int?, long?, byte?, ushort?, uint?, ulong?, double?, float?, decimal?, int? + +TimeSpan, DateTime + +TimeSpan?, DateTime? + +byte[], string + +MygisPoint, MygisLineString, MygisPolygon, MygisMultiPoint, MygisMultiLineString, MygisMultiPolygon + +```csharp +var mysql = new MySql(null, null, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=3", null, null); +``` + +### 自动同步实体结构【开发环境必备】 + +自动同步实体结构到数据库,程序运行中检查实体表是否存在,然后创建或修改 + +```csharp +mysql.CodeFirst.IsAutoSyncDataStructure = true; +``` + +> 此功能默认为开启状态,发布正式环境后,请修改此设置 + +> 虽然【自动同步实体结构】功能开发非常好用,但是有个坏处,就是数据库后面会很乱,没用的字段一大堆 + +### 手工同步实体结构 + +| 实体&表对比 | 添加 | 改名 | 删除 | +| - | - | - | - | +| | √ | √ | X | + +| 实体属性&字段对比 | 添加 | 修改可空 | 修改自增 | 修改类型 | 改名 | 删除 | +| - | - | - | - | - | - | - | +| | √ | √ | √ | √ | √ | X | + +> 为了保证安全,不提供删除字段 + + +1、提供方法对比实体,与数据库中的变化部分 + +```csharp +var t1 = mysql.CodeFirst.GetComparisonDDLStatements(); + +class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + public ushort fusho { get; set; } +} +``` +```sql +CREATE TABLE IF NOT EXISTS `cccddd`.`Topic` ( + `Id` INT(11) NOT NULL AUTO_INCREMENT, + `Clicks` INT(11) NOT NULL, + `Title` VARCHAR(255), + `CreateTime` DATETIME NOT NULL, + `fusho` SMALLINT(5) UNSIGNED NOT NULL, + PRIMARY KEY (`Id`) +) Engine=InnoDB CHARACTER SET utf8; +``` + +2、指定实体的表名 + +指定 Name 后,实体类名变化不影响数据库对应的表 +```csharp +[Table(Name = "tb_topic111")] +class Topic { + //... +} +``` + +3、无指定实体的表名,修改实体类名 + +指定数据库旧的表名,修改实体命名时,同时设置此参数为修改之前的值,CodeFirst才可以正确修改数据库表;否则将视为【创建新表】 + +```csharp +[Table(OldName = "Topic")] +class Topic2 { + //... +} +``` +```sql +ALTER TABLE `cccddd`.`Topic` RENAME TO `cccddd`.`Topic2`; +``` + +4、修改属性的类型 + +把 Id 类型改为 uint 后 +```sql +ALTER TABLE `cccddd`.`Topic2` MODIFY `Id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT; +``` +```csharp +[Column(DbType = "varchar(128)")] +public string Title { get; set; } +``` +```sql +ALTER TABLE `cccddd`.`Topic2` MODIFY `Title2` VARCHAR(128); +``` + +5、指定属性的字段名 + +这样指定后,修改实体的属性名不影响数据库对应的列 +```csharp +[Column(Name = "titl2")] +public string Title { get; set; } +``` + +6、无指定属性的字段名,修改属性名 + +指定数据库旧的列名,修改实体属性命名时,同时设置此参数为修改之前的值,CodeFirst才可以正确修改数据库字段;否则将视为【新增字段】 + +```csharp +[Column(OldName = "Title2")] +public string Title { get; set; } +``` +```sql +ALTER TABLE `cccddd`.`Topic2` CHANGE COLUMN `Title2` `Title` VARCHAR(255); +``` + +7、提供方法同步结构 + +```csharp +var t2 = mysql.CodeFirst.SyncStructure(); +//同步实体类型到数据库 +``` diff --git a/Docs/dbfirst.md b/Docs/dbfirst.md new file mode 100644 index 00000000..b8ba7091 --- /dev/null +++ b/Docs/dbfirst.md @@ -0,0 +1,112 @@ +# DbFirst + +```csharp +MySql mysql = new MySql(null, null, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=3", null, null); +``` + +### 获取所有数据库 + +```csharp +var t1 = mysql.DbFirst.GetDatabases(); +//返回字符串数组, ["cccddd", "test"] +``` + +### 获取指定数据库的表信息 + +```csharp +var t2 = mysql.DbFirst.GetTablesByDatabase(mysql.DbFirst.GetDatabases()[0]); +//返回包括表、列详情、主键、唯一键、索引、外键 +``` + +# 生成器 + +生成器是基于 dbfirst 开发的辅助工具,适用老项目一键生成实体。生成器采用模板的方式,作者实现了三种生成模板: + +| 模板名称 | 路径 | 类型映射 | 外键导航属性 | 缓存管理 | 失血 | 贫血 | 充血 | +| ------------- | - | - |- | - |- | - |- | +| simple-entity | ../Templates/MySql/simple-entity | √ | X | X | √ | X | X | +| simple-entity-navigation-object | ../Templates/MySql/simple-entity-navigation-object | √ | √ | X | √ | X | X | +| rich-entity-navigation-object | ../Templates/MySql/rich-entity-navigation-object | √ | √ | √ | X | √ | X | + +> 更多模板逐步开发中。。。 + +```csharp +//定义 mysql FreeSql +var mysql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10") + .Build(); + +//创建模板生成类现实 +var gen = new FreeSql.Generator.TemplateGenerator(); +gen.Build(mysql.DbFirst, + @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", //模板目录(事先下载) + @"C:\Users\28810\Desktop\新建文件夹 (9)", //生成后保存的目录 + "cccddd" //数据库 +); +``` + +## 模板语法 + +```html + + +{#title} + + + + +{#表达式} +{##表达式} 当表达式可能发生runtime错误时使用,性能没有上面的高 + + +{include ../header.html} +
+

aaa

+

bbb {#i}

+

ccc {#i}

+
+ + +{module module_name1 parms1, 2, 3...} +{/module} +{module module_name2 parms1, 2, 3...} +{/module} + + +{import ../module.html as myname} +{#myname.module_name(parms1, 2, 3...)} + + +{extends ../inc/layout.html} +{block body}{/block} + + +{% +for (var a = 0; a < 100; a++) + print(a); +%} + + +{if i === 50} +{elseif i > 60} +{else} +{/if} + + +{for i 1,101} 可自定义名 {for index2 表达式1 in 表达式2} + +{for item,index in items} 可选参数称 index + 可自定义名 {for item2, index99 in 数组表达式} + +{for key,item,index on json} 可选参数 item, index, + 可自定义名 {for key2, item2, index99 in 对象表达式} +{/for} + + +{miss} +此块内容不被bmw.js解析 +{/miss} + + + +``` \ No newline at end of file diff --git a/Docs/delete.md b/Docs/delete.md new file mode 100644 index 00000000..c12c71c0 --- /dev/null +++ b/Docs/delete.md @@ -0,0 +1,80 @@ +# 删除数据 + +| 方法 | 返回值 | 参数 | 描述 | +| - | - | - | - | +| Where | \ | Lambda | 表达式条件,仅支持实体基础成员(不包含导航对象) | +| Where | \ | string, parms | 原生sql语法条件,Where("id = ?id", new { id = 1 }) | +| Where | \ | T1 \| IEnumerable | 传入实体或集合,将其主键作为条件 | +| WhereExists | \ | ISelect | 子查询是否存在 | +| ToSql | string | | 返回即将执行的SQL语句 | +| ExecuteAffrows | long | | 执行SQL语句,返回影响的行数 | +| ExecuteDeleted | List\ | | 执行SQL语句,返回被删除的记录 | + +### 测试代码 + +```csharp +var mysql = new MySql("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=3"); +IDelete delete => mysql.Delete(); + +[Table(Name = "tb_topic")] +class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } +} +``` + +### 动态条件 +```csharp +Delete(object dywhere) +``` +dywhere 支持 + +* 主键值 +* new[] { 主键值1, 主键值2 } +* Topic对象 +* new[] { Topic对象1, Topic对象2 } +* new { id = 1 } + +```csharp +var t1 = mysql.Delete(new[] { 1, 2 }).ToSql(); +//DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2) + +var t2 = mysql.Delete(new Topic { Id = 1, Title = "test" }).ToSql(); +//DELETE FROM `tb_topic` WHERE (`Id` = 1) + +var t3 = mysql.Delete(new[] { new Topic { Id = 1, Title = "test" }, new Topic { Id = 2, Title = "test" } }).ToSql(); +//DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2) + +var t4 = mysql.Delete(new { id = 1 }).ToSql(); +//DELETE FROM `tb_topic` WHERE (`Id` = 1) +``` + +### 删除条件 + +```csharp +var t5 = delete.Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); +//DELETE FROM `tb_topic` WHERE (`Id` = 1) + +var t6 = delete.Where("id = ?id", new { id = 1 }).ToSql().Replace("\r\n", ""); +//DELETE FROM `tb_topic` WHERE (id = ?id) + +var item = new Topic { Id = 1, Title = "newtitle" }; +var t7 = delete.Where(item).ToSql().Replace("\r\n", ""); +//DELETE FROM `tb_topic` WHERE (`Id` = 1) + +var items = new List(); +for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); +var t8 = delete.Where(items).ToSql().Replace("\r\n", ""); +//DELETE FROM `tb_topic` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10)) +``` + +### 执行命令 + +| 方法 | 返回值 | 参数 | 描述 | +| - | - | - | - | +| ExecuteAffrows | long | | 执行SQL语句,返回影响的行数 | +| ExecuteDeleted | List\ | | 执行SQL语句,返回被删除的记录 | \ No newline at end of file diff --git a/Docs/insert.md b/Docs/insert.md new file mode 100644 index 00000000..fc8fff86 --- /dev/null +++ b/Docs/insert.md @@ -0,0 +1,77 @@ +# 插入数据 + +| 方法 | 返回值 | 参数 | 描述 | +| - | - | - | - | +| AppendData | \ | T1 \| IEnumerable | 追加准备插入的实体 | +| InsertColumns | \ | Lambda | 只插入的列 | +| IgnoreColumns | \ | Lambda | 忽略的列 | +| ToSql | string | | 返回即将执行的SQL语句 | +| ExecuteAffrows | long | | 执行SQL语句,返回影响的行数 | +| ExecuteIdentity | long | | 执行SQL语句,返回自增值 | +| ExecuteInserted | List\ | | 执行SQL语句,返回插入后的记录 | + +### 列优先级 + +> 全部列 < 指定列(InsertColumns) < 忽略列(IgnoreColumns) + +### 测试代码 + +```csharp +var mysql = new MySql("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=3"); +IInsert insert => mysql.Insert(); + +[Table(Name = "tb_topic")] +class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } +} + +var items = new List(); +for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); +``` + +### 插入 + +```csharp +var t1 = insert.AppendData(items.First()).ToSql(); +//INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0) +``` + +### 批量插入 + +```csharp +var t2 = insert.AppendData(items).ToSql(); +//INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0), (?Clicks1, ?Title1, ?CreateTime1), (?Clicks2, ?Title2, ?CreateTime2), (?Clicks3, ?Title3, ?CreateTime3), (?Clicks4, ?Title4, ?CreateTime4), (?Clicks5, ?Title5, ?CreateTime5), (?Clicks6, ?Title6, ?CreateTime6), (?Clicks7, ?Title7, ?CreateTime7), (?Clicks8, ?Title8, ?CreateTime8), (?Clicks9, ?Title9, ?CreateTime9) +``` + +### 只想插入指定的列 + +```csharp +var t3 = insert.AppendData(items).InsertColumns(a => a.Title).ToSql(); +//INSERT INTO `tb_topic`(`Title`) VALUES(?Title0), (?Title1), (?Title2), (?Title3), (?Title4), (?Title5), (?Title6), (?Title7), (?Title8), (?Title9) + +var t4 = insert.AppendData(items).InsertColumns(a =>new { a.Title, a.Clicks }).ToSql(); +//INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9) +``` + +### 忽略列 + +```csharp +var t5 = insert.AppendData(items).IgnoreColumns(a => a.CreateTime).ToSql(); +//INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9) + +var t6 = insert.AppendData(items).IgnoreColumns(a => new { a.Title, a.CreateTime }).ToSql(); +///INSERT INTO `tb_topic`(`Clicks`) VALUES(?Clicks0), (?Clicks1), (?Clicks2), (?Clicks3), (?Clicks4), (?Clicks5), (?Clicks6), (?Clicks7), (?Clicks8), (?Clicks9) +``` + +### 执行命令 + +| 方法 | 返回值 | 描述 | +| - | - | - | +| ExecuteAffrows | long | 执行SQL语句,返回影响的行数 | +| ExecuteIdentity | long | 执行SQL语句,返回自增值 | +| ExecuteInserted | List\ | 执行SQL语句,返回插入后的记录 | diff --git a/Docs/select.md b/Docs/select.md new file mode 100644 index 00000000..49ca66d4 --- /dev/null +++ b/Docs/select.md @@ -0,0 +1,48 @@ +# 查询数据 + +| 方法 | 返回值 | 参数 | 描述 | +| ------------- | - | - | - | +| ToSql | string | | 返回即将执行的SQL语句 | +| ToList | List | | 执行SQL查询,返回 T1 实体所有字段的记录,若存在导航属性则一起查询返回,记录不存在时返回 Count 为 0 的列表 | +| ToList\ | List\ | Lambda | 执行SQL查询,返回指定字段的记录,记录不存在时返回 Count 为 0 的列表 | +| ToList\ | List\ | string field | 执行SQL查询,返回 field 指定字段的记录,并以元组或基础类型(int,string,long)接收,记录不存在时返回 Count 为 0 的列表 | +| ToOne | T1 | | 执行SQL查询,返回 T1 实体所有字段的第一条记录,记录不存在时返回 null | +| Any | bool | | 执行SQL查询,是否有记录 | +| Sum | T | Lambda | 指定一个列求和 | +| Min | T | Lambda | 指定一个列求最小值 | +| Max | T | Lambda | 指定一个列求最大值 | +| Avg | T | Lambda | 指定一个列求平均值 | +| 【分页】 | +| Count | long | | 查询的记录数量 | +| Count | \ | out long | 查询的记录数量,以参数out形式返回 | +| Skip | \ | int offset | 查询向后偏移行数 | +| Offset | \ | int offset | 查询向后偏移行数 | +| Limit | \ | int limit | 查询多少条数据 | +| Take | \ | int limit | 查询多少条数据 | +| Page | \ | int pageIndex, int pageSize | 分页 | +| 【条件】 | +| Where | \ | Lambda | 支持多表查询表达式 | +| WhereIf | \ | bool, Lambda | 支持多表查询表达式 | +| Where | \ | string, parms | 原生sql语法条件,Where("id = ?id", new { id = 1 }) | +| WhereIf | \ | bool, string, parms | 原生sql语法条件,WhereIf(true, "id = ?id", new { id = 1 }) | +| WhereLike | \ | Lambda, string, bool | like 查询条件,where title like '%xxx%' or content like '%xxx%' | +| 【分组】 | +| GroupBy | \ | Lambda | 按选择的列分组,GroupBy(a => a.Name) | GroupBy(a => new{a.Name,a.Time}) | GroupBy(a => new[]{"name","time"}) | +| GroupBy | \ | string, parms | 按原生sql语法分组,GroupBy("concat(name, ?cc)", new { cc = 1 }) | +| Having | \ | string, parms | 按原生sql语法聚合条件过滤,Having("count(name) = ?cc", new { cc = 1 }) | +| 【排序】 | +| OrderBy | \ | Lambda | 按列排序,OrderBy(a => a.Time) | +| OrderByDescending | \ | Lambda | 按列倒向排序,OrderByDescending(a => a.Time) | +| OrderBy | \ | string, parms | 按原生sql语法排序,OrderBy("count(name) + ?cc", new { cc = 1 }) | +| 【联表】 | +| LeftJoin | \ | Lambda | 左联查询,可使用导航属性,或指定关联的实体类型 | +| InnerJoin | \ | Lambda | 联接查询,可使用导航属性,或指定关联的实体类型 | +| RightJoin | \ | Lambda | 右联查询,可使用导航属性,或指定关联的实体类型 | +| LeftJoin | \ | string, parms | 左联查询,使用原生sql语法,LeftJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) | +| InnerJoin | \ | string, parms | 联接查询,使用原生sql语法,InnerJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) | +| RightJoin | \ | string, parms | 右联查询,使用原生sql语法,RightJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) | +| From | \ | Lambda | 多表查询,3个表以上使用非常方便,目前设计最大支持10个表 | +| 【其他】 | +| As | \ | string alias = "a" | 指定别名 | +| Master | \ | | 指定从主库查询(默认查询从库) | +| Caching | \ | int seconds, string key = null | 缓存查询结果 | diff --git a/Docs/update.md b/Docs/update.md new file mode 100644 index 00000000..7ba27d50 --- /dev/null +++ b/Docs/update.md @@ -0,0 +1,127 @@ +# 更新数据 + +| 方法 | 返回值 | 参数 | 描述 | +| - | - | - | - | +| SetSource | \ | T1 \| IEnumerable | 更新数据,设置更新的实体 | +| IgnoreColumns | \ | Lambda | 忽略的列 | +| Set | \ | Lambda, value | 设置列的新值,Set(a => a.Name, "newvalue") | +| Set | \ | Lambda | 设置列的的新值为基础上增加,Set(a => a.Clicks + 1),相当于 clicks=clicks+1; | +| SetRaw | \ | string, parms | 设置值,自定义SQL语法,SetRaw("title = ?title", new { title = "newtitle" }) | +| Where | \ | Lambda | 表达式条件,仅支持实体基础成员(不包含导航对象) | +| Where | \ | string, parms | 原生sql语法条件,Where("id = ?id", new { id = 1 }) | +| Where | \ | T1 \| IEnumerable | 传入实体或集合,将其主键作为条件 | +| WhereExists | \ | ISelect | 子查询是否存在 | +| ToSql | string | | 返回即将执行的SQL语句 | +| ExecuteAffrows | long | | 执行SQL语句,返回影响的行数 | +| ExecuteUpdated | List\ | | 执行SQL语句,返回更新后的记录 | + +### 列优先级 + +> 全部列 < 指定列(Set/SetRaw) < 忽略列(IgnoreColumns) + +### 测试代码 + +```csharp +[Table(Name = "tb_topic")] +class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } +} + +var mysql = new MySql("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=3"); +IUpdate update => mysql.Update(); +``` + +### 动态条件 +```csharp +Update(object dywhere) +``` +dywhere 支持 + +* 主键值 +* new[] { 主键值1, 主键值2 } +* Topic对象 +* new[] { Topic对象1, Topic对象2 } +* new { id = 1 } + +### 更新指定列 +```csharp +var t1 = mysql.Update(1).Set(a => a.CreateTime, DateTime.Now).ToSql(); +//UPDATE `tb_topic` SET `CreateTime` = '2018-12-08 00:04:59' WHERE (`Id` = 1) +``` + +### 更新指定列,累加 +```csharp +var t2 = mysql.Update(1).Set(a => a.Clicks + 1).ToSql(); +//UPDATE `tb_topic` SET `Clicks` = ifnull(`Clicks`,0) + 1 WHERE (`Id` = 1) +``` + +### 保存实体 +```csharp +var item = new Topic { Id = 1, Title = "newtitle" }; +var t3 = update.SetSource(item).ToSql(); +//UPDATE `tb_topic` SET `Clicks` = ?p_0, `Title` = ?p_1, `CreateTime` = ?p_2 WHERE (`Id` = 1) +``` + +### 保存实体,忽略一些列 +```csharp +var t4 = update.SetSource(item).IgnoreColumns(a => a.Clicks).ToSql(); +//UPDATE `tb_topic` SET `Title` = ?p_0, `CreateTime` = ?p_1 WHERE (`Id` = 1) +var t5 = update.SetSource(item).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql(); +//UPDATE `tb_topic` SET `Title` = ?p_0 WHERE (`Id` = 1) +``` + +### 批量保存 +```csharp +var items = new List(); +for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + +var t6 = update.SetSource(items).ToSql(); +//UPDATE `tb_topic` SET `Clicks` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END, `Title` = CASE `Id` WHEN 1 THEN ?p_10 WHEN 2 THEN ?p_11 WHEN 3 THEN ?p_12 WHEN 4 THEN ?p_13 WHEN 5 THEN ?p_14 WHEN 6 THEN ?p_15 WHEN 7 THEN ?p_16 WHEN 8 THEN ?p_17 WHEN 9 THEN ?p_18 WHEN 10 THEN ?p_19 END, `CreateTime` = CASE `Id` WHEN 1 THEN ?p_20 WHEN 2 THEN ?p_21 WHEN 3 THEN ?p_22 WHEN 4 THEN ?p_23 WHEN 5 THEN ?p_24 WHEN 6 THEN ?p_25 WHEN 7 THEN ?p_26 WHEN 8 THEN ?p_27 WHEN 9 THEN ?p_28 WHEN 10 THEN ?p_29 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10)) +``` + +> 批量保存的场景,先查询20条记录,根据本地很复杂的规则把集合的值改完后 + +> 传统做法是循环20次保存,用 case when 只要一次就行 + +### 批量保存,忽略一些列 +```csharp +var t7 = update.SetSource(items).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql(); +//UPDATE `tb_topic` SET `Title` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10)) +``` + +### 批量更新指定列 +```csharp +var t8 = update.SetSource(items).Set(a => a.CreateTime, DateTime.Now).ToSql(); +//UPDATE `tb_topic` SET `CreateTime` = ?p_0 WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10)) +``` + +> 指定列更新后,批量保存将失效 + +### 更新条件 + +> 除了顶上介绍的 dywhere 构造参数外,还支持 Where lambda/sql 方法 + +```csharp +var t9 = update.Set(a => a.Title, "新标题").Where(a => a.Id == 1).ToSql(); +//UPDATE `tb_topic` SET `Title` = '新标题' WHERE (Id = 1) +``` + +### 自定义SQL + +```csharp +var t10 = update.SetRaw("Title = {0}", "新标题").Where("Id = {0}", 1).ToSql(); +//UPDATE `tb_topic` SET Title = '新标题' WHERE (Id = 1) +//sql语法条件,参数使用 {0},与 string.Format 保持一致,无须加单引号,错误的用法:'{0}' +``` + +### 执行命令 + +| 方法 | 返回值 | 参数 | 描述 | +| - | - | - | - | +| ExecuteAffrows | long | | 执行SQL语句,返回影响的行数 | +| ExecuteUpdated | List\ | | 执行SQL语句,返回更新后的记录 | \ No newline at end of file diff --git a/FreeSql.Tests/Class1.cs b/FreeSql.Tests/Class1.cs new file mode 100644 index 00000000..da9da817 --- /dev/null +++ b/FreeSql.Tests/Class1.cs @@ -0,0 +1,126 @@ + +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Text.RegularExpressions; +//using FreeSql; +//using FreeSql.DatabaseModel; + +////namespace TplDynamicCodeGenerate { +//public class TplDynamicCodeGenerate_view1 : FreeSql.Generator.TemplateEngin.ITemplateOutput { +// public FreeSql.Generator.TemplateEngin.TemplateReturnInfo OuTpUt(StringBuilder tOuTpUt, IDictionary oPtIoNs, string rEfErErFiLeNaMe, FreeSql.Generator.TemplateEngin tEmPlAtEsEnDeR) { +// FreeSql.Generator.TemplateEngin.TemplateReturnInfo rTn = tOuTpUt == null ? +// new FreeSql.Generator.TemplateEngin.TemplateReturnInfo { Sb = (tOuTpUt = new StringBuilder()), Blocks = new Dictionary() } : +// new FreeSql.Generator.TemplateEngin.TemplateReturnInfo { Sb = tOuTpUt, Blocks = new Dictionary() }; +// Dictionary TPL__blocks = rTn.Blocks; +// Stack TPL__blocks_stack = new Stack(); +// int[] TPL__blocks_stack_peek; +// List TPL__forc = new List(); +// Func pRoCeSsOpTiOnS = new Func(delegate () { +// IDictionary nEwoPtIoNs = new Hashtable(); +// foreach (DictionaryEntry oPtIoNs_dE in oPtIoNs) +// nEwoPtIoNs[oPtIoNs_dE.Key] = oPtIoNs_dE.Value; +// foreach (IDictionary TPL__forc_dIc in TPL__forc) +// foreach (DictionaryEntry TPL__forc_dIc_dE in TPL__forc_dIc) +// nEwoPtIoNs[TPL__forc_dIc_dE.Key] = TPL__forc_dIc_dE.Value; +// return nEwoPtIoNs; +// }); +// FreeSql.Generator.TemplateEngin.TemplateIf tPlIf = delegate (object exp) { +// if (exp is bool) return (bool)exp; +// if (exp == null) return false; +// if (exp is int && (int)exp == 0) return false; +// if (exp is string && (string)exp == string.Empty) return false; +// if (exp is long && (long)exp == 0) return false; +// if (exp is short && (short)exp == 0) return false; +// if (exp is byte && (byte)exp == 0) return false; +// if (exp is double && (double)exp == 0) return false; +// if (exp is float && (float)exp == 0) return false; +// if (exp is decimal && (decimal)exp == 0) return false; +// return true; +// }; +// FreeSql.Generator.TemplateEngin.TemplatePrint print = delegate (object[] pArMs) { +// if (pArMs == null || pArMs.Length == 0) return; +// foreach (object pArMs_A in pArMs) if (pArMs_A != null) tOuTpUt.Append(pArMs_A); +// }; +// FreeSql.Generator.TemplateEngin.TemplatePrint Print = prin +// dynamic index = oPtIoNs["index"]; +// dynamic col = oPtIoNs["col"]; +// dynamic table = oPtIoNs["table"]; +// dynamic dbfirst = oPtIoNs["dbfirst"]; t; +// tOuTpUt.Append("using System;\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing System.Threading.Tasks;\r\nusing Newtonsoft.Json;\r\nusing FreeSql.DataAnnotations;\r\n"); + +// var dbf = dbfirst as FreeSql.IDbFirst; +// var cols = (table.Columns as List); + +// Func UString = stra => stra.Substring(0, 1).ToUpper() + stra.Substring(1); +// Func GetCsType = cola3 => { +// if (cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { +// return $"{UString(cola3.Table.Name)}{cola3.Name.ToUpper()}{(cola3.IsNullable ? "?" : "")}"; +// } +// return dbf.GetCsType(cola3); +// }; + +// tOuTpUt.Append("\r\nnamespace test.Model {\r\n\r\n [JsonObject(MemberSerialization.OptIn), Table(Name = \""); +// Print(!string.IsNullOrEmpty(table.Schema) ? table.Schema + "." : ""); +// tOuTpUt.Append(""); +// Print(table.Name); +// tOuTpUt.Append("\""); +// if (tPlIf(cols.Where(cola003 => cola003.Name.ToLower() == "is_deleted" || cola003.Name.ToLower() == "isdeleted").Any())) { +// tOuTpUt.Append(", SelectFilter = \"a.IsDeleted = 1\""); +// } +// tOuTpUt.Append(")]\r\n public partial class "); +// Print(UString(table.Name)); +// tOuTpUt.Append(" {"); +// //new Action(delegate () { +// IDictionary TPL__tmp1 = new Hashtable(); +// TPL__forc.Add(TPL__tmp1); +// var TPL__tmp2 = table.Columns; +// var TPL__tmp3 = col; +// var TPL__tmp4 = index; +// index = 0; +// if (TPL__tmp2 != null) +// foreach (var TPL__tmp5 in TPL__tmp2) { +// TPL__tmp1["index"] = ++index; +// TPL__tmp1["col"] = TPL__tmp5; +// col = TPL__tmp5; +// tOuTpUt.Append("\r\n "); +// if (tPlIf(string.IsNullOrEmpty(col.Coment) == false)) { +// tOuTpUt.Append("/// \r\n /// "); +// Print(col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")); +// tOuTpUt.Append("\r\n /// "); +// } +// tOuTpUt.Append("\r\n [JsonProperty, Column(Name = \""); +// Print(col.Name); +// tOuTpUt.Append("\", DbType = \""); +// Print(col.DbTypeTextFull); +// tOuTpUt.Append("\""); +// if (tPlIf(col.IsPrimary == true)) { +// tOuTpUt.Append(", IsPrimary = true"); +// } +// tOuTpUt.Append(""); +// if (tPlIf(col.IsIdentity == true)) { +// tOuTpUt.Append(", IsIdentity = true"); +// } +// tOuTpUt.Append(""); +// if (tPlIf(col.IsNullable == true)) { +// tOuTpUt.Append(", IsNullable = true"); +// } +// tOuTpUt.Append(")]\r\n public "); +// Print(GetCsType(col)); +// tOuTpUt.Append(" "); +// Print(UString(col.Name)); +// tOuTpUt.Append(" { get; set; }\r\n "); +// } +// col = TPL__tmp3; +// index = TPL__tmp4; +// TPL__forc.RemoveAt(TPL__forc.Count - 1); +// //})(); +// tOuTpUt.Append("\r\n }\r\n"); +// tEmPlAtEsEnDeR.RenderFile2(tOuTpUt, pRoCeSsOpTiOnS(), "../../include/enumtype.tpl", rEfErErFiLeNaMe); +// tOuTpUt.Append("\r\n}"); +// return rTn; +// } +//} +////} diff --git a/FreeSql.Tests/Extensions/StringExtensionsTest.cs b/FreeSql.Tests/Extensions/StringExtensionsTest.cs new file mode 100644 index 00000000..c7352c24 --- /dev/null +++ b/FreeSql.Tests/Extensions/StringExtensionsTest.cs @@ -0,0 +1,65 @@ +using Xunit; + +namespace FreeSql.Tests.Extensions { + public class StringExtensionsTest { + [Fact] + public void FormatMySql() { + + Assert.Empty(((string)null).FormatMySql("11")); + Assert.Equal("a=1", "a={0}".FormatMySql(1)); + Assert.Equal("a =1", "a ={0}".FormatMySql(1)); + Assert.Equal("a = 1", "a = {0}".FormatMySql(1)); + Assert.Equal("a='a'", "a={0}".FormatMySql('a')); + Assert.Equal("a ='a'", "a ={0}".FormatMySql('a')); + Assert.Equal("a = 'a'", "a = {0}".FormatMySql('a')); + + Assert.Equal("a=1 and b IS NULL", "a={0} and b={1}".FormatMySql(1, null)); + Assert.Equal("a =1 and b IS NULL", "a ={0} and b ={1}".FormatMySql(1, null)); + Assert.Equal("a = 1 and b IS NULL", "a = {0} and b = {1}".FormatMySql(1, null)); + + Assert.Equal("a=1 and b IS NULL and c in (1,2,3,4)", "a={0} and b={1} and c in {2}".FormatMySql(1, null, new[] { 1, 2, 3, 4 })); + Assert.Equal("a=1 and b IS NULL and c IS NULL", "a={0} and b={1} and c in {2}".FormatMySql(1, null, null)); + Assert.Equal("a=1 and b IS NULL and c not IS NULL", "a={0} and b={1} and c not in {2}".FormatMySql(1, null, null)); + } + + [Fact] + public void FormatSqlServer() { + + Assert.Empty(((string)null).FormatSqlServer("11")); + Assert.Equal("a=1", "a={0}".FormatSqlServer(1)); + Assert.Equal("a =1", "a ={0}".FormatSqlServer(1)); + Assert.Equal("a = 1", "a = {0}".FormatSqlServer(1)); + Assert.Equal("a='a'", "a={0}".FormatSqlServer('a')); + Assert.Equal("a ='a'", "a ={0}".FormatSqlServer('a')); + Assert.Equal("a = 'a'", "a = {0}".FormatSqlServer('a')); + + Assert.Equal("a=1 and b IS NULL", "a={0} and b={1}".FormatSqlServer(1, null)); + Assert.Equal("a =1 and b IS NULL", "a ={0} and b ={1}".FormatSqlServer(1, null)); + Assert.Equal("a = 1 and b IS NULL", "a = {0} and b = {1}".FormatSqlServer(1, null)); + + Assert.Equal("a=1 and b IS NULL and c in (1,2,3,4)", "a={0} and b={1} and c in {2}".FormatSqlServer(1, null, new[] { 1, 2, 3, 4 })); + Assert.Equal("a=1 and b IS NULL and c IS NULL", "a={0} and b={1} and c in {2}".FormatSqlServer(1, null, null)); + Assert.Equal("a=1 and b IS NULL and c not IS NULL", "a={0} and b={1} and c not in {2}".FormatSqlServer(1, null, null)); + } + + [Fact] + public void FormatPostgreSQL() { + + Assert.Empty(((string)null).FormatPostgreSQL("11")); + Assert.Equal("a=1", "a={0}".FormatPostgreSQL(1)); + Assert.Equal("a =1", "a ={0}".FormatPostgreSQL(1)); + Assert.Equal("a = 1", "a = {0}".FormatPostgreSQL(1)); + Assert.Equal("a='a'", "a={0}".FormatPostgreSQL('a')); + Assert.Equal("a ='a'", "a ={0}".FormatPostgreSQL('a')); + Assert.Equal("a = 'a'", "a = {0}".FormatPostgreSQL('a')); + + Assert.Equal("a=1 and b IS NULL", "a={0} and b={1}".FormatPostgreSQL(1, null)); + Assert.Equal("a =1 and b IS NULL", "a ={0} and b ={1}".FormatPostgreSQL(1, null)); + Assert.Equal("a = 1 and b IS NULL", "a = {0} and b = {1}".FormatPostgreSQL(1, null)); + + Assert.Equal("a=1 and b IS NULL and c in (1,2,3,4)", "a={0} and b={1} and c in {2}".FormatPostgreSQL(1, null, new[] { 1, 2, 3, 4 })); + Assert.Equal("a=1 and b IS NULL and c IS NULL", "a={0} and b={1} and c in {2}".FormatSqlServer(1, null, null)); + Assert.Equal("a=1 and b IS NULL and c not IS NULL", "a={0} and b={1} and c not in {2}".FormatSqlServer(1, null, null)); + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests.csproj new file mode 100644 index 00000000..f8c639cb --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/FreeSql.Tests/Generator/MySqlTemplateGeneratorTest.cs b/FreeSql.Tests/Generator/MySqlTemplateGeneratorTest.cs new file mode 100644 index 00000000..ff0e085b --- /dev/null +++ b/FreeSql.Tests/Generator/MySqlTemplateGeneratorTest.cs @@ -0,0 +1,27 @@ +using FreeSql.DataAnnotations; +using FreeSql.Generator; +using System; +using Xunit; + +namespace FreeSql.Tests.Generator { + public class MySqlTemplateGeneratorTest { + + [Fact] + public void BuildSimpleEntity() { + var gen = new TemplateGenerator(); + gen.Build(g.mysql.DbFirst, @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", @"C:\Users\28810\Desktop\新建文件夹 (9)", "cccddd"); + } + + [Fact] + public void BuildSimpleEntityNavigationObject () { + var gen = new TemplateGenerator(); + gen.Build(g.mysql.DbFirst, @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity-navigation-object", @"C:\Users\28810\Desktop\新建文件夹 (9)", "cccddd"); + } + + [Fact] + public void BuildRichEntityNavigationObject() { + var gen = new TemplateGenerator(); + gen.Build(g.mysql.DbFirst, @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\rich-entity-navigation-object", @"C:\Users\28810\Desktop\新建文件夹 (9)", "cccddd"); + } + } +} diff --git a/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs b/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs new file mode 100644 index 00000000..33b4c443 --- /dev/null +++ b/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs @@ -0,0 +1,72 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlDeleteTest { + + IDelete delete => g.mysql.Delete(); //�������� + + [Table(Name = "tb_topic")] + class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + + [Fact] + public void Dywhere() { + Assert.Null(g.mysql.Delete().ToSql()); + var sql = g.mysql.Delete(new[] { 1, 2 }).ToSql(); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2)", sql); + + sql = g.mysql.Delete(new Topic { Id = 1, Title = "test" }).ToSql(); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1)", sql); + + sql = g.mysql.Delete(new[] { new Topic { Id = 1, Title = "test" }, new Topic { Id = 2, Title = "test" } }).ToSql(); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2)", sql); + + sql = g.mysql.Delete(new { id = 1 }).ToSql(); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1)", sql); + } + + [Fact] + public void Where() { + var sql = delete.Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1)", sql); + + sql = delete.Where("id = ?id", new { id = 1 }).ToSql().Replace("\r\n", ""); + Assert.Equal("DELETE FROM `tb_topic` WHERE (id = ?id)", sql); + + var item = new Topic { Id = 1, Title = "newtitle" }; + sql = delete.Where(item).ToSql().Replace("\r\n", ""); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` = 1)", sql); + + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + sql = delete.Where(items).ToSql().Replace("\r\n", ""); + Assert.Equal("DELETE FROM `tb_topic` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))", sql); + } + [Fact] + public void WhereExists() { + + } + [Fact] + public void ExecuteAffrows() { + + var id = g.mysql.Insert(new Topic { Title = "xxxx" }).ExecuteIdentity(); + Assert.Equal(1, delete.Where(a => a.Id == id).ExecuteAffrows()); + } + [Fact] + public void ExecuteDeleted() { + + Assert.Throws(() => delete.Where(a => a.Id > 0).ExecuteDeleted()); + } + } +} diff --git a/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs b/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs new file mode 100644 index 00000000..639af244 --- /dev/null +++ b/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs @@ -0,0 +1,85 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlInsertTest { + + IInsert insert => g.mysql.Insert(); //�������� + + [Table(Name = "tb_topic")] + class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + + [Fact] + public void AppendData() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + var sql = insert.AppendData(items.First()).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0)", sql); + + sql = insert.AppendData(items).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0), (?Clicks1, ?Title1, ?CreateTime1), (?Clicks2, ?Title2, ?CreateTime2), (?Clicks3, ?Title3, ?CreateTime3), (?Clicks4, ?Title4, ?CreateTime4), (?Clicks5, ?Title5, ?CreateTime5), (?Clicks6, ?Title6, ?CreateTime6), (?Clicks7, ?Title7, ?CreateTime7), (?Clicks8, ?Title8, ?CreateTime8), (?Clicks9, ?Title9, ?CreateTime9)", sql); + + sql = insert.AppendData(items).InsertColumns(a => a.Title).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Title`) VALUES(?Title0), (?Title1), (?Title2), (?Title3), (?Title4), (?Title5), (?Title6), (?Title7), (?Title8), (?Title9)", sql); + + sql = insert.AppendData(items).IgnoreColumns(a => a.CreateTime).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9)", sql); + } + + [Fact] + public void InsertColumns() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + var sql = insert.AppendData(items).InsertColumns(a => a.Title).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Title`) VALUES(?Title0), (?Title1), (?Title2), (?Title3), (?Title4), (?Title5), (?Title6), (?Title7), (?Title8), (?Title9)", sql); + + sql = insert.AppendData(items).InsertColumns(a =>new { a.Title, a.Clicks }).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9)", sql); + } + [Fact] + public void IgnoreColumns() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + var sql = insert.AppendData(items).IgnoreColumns(a => a.CreateTime).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9)", sql); + + sql = insert.AppendData(items).IgnoreColumns(a => new { a.Title, a.CreateTime }).ToSql(); + Assert.Equal("INSERT INTO `tb_topic`(`Clicks`) VALUES(?Clicks0), (?Clicks1), (?Clicks2), (?Clicks3), (?Clicks4), (?Clicks5), (?Clicks6), (?Clicks7), (?Clicks8), (?Clicks9)", sql); + } + [Fact] + public void ExecuteAffrows() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + Assert.Equal(1, insert.AppendData(items.First()).ExecuteAffrows()); + Assert.Equal(10, insert.AppendData(items).ExecuteAffrows()); + } + [Fact] + public void ExecuteIdentity() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + Assert.NotEqual(0, insert.AppendData(items.First()).ExecuteIdentity()); + } + [Fact] + public void ExecuteInserted() { + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + Assert.Throws(() => insert.AppendData(items.First()).ExecuteInserted()); + } + } +} diff --git a/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs b/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs new file mode 100644 index 00000000..5e2f3e84 --- /dev/null +++ b/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs @@ -0,0 +1,501 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlSelectTest { + + ISelect select => g.mysql.Select(); + + [Table(Name = "tb_topic")] + class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public int TestTypeInfoGuid { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + class TestTypeInfo { + public int Guid { get; set; } + public int ParentId { get; set; } + public TestTypeParentInfo Parent { get; set; } + public string Name { get; set; } + } + class TestTypeParentInfo { + public int Id { get; set; } + public string Name { get; set; } + + public List Types { get; set; } + } + + [Fact] + public void ToList() { + } + [Fact] + public void ToOne() { + } + [Fact] + public void ToSql() { + } + [Fact] + public void Any() { + var count = select.Where(a => 1 == 1).Count(); + Assert.False(select.Where(a => 1 == 2).Any()); + Assert.Equal(count > 0, select.Where(a => 1 == 1).Any()); + } + [Fact] + public void Count() { + var count = select.Where(a => 1 == 1).Count(); + select.Where(a => 1 == 1).Count(out var count2); + Assert.Equal(count, count2); + Assert.Equal(0, select.Where(a => 1 == 2).Count()); + } + [Fact] + public void Master() { + Assert.StartsWith(" SELECT", select.Master().Where(a => 1 == 1).ToSql()); + } + [Fact] + public void Caching() { + var result1 = select.Where(a => 1 == 1).Caching(20, "testcaching").ToList(); + var testcaching1 = g.mysql.Cache.Get("testcaching"); + Assert.NotNull(testcaching1); + var result2 = select.Where(a => 1 == 1).Caching(20, "testcaching").ToList(); + var testcaching2 = g.mysql.Cache.Get("testcaching"); + Assert.NotNull(testcaching2); + Assert.Equal(result1.Count, result1.Count); + } + [Fact] + public void From() { + //������� + var query2 = select.From((s, b, c) => s + .LeftJoin(a => a.TestTypeInfoGuid == b.Guid) + .LeftJoin(a => b.ParentId == c.Id) + ); + var sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON a.`TestTypeInfoGuid` = b.`Guid` LEFT JOIN `TestTypeParentInfo` c ON b.`ParentId` = c.`Id`", sql); + query2.ToList(); + } + [Fact] + public void LeftJoin() { + //����е�������a.Type��a.Type.Parent ���ǵ������� + var query = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid); + var sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON 1 = 1 LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx' WHERE (a__Type__Parent.`Id` = 10)", sql); + query.ToList(); + + //���û�е������� + query = select.LeftJoin((a, b) => b.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.LeftJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.LeftJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` b__Parent ON 1 = 1 LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx' WHERE (b__Parent.`Id` = 10)", sql); + query.ToList(); + + //������� + query = select + .LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid) + .LeftJoin(a => a.Type.Parent.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON a__Type__Parent.`Id` = a__Type.`ParentId`", sql); + query.ToList(); + + query = select + .LeftJoin((a, b) => b.Guid == a.TestTypeInfoGuid) + .LeftJoin((a, c) => c.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` LEFT JOIN `TestTypeParentInfo` c ON c.`Id` = b.`ParentId`", sql); + query.ToList(); + + //���û�е�������b��c������ϵ + var query2 = select.From((s, b, c) => s + .LeftJoin(a => a.TestTypeInfoGuid == b.Guid) + .LeftJoin(a => b.ParentId == c.Id)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON a.`TestTypeInfoGuid` = b.`Guid` LEFT JOIN `TestTypeParentInfo` c ON b.`ParentId` = c.`Id`", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.LeftJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a LEFT JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid", sql); + query.ToList(); + + query = select.LeftJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", new { bname = "xxx" }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a LEFT JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", sql); + query.ToList(); + } + [Fact] + public void InnerJoin() { + //����е�������a.Type��a.Type.Parent ���ǵ������� + var query = select.InnerJoin(a => a.Type.Guid == a.TestTypeInfoGuid); + var sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.InnerJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.InnerJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON 1 = 1 INNER JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx' WHERE (a__Type__Parent.`Id` = 10)", sql); + query.ToList(); + + //���û�е������� + query = select.InnerJoin((a, b) => b.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.InnerJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.InnerJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` b__Parent ON 1 = 1 INNER JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx' WHERE (b__Parent.`Id` = 10)", sql); + query.ToList(); + + //������� + query = select + .InnerJoin(a => a.Type.Guid == a.TestTypeInfoGuid) + .InnerJoin(a => a.Type.Parent.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` INNER JOIN `TestTypeParentInfo` a__Type__Parent ON a__Type__Parent.`Id` = a__Type.`ParentId`", sql); + query.ToList(); + + query = select + .InnerJoin((a, b) => b.Guid == a.TestTypeInfoGuid) + .InnerJoin((a, c) => c.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` INNER JOIN `TestTypeParentInfo` c ON c.`Id` = b.`ParentId`", sql); + query.ToList(); + + //���û�е�������b��c������ϵ + var query2 = select.From((s, b, c) => s + .InnerJoin(a => a.TestTypeInfoGuid == b.Guid) + .InnerJoin(a => b.ParentId == c.Id)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a INNER JOIN `TestTypeInfo` b ON a.`TestTypeInfoGuid` = b.`Guid` INNER JOIN `TestTypeParentInfo` c ON b.`ParentId` = c.`Id`", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.InnerJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a INNER JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid", sql); + query.ToList(); + + query = select.InnerJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", new { bname = "xxx" }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a INNER JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", sql); + query.ToList(); + + } + [Fact] + public void RightJoin() { + //����е�������a.Type��a.Type.Parent ���ǵ������� + var query = select.RightJoin(a => a.Type.Guid == a.TestTypeInfoGuid); + var sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.RightJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.RightJoin(a => a.Type.Guid == a.TestTypeInfoGuid && a.Type.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON 1 = 1 RIGHT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` AND a__Type.`Name` = 'xxx' WHERE (a__Type__Parent.`Id` = 10)", sql); + query.ToList(); + + //���û�е������� + query = select.RightJoin((a, b) => b.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid`", sql); + query.ToList(); + + query = select.RightJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx'", sql); + query.ToList(); + + query = select.RightJoin((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "xxx").Where(a => a.Type.Parent.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a LEFT JOIN `TestTypeParentInfo` b__Parent ON 1 = 1 RIGHT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'xxx' WHERE (b__Parent.`Id` = 10)", sql); + query.ToList(); + + //������� + query = select + .RightJoin(a => a.Type.Guid == a.TestTypeInfoGuid) + .RightJoin(a => a.Type.Parent.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` RIGHT JOIN `TestTypeParentInfo` a__Type__Parent ON a__Type__Parent.`Id` = a__Type.`ParentId`", sql); + query.ToList(); + + query = select + .RightJoin((a, b) => b.Guid == a.TestTypeInfoGuid) + .RightJoin((a, c) => c.Id == a.Type.ParentId); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` RIGHT JOIN `TestTypeParentInfo` c ON c.`Id` = b.`ParentId`", sql); + query.ToList(); + + //���û�е�������b��c������ϵ + var query2 = select.From((s, b, c) => s + .RightJoin(a => a.TestTypeInfoGuid == b.Guid) + .RightJoin(a => b.ParentId == c.Id)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a RIGHT JOIN `TestTypeInfo` b ON a.`TestTypeInfoGuid` = b.`Guid` RIGHT JOIN `TestTypeParentInfo` c ON b.`ParentId` = c.`Id`", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.RightJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a RIGHT JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid", sql); + query.ToList(); + + query = select.RightJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", new { bname = "xxx" }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a RIGHT JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", sql); + query.ToList(); + + } + [Fact] + public void Where() { + //����е�������a.Type��a.Type.Parent ���ǵ������� + var query = select.Where(a => a.Id == 10); + var sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10)", sql); + query.ToList(); + + query = select.Where(a => a.Id == 10 && a.Id > 10 || a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10 AND a.`Id` > 10 OR a.`Clicks` > 100)", sql); + query.ToList(); + + query = select.Where(a => a.Id == 10).Where(a => a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10) AND (a.`Clicks` > 100)", sql); + query.ToList(); + + query = select.Where(a => a.Type.Name == "typeTitle"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type WHERE (a__Type.`Name` = 'typeTitle')", sql); + query.ToList(); + + query = select.Where(a => a.Type.Name == "typeTitle" && a.Type.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type WHERE (a__Type.`Name` = 'typeTitle' AND a__Type.`Guid` = a.`TestTypeInfoGuid`)", sql); + query.ToList(); + + query = select.Where(a => a.Type.Parent.Name == "tparent"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type, `TestTypeParentInfo` a__Type__Parent WHERE (a__Type__Parent.`Name` = 'tparent')", sql); + query.ToList(); + + //���û�е������ԣ��򵥶������ + query = select.Where((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "typeTitle"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` b WHERE (b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'typeTitle')", sql); + query.ToList(); + + query = select.Where((a, b) => b.Name == "typeTitle" && b.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` b WHERE (b.`Name` = 'typeTitle' AND b.`Guid` = a.`TestTypeInfoGuid`)", sql); + query.ToList(); + + query = select.Where((a, b, c) => c.Name == "tparent"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a, `TestTypeParentInfo` c WHERE (c.`Name` = 'tparent')", sql); + query.ToList(); + + //����һ�� From ��Ķ������ + var query2 = select.From((s, b, c) => s + .Where(a => a.Id == 10 && c.Name == "xxx") + .Where(a => b.ParentId == 20)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeParentInfo` c, `TestTypeInfo` b WHERE (a.`Id` = 10 AND c.`Name` = 'xxx') AND (b.`ParentId` = 20)", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.Where("a.clicks > 100 && a.id = ?id", new { id = 10 }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.clicks > 100 && a.id = ?id)", sql); + query.ToList(); + } + [Fact] + public void WhereIf() { + //����е�������a.Type��a.Type.Parent ���ǵ������� + var query = select.WhereIf(true, a => a.Id == 10); + var sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10)", sql); + query.ToList(); + + query = select.WhereIf(true, a => a.Id == 10 && a.Id > 10 || a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10 AND a.`Id` > 10 OR a.`Clicks` > 100)", sql); + query.ToList(); + + query = select.WhereIf(true, a => a.Id == 10).WhereIf(true, a => a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.`Id` = 10) AND (a.`Clicks` > 100)", sql); + query.ToList(); + + query = select.WhereIf(true, a => a.Type.Name == "typeTitle"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type WHERE (a__Type.`Name` = 'typeTitle')", sql); + query.ToList(); + + query = select.WhereIf(true, a => a.Type.Name == "typeTitle" && a.Type.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type WHERE (a__Type.`Name` = 'typeTitle' AND a__Type.`Guid` = a.`TestTypeInfoGuid`)", sql); + query.ToList(); + + query = select.WhereIf(true, a => a.Type.Parent.Name == "tparent"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a__Type.`Guid` as4, a__Type.`ParentId` as5, a__Type.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeInfo` a__Type, `TestTypeParentInfo` a__Type__Parent WHERE (a__Type__Parent.`Name` = 'tparent')", sql); + query.ToList(); + + //����һ�� From ��Ķ������ + var query2 = select.From((s, b, c) => s + .WhereIf(true, a => a.Id == 10 && c.Name == "xxx") + .WhereIf(true, a => b.ParentId == 20)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, b.`Guid` as4, b.`ParentId` as5, b.`Name` as6, a.`Title` as7, a.`CreateTime` as8 FROM `tb_topic` a, `TestTypeParentInfo` c, `TestTypeInfo` b WHERE (a.`Id` = 10 AND c.`Name` = 'xxx') AND (b.`ParentId` = 20)", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.WhereIf(true, "a.clicks > 100 && a.id = ?id", new { id = 10 }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a WHERE (a.clicks > 100 && a.id = ?id)", sql); + query.ToList(); + + // ==========================================WhereIf(false) + + //����е�������a.Type��a.Type.Parent ���ǵ������� + query = select.WhereIf(false, a => a.Id == 10); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + query = select.WhereIf(false, a => a.Id == 10 && a.Id > 10 || a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + query = select.WhereIf(false, a => a.Id == 10).WhereIf(false, a => a.Clicks > 100); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + query = select.WhereIf(false, a => a.Type.Name == "typeTitle"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + query = select.WhereIf(false, a => a.Type.Name == "typeTitle" && a.Type.Guid == a.TestTypeInfoGuid); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + query = select.WhereIf(false, a => a.Type.Parent.Name == "tparent"); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + + //����һ�� From ��Ķ������ + query2 = select.From((s, b, c) => s + .WhereIf(false, a => a.Id == 10 && c.Name == "xxx") + .WhereIf(false, a => b.ParentId == 20)); + sql = query2.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query2.ToList(); + + //������϶����㲻�� + query = select.WhereIf(false, "a.clicks > 100 && a.id = ?id", new { id = 10 }); + sql = query.ToSql().Replace("\r\n", ""); + Assert.Equal("SELECT a.`Id` as1, a.`Clicks` as2, a.`TestTypeInfoGuid` as3, a.`Title` as4, a.`CreateTime` as5 FROM `tb_topic` a", sql); + query.ToList(); + } + [Fact] + public void WhereLike() { + //ģ����ѯ��WhereLike(a => a.Title, "%sql") + var query = select.Where(a => a.Title.StartsWith("ss")).Where(a => a.Type.Name.Contains("sss")); + var sql = query.ToSql().Replace("\r\n", ""); + + query = select.Where(a => a.Title.EndsWith("ss")); + sql = query.ToSql().Replace("\r\n", ""); + + query = select.Where(a => a.Title.Contains("ss")); + sql = query.ToSql().Replace("\r\n", ""); + + query = select.WhereLike(a => a.Title, "%ss"); + sql = query.ToSql().Replace("\r\n", ""); + + query = select.WhereLike(a => a.Title, "%ss").WhereLike(a => a.Title, "%aa"); + sql = query.ToSql().Replace("\r\n", ""); + + //ģ����ѯ��ѡ������ OR��WhereLike(a => new[] { a.Title, a.Content }, "%sql%") + query = select.WhereLike(a => new[] { a.Title, a.Type.Name, a.Type.Parent.Name }, "%aa"); + sql = query.ToSql().Replace("\r\n", ""); + } + [Fact] + public void GroupBy() { + } + [Fact] + public void Having() { + } + [Fact] + public void OrderBy() { + } + [Fact] + public void Skip_Offset() { + } + [Fact] + public void Take_Limit() { + } + [Fact] + public void Page() { + } + [Fact] + public void Sum() { + } + [Fact] + public void Min() { + } + [Fact] + public void Max() { + } + [Fact] + public void Avg() { + } + [Fact] + public void As() { + } + } +} diff --git a/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs b/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs new file mode 100644 index 00000000..ca433072 --- /dev/null +++ b/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs @@ -0,0 +1,107 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlUpdateTest { + IUpdate update => g.mysql.Update(); + + [Table(Name = "tb_topic")] + class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int? Clicks { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + + [Fact] + public void Dywhere() { + Assert.Null(g.mysql.Update().ToSql()); + Assert.Equal("UPDATE `tb_topic` SET title='test' \r\nWHERE (`Id` = 1 OR `Id` = 2)", g.mysql.Update(new[] { 1, 2 }).SetRaw("title='test'").ToSql()); + Assert.Equal("UPDATE `tb_topic` SET title='test1' \r\nWHERE (`Id` = 1)", g.mysql.Update(new Topic { Id = 1, Title = "test" }).SetRaw("title='test1'").ToSql()); + Assert.Equal("UPDATE `tb_topic` SET title='test1' \r\nWHERE (`Id` = 1 OR `Id` = 2)", g.mysql.Update(new[] { new Topic { Id = 1, Title = "test" }, new Topic { Id = 2, Title = "test" } }).SetRaw("title='test1'").ToSql()); + Assert.Equal("UPDATE `tb_topic` SET title='test1' \r\nWHERE (`Id` = 1)", g.mysql.Update(new { id = 1 }).SetRaw("title='test1'").ToSql()); + } + + [Fact] + public void SetSource() { + var sql = update.SetSource(new Topic { Id = 1, Title = "newtitle" }).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Clicks` = ?p_0, `Title` = ?p_1, `CreateTime` = ?p_2 WHERE (`Id` = 1)", sql); + + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + + sql = update.SetSource(items).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Clicks` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END, `Title` = CASE `Id` WHEN 1 THEN ?p_10 WHEN 2 THEN ?p_11 WHEN 3 THEN ?p_12 WHEN 4 THEN ?p_13 WHEN 5 THEN ?p_14 WHEN 6 THEN ?p_15 WHEN 7 THEN ?p_16 WHEN 8 THEN ?p_17 WHEN 9 THEN ?p_18 WHEN 10 THEN ?p_19 END, `CreateTime` = CASE `Id` WHEN 1 THEN ?p_20 WHEN 2 THEN ?p_21 WHEN 3 THEN ?p_22 WHEN 4 THEN ?p_23 WHEN 5 THEN ?p_24 WHEN 6 THEN ?p_25 WHEN 7 THEN ?p_26 WHEN 8 THEN ?p_27 WHEN 9 THEN ?p_28 WHEN 10 THEN ?p_29 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))", sql); + + sql = update.SetSource(items).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Title` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))", sql); + + sql = update.SetSource(items).Set(a => a.CreateTime, new DateTime(2020,1,1)).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `CreateTime` = ?p_0 WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))", sql); + } + [Fact] + public void IgnoreColumns() { + var sql = update.SetSource(new Topic { Id = 1, Title = "newtitle" }).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Title` = ?p_0 WHERE (`Id` = 1)", sql); + } + [Fact] + public void Set() { + var sql = update.Where(a => a.Id == 1).Set(a => a.Title, "newtitle").ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Title` = ?p_0 WHERE (`Id` = 1)", sql); + + sql = update.Where(a => a.Id == 1).Set(a => a.Title, "newtitle").Set(a => a.CreateTime, new DateTime(2020, 1, 1)).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Title` = ?p_0, `CreateTime` = ?p_1 WHERE (`Id` = 1)", sql); + + sql = update.Set(a => a.Clicks * 10 / 1).Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Clicks` = ifnull(`Clicks`, 0) * 10 / 1 WHERE (`Id` = 1)", sql); + + sql = update.Set(a => a.Id - 10).Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Id` = `Id` - 10 WHERE (`Id` = 1)", sql); + + int incrv = 10; + sql = update.Set(a => a.Clicks * incrv / 1).Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Clicks` = ifnull(`Clicks`, 0) * 10 / 1 WHERE (`Id` = 1)", sql); + + sql = update.Set(a => a.Id - incrv).Where(a => a.Id == 1).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET `Id` = `Id` - 10 WHERE (`Id` = 1)", sql); + } + [Fact] + public void SetRaw() { + var sql = update.Where(a => a.Id == 1).SetRaw("clicks = clicks + ?incrClick", new { incrClick = 1 }).ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET clicks = clicks + ?incrClick WHERE (`Id` = 1)", sql); + } + [Fact] + public void Where() { + var sql = update.Where(a => a.Id == 1).SetRaw("title='newtitle'").ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET title='newtitle' WHERE (`Id` = 1)", sql); + + sql = update.Where("id = ?id", new { id = 1 }).SetRaw("title='newtitle'").ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET title='newtitle' WHERE (id = ?id)", sql); + + var item = new Topic { Id = 1, Title = "newtitle" }; + sql = update.Where(item).SetRaw("title='newtitle'").ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET title='newtitle' WHERE (`Id` = 1)", sql); + + var items = new List(); + for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); + sql = update.Where(items).SetRaw("title='newtitle'").ToSql().Replace("\r\n", ""); + Assert.Equal("UPDATE `tb_topic` SET title='newtitle' WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))", sql); + } + [Fact] + public void WhereExists() { + + } + [Fact] + public void ExecuteAffrows() { + + } + [Fact] + public void ExecuteUpdated() { + + } + } +} diff --git a/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs b/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs new file mode 100644 index 00000000..83812d4f --- /dev/null +++ b/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs @@ -0,0 +1,54 @@ +using FreeSql.DataAnnotations; +using System; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlAdoTest { + [Fact] + public void Pool() { + var t1 = g.mysql.Ado.MasterPool.StatisticsFullily; + } + + [Fact] + public void SlavePools() { + var t2 = g.mysql.Ado.SlavePools.Count; + } + + [Fact] + public void IsTracePerformance() { + Assert.True(g.mysql.Ado.IsTracePerformance); + } + + [Fact] + public void ExecuteReader() { + + } + [Fact] + public void ExecuteArray() { + + } + [Fact] + public void ExecuteNonQuery() { + + } + [Fact] + public void ExecuteScalar() { + + } + + [Fact] + public void Query() { + var t3 = g.mysql.Ado.Query("select * from song"); + + var t4 = g.mysql.Ado.Query<(int, string, string)>("select * from song"); + + var t5 = g.mysql.Ado.Query("select * from song"); + } + + class xxx { + public int Id { get; set; } + public string Path { get; set; } + public string Title2 { get; set; } + } + } +} diff --git a/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs b/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs new file mode 100644 index 00000000..4245a27d --- /dev/null +++ b/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs @@ -0,0 +1,350 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlCodeFirstTest { + [Fact] + public void GetComparisonDDLStatements() { + + var sql = g.mysql.CodeFirst.GetComparisonDDLStatements(); + if (string.IsNullOrEmpty(sql) == false) { + Assert.Equal(@"CREATE TABLE IF NOT EXISTS `cccddd`.`tb_alltype` ( + `Id` INT(11) NOT NULL AUTO_INCREMENT, + `testFieldBool` BIT(1) NOT NULL, + `testFieldSByte` TINYINT(3) NOT NULL, + `testFieldShort` SMALLINT(6) NOT NULL, + `testFieldInt` INT(11) NOT NULL, + `testFieldLong` BIGINT(20) NOT NULL, + `testFieldByte` TINYINT(3) UNSIGNED NOT NULL, + `testFieldUShort` SMALLINT(5) UNSIGNED NOT NULL, + `testFieldUInt` INT(10) UNSIGNED NOT NULL, + `testFieldULong` BIGINT(20) UNSIGNED NOT NULL, + `testFieldDouble` DOUBLE NOT NULL, + `testFieldFloat` FLOAT NOT NULL, + `testFieldDecimal` DECIMAL(10,2) NOT NULL, + `testFieldTimeSpan` TIME NOT NULL, + `testFieldDateTime` DATETIME NOT NULL, + `testFieldBytes` VARBINARY(255), + `testFieldString` VARCHAR(255), + `testFieldGuid` VARCHAR(36), + `testFieldBoolNullable` BIT(1), + `testFieldSByteNullable` TINYINT(3), + `testFieldShortNullable` SMALLINT(6), + `testFieldIntNullable` INT(11), + `testFielLongNullable` BIGINT(20), + `testFieldByteNullable` TINYINT(3) UNSIGNED, + `testFieldUShortNullable` SMALLINT(5) UNSIGNED, + `testFieldUIntNullable` INT(10) UNSIGNED, + `testFieldULongNullable` BIGINT(20) UNSIGNED, + `testFieldDoubleNullable` DOUBLE, + `testFieldFloatNullable` FLOAT, + `testFieldDecimalNullable` DECIMAL(10,2), + `testFieldTimeSpanNullable` TIME, + `testFieldDateTimeNullable` DATETIME, + `testFieldGuidNullable` VARCHAR(36), + `testFieldPoint` POINT, + `testFieldLineString` LINESTRING, + `testFieldPolygon` POLYGON, + `testFieldMultiPoint` MULTIPOINT, + `testFieldMultiLineString` MULTILINESTRING, + `testFieldMultiPolygon` MULTIPOLYGON, + `testFieldEnum1` ENUM('E1','E2','E3') NOT NULL, + `testFieldEnum1Nullable` ENUM('E1','E2','E3'), + `testFieldEnum2` SET('F1','F2','F3') NOT NULL, + `testFieldEnum2Nullable` SET('F1','F2','F3'), + PRIMARY KEY (`Id`) +) Engine=InnoDB CHARACTER SET utf8; +", sql); + } + + sql = g.mysql.CodeFirst.GetComparisonDDLStatements(); + } + + + [JsonObject(MemberSerialization.OptIn), Table(Name = "tb_alltype")] + public partial class Tb_alltype { + + [JsonProperty, Column(Name = "Id", DbType = "int(11)", IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + + [JsonProperty, Column(Name = "testFieldBool", DbType = "bit(1)")] + public bool TestFieldBool { get; set; } + + + [JsonProperty, Column(Name = "testFieldBoolNullable", DbType = "bit(1)", IsNullable = true)] + public bool? TestFieldBoolNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldByte", DbType = "tinyint(3) unsigned")] + public byte TestFieldByte { get; set; } + + + [JsonProperty, Column(Name = "testFieldByteNullable", DbType = "tinyint(3) unsigned", IsNullable = true)] + public byte? TestFieldByteNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldBytes", DbType = "varbinary(255)", IsNullable = true)] + public byte[] TestFieldBytes { get; set; } + + + [JsonProperty, Column(Name = "testFieldDateTime", DbType = "datetime")] + public DateTime TestFieldDateTime { get; set; } + + + [JsonProperty, Column(Name = "testFieldDateTimeNullable", DbType = "datetime", IsNullable = true)] + public DateTime? TestFieldDateTimeNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldDecimal", DbType = "decimal(10,2)")] + public decimal TestFieldDecimal { get; set; } + + + [JsonProperty, Column(Name = "testFieldDecimalNullable", DbType = "decimal(10,2)", IsNullable = true)] + public decimal? TestFieldDecimalNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldDouble", DbType = "double")] + public double TestFieldDouble { get; set; } + + + [JsonProperty, Column(Name = "testFieldDoubleNullable", DbType = "double", IsNullable = true)] + public double? TestFieldDoubleNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldEnum1", DbType = "enum('E1','E2','E3','E5')")] + public Tb_alltypeTESTFIELDENUM1 TestFieldEnum1 { get; set; } + + + [JsonProperty, Column(Name = "testFieldEnum1Nullable", DbType = "enum('E1','E2','E3','E5')", IsNullable = true)] + public Tb_alltypeTESTFIELDENUM1NULLABLE? TestFieldEnum1Nullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldEnum2", DbType = "set('F1','F2','F3')")] + public Tb_alltypeTESTFIELDENUM2 TestFieldEnum2 { get; set; } + + + [JsonProperty, Column(Name = "testFieldEnum2Nullable", DbType = "set('F1','F2','F3')", IsNullable = true)] + public Tb_alltypeTESTFIELDENUM2NULLABLE? TestFieldEnum2Nullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldFloat", DbType = "float")] + public float TestFieldFloat { get; set; } + + + [JsonProperty, Column(Name = "testFieldFloatNullable", DbType = "float", IsNullable = true)] + public float? TestFieldFloatNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldGuid", DbType = "char(36)")] + public Guid TestFieldGuid { get; set; } + + + [JsonProperty, Column(Name = "testFieldGuidNullable", DbType = "char(36)", IsNullable = true)] + public Guid? TestFieldGuidNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldInt", DbType = "int(11)")] + public int TestFieldInt { get; set; } + + + [JsonProperty, Column(Name = "testFieldIntNullable", DbType = "int(11)", IsNullable = true)] + public int? TestFieldIntNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldLineString", DbType = "linestring", IsNullable = true)] + public MygisGeometry TestFieldLineString { get; set; } + + + [JsonProperty, Column(Name = "testFieldLong", DbType = "bigint(20)")] + public long TestFieldLong { get; set; } + + + [JsonProperty, Column(Name = "testFieldMultiLineString", DbType = "multilinestring", IsNullable = true)] + public MygisGeometry TestFieldMultiLineString { get; set; } + + + [JsonProperty, Column(Name = "testFieldMultiPoint", DbType = "multipoint", IsNullable = true)] + public MygisGeometry TestFieldMultiPoint { get; set; } + + + [JsonProperty, Column(Name = "testFieldMultiPolygon", DbType = "multipolygon", IsNullable = true)] + public MygisGeometry TestFieldMultiPolygon { get; set; } + + + [JsonProperty, Column(Name = "testFieldPoint", DbType = "point", IsNullable = true)] + public MygisGeometry TestFieldPoint { get; set; } + + + [JsonProperty, Column(Name = "testFieldPolygon", DbType = "polygon", IsNullable = true)] + public MygisGeometry TestFieldPolygon { get; set; } + + + [JsonProperty, Column(Name = "testFieldSByte", DbType = "tinyint(3)")] + public sbyte TestFieldSByte { get; set; } + + + [JsonProperty, Column(Name = "testFieldSByteNullable", DbType = "tinyint(3)", IsNullable = true)] + public sbyte? TestFieldSByteNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldShort", DbType = "smallint(6)")] + public short TestFieldShort { get; set; } + + + [JsonProperty, Column(Name = "testFieldShortNullable", DbType = "smallint(6)", IsNullable = true)] + public short? TestFieldShortNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldString", DbType = "varchar(255)", IsNullable = true)] + public string TestFieldString { get; set; } + + + [JsonProperty, Column(Name = "testFieldTimeSpan", DbType = "time")] + public TimeSpan TestFieldTimeSpan { get; set; } + + + [JsonProperty, Column(Name = "testFieldTimeSpanNullable", DbType = "time", IsNullable = true)] + public TimeSpan? TestFieldTimeSpanNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldUInt", DbType = "int(10) unsigned")] + public uint TestFieldUInt { get; set; } + + + [JsonProperty, Column(Name = "testFieldUIntNullable", DbType = "int(10) unsigned", IsNullable = true)] + public uint? TestFieldUIntNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldULong", DbType = "bigint(20) unsigned")] + public ulong TestFieldULong { get; set; } + + + [JsonProperty, Column(Name = "testFieldULongNullable", DbType = "bigint(20) unsigned", IsNullable = true)] + public ulong? TestFieldULongNullable { get; set; } + + + [JsonProperty, Column(Name = "testFieldUShort", DbType = "smallint(5) unsigned")] + public ushort TestFieldUShort { get; set; } + + + [JsonProperty, Column(Name = "testFieldUShortNullable", DbType = "smallint(5) unsigned", IsNullable = true)] + public ushort? TestFieldUShortNullable { get; set; } + + + [JsonProperty, Column(Name = "testFielLongNullable", DbType = "bigint(20)", IsNullable = true)] + public long? TestFielLongNullable { get; set; } + + internal static IFreeSql mysql => null; + public static FreeSql.ISelect Select => mysql.Select(); + + public static int ItemCacheTimeout = 180; + public static Tb_alltype GetItem(int Id) => Select.Where(a => a.Id == Id).Caching(ItemCacheTimeout, string.Concat("test:tb_alltype:", Id)).ToOne(); + + public static long Delete(int Id) { + var affrows = mysql.Delete().Where(a => a.Id == Id).ExecuteAffrows(); + if (ItemCacheTimeout > 0) RemoveCache(new Tb_alltype { Id = Id }); + return affrows; + } + + internal static void RemoveCache(Tb_alltype item) => RemoveCache(item == null ? null : new[] { item }); + internal static void RemoveCache(IEnumerable items) { + if (ItemCacheTimeout <= 0 || items == null || items.Any() == false) return; + var keys = new string[items.Count() * 1]; + var keysIdx = 0; + foreach (var item in items) { + keys[keysIdx++] = string.Concat("test:tb_alltype:", item.Id); + } + if (mysql.Ado.TransactionCurrentThread != null) mysql.Ado.TransactionPreRemoveCache(keys); + else mysql.Cache.Remove(keys); + } + + /// + /// ӣֵ UpdateӰΪ 0 Insert + /// + public void Save() { + if (this.Id != default(int)) { + var affrows = mysql.Update().Where(a => a.Id == Id).ExecuteAffrows(); + if (affrows > 0) return; + } + this.Id = (int)mysql.Insert().AppendData(this).ExecuteIdentity(); + } + + } + + public enum Tb_alltypeTESTFIELDENUM1 { + E1 = 1, E2, E3, E5 + } + public enum Tb_alltypeTESTFIELDENUM1NULLABLE { + E1 = 1, E2, E3, E5 + } + [Flags] + public enum Tb_alltypeTESTFIELDENUM2 : long { + F1 = 1, F2 = 2, F3 = 4 + } + [Flags] + public enum Tb_alltypeTESTFIELDENUM2NULLABLE : long { + F1 = 1, F2 = 2, F3 = 4 + } + + + [Table(Name = "tb_alltype")] + class TableAllType { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + + public bool testFieldBool { get; set; } + public sbyte testFieldSByte { get; set; } + public short testFieldShort { get; set; } + public int testFieldInt { get; set; } + public long testFieldLong { get; set; } + public byte testFieldByte { get; set; } + public ushort testFieldUShort { get; set; } + public uint testFieldUInt { get; set; } + public ulong testFieldULong { get; set; } + public double testFieldDouble { get; set; } + public float testFieldFloat { get; set; } + public decimal testFieldDecimal { get; set; } + public TimeSpan testFieldTimeSpan { get; set; } + public DateTime testFieldDateTime { get; set; } + public byte[] testFieldBytes { get; set; } + public string testFieldString { get; set; } + public Guid testFieldGuid { get; set; } + + public bool? testFieldBoolNullable { get; set; } + public sbyte? testFieldSByteNullable { get; set; } + public short? testFieldShortNullable { get; set; } + public int? testFieldIntNullable { get; set; } + public long? testFielLongNullable { get; set; } + public byte? testFieldByteNullable { get; set; } + public ushort? testFieldUShortNullable { get; set; } + public uint? testFieldUIntNullable { get; set; } + public ulong? testFieldULongNullable { get; set; } + public double? testFieldDoubleNullable { get; set; } + public float? testFieldFloatNullable { get; set; } + public decimal? testFieldDecimalNullable { get; set; } + public TimeSpan? testFieldTimeSpanNullable { get; set; } + public DateTime? testFieldDateTimeNullable { get; set; } + public Guid? testFieldGuidNullable { get; set; } + + public MygisPoint testFieldPoint { get; set; } + public MygisLineString testFieldLineString { get; set; } + public MygisPolygon testFieldPolygon { get; set; } + public MygisMultiPoint testFieldMultiPoint { get; set; } + public MygisMultiLineString testFieldMultiLineString { get; set; } + public MygisMultiPolygon testFieldMultiPolygon { get; set; } + + public TableAllTypeEnumType1 testFieldEnum1 { get; set; } + public TableAllTypeEnumType1? testFieldEnum1Nullable { get; set; } + public TableAllTypeEnumType2 testFieldEnum2 { get; set; } + public TableAllTypeEnumType2? testFieldEnum2Nullable { get; set; } + } + + public enum TableAllTypeEnumType1 { e1, e2, e3, e5 } + [Flags] public enum TableAllTypeEnumType2 { f1, f2, f3 } + } +} diff --git a/FreeSql.Tests/MySql/MySqlDbFirstTest.cs b/FreeSql.Tests/MySql/MySqlDbFirstTest.cs new file mode 100644 index 00000000..cff6585d --- /dev/null +++ b/FreeSql.Tests/MySql/MySqlDbFirstTest.cs @@ -0,0 +1,21 @@ +using FreeSql.DataAnnotations; +using System; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlDbFirstTest { + [Fact] + public void GetDatabases() { + + var t1 = g.mysql.DbFirst.GetDatabases(); + + } + + [Fact] + public void GetTablesByDatabase() { + + var t2 = g.mysql.DbFirst.GetTablesByDatabase(g.mysql.DbFirst.GetDatabases()[0]); + + } + } +} diff --git a/FreeSql.Tests/MySql/MySqlExpressionTest.cs b/FreeSql.Tests/MySql/MySqlExpressionTest.cs new file mode 100644 index 00000000..b0fdfd10 --- /dev/null +++ b/FreeSql.Tests/MySql/MySqlExpressionTest.cs @@ -0,0 +1,82 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.MySql { + public class MySqlExpressionTest { + + ISelect select => g.mysql.Select(); + + [Table(Name = "tb_topic")] + class Topic { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int Clicks { get; set; } + public int TestTypeInfoGuid { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + class TestTypeInfo { + public int Guid { get; set; } + public int ParentId { get; set; } + public TestTypeParentInfo Parent { get; set; } + public string Name { get; set; } + } + class TestTypeParentInfo { + public int Id { get; set; } + public string Name { get; set; } + + public List Types { get; set; } + } + + [Fact] + public void StartsWith() { + } + [Fact] + public void EndsWith() { + } + [Fact] + public void Contains() { + } + [Fact] + public void ToLower() { + } + [Fact] + public void ToUpper() { + + } + [Fact] + public void Substring() { + } + [Fact] + public void Length() { + } + [Fact] + public void IndexOf() { + } + [Fact] + public void PadLeft() { + } + [Fact] + public void PadRight() { + } + [Fact] + public void Trim() { + } + [Fact] + public void TrimStart() { + } + [Fact] + public void TrimEnd() { + } + [Fact] + public void Replace() { + } + [Fact] + public void CompareTo() { + } + } +} diff --git a/FreeSql.Tests/UnitTest1.cs b/FreeSql.Tests/UnitTest1.cs new file mode 100644 index 00000000..c60368d0 --- /dev/null +++ b/FreeSql.Tests/UnitTest1.cs @@ -0,0 +1,150 @@ +using FreeSql.DataAnnotations; +using FreeSql; +using System; +using System.Collections.Generic; +using Xunit; + +namespace FreeSql.Tests { + public class UnitTest1 { + + ISelect select => g.mysql.Select(); + [Fact] + public void Test1() { + + var t1 = g.mysql.Select().Where("").Where(a => a.Id > 0).Skip(100).Limit(200).ToSql(); + var t2 = g.mysql.Select().As("b").Where("").Where(a => a.Id > 0).Skip(100).Limit(200).ToSql(); + + + var sql1 = select.LeftJoin(a => a.Type.Guid == a.TypeGuid).ToSql(); + var sql2 = select.LeftJoin((a, b) => a.TypeGuid == b.Guid && b.Name == "111").ToSql(); + var sql3 = select.LeftJoin("TestTypeInfo b on b.Guid = a.TypeGuid").ToSql(); + + //g.mysql.Select().Join((a, b, c) => new Model.JoinResult3( + // Model.JoinType.LeftJoin, a.TypeGuid == b.Guid, + // Model.JoinType.InnerJoin, c.Id == b.ParentId && c.Name == "xxx") + //); + + //var sql4 = select.From((a, b, c) => new SelectFrom() + // .InnerJoin(a.TypeGuid == b.Guid) + // .LeftJoin(c.Id == b.ParentId) + // .Where(b.Name == "xxx")) + //.Where(a => a.Id == 1).ToSql(); + + var sql4 = select.From((s, b, c) => s + .InnerJoin(a => a.TypeGuid == b.Guid) + .LeftJoin(a => c.Id == b.ParentId) + .Where(a => b.Name == "xxx")); + //.Where(a => a.Id == 1).ToSql(); + + + var list111 = select.From((s, b, c) => s + .InnerJoin(a => a.TypeGuid == b.Guid) + .LeftJoin(a => c.Id == b.ParentId) + .Where(a => b.Name != "xxx")).ToList((a, b, c) => new { + a.Id, + a.Title, + a.Type, + ccc = new { a.Id, a.Title }, + tp = a.Type, + tp2 = new { + a.Id, + tp2 = a.Type.Name + }, + tp3 = new { + a.Id, + tp33 = new { + a.Id + } + } + }); + + var ttt122 = g.mysql.Select().Where(a => a.Id > 0).ToSql(); + + + + + var sql5 = g.mysql.Select().From((s, b, c) => s).Where((a, b, c) => a.Id == b.ParentId).ToSql(); + + + + + + //((JoinType.LeftJoin, a.TypeGuid == b.Guid), (JoinType.InnerJoin, b.ParentId == c.Id) + + var t11112 = g.mysql.Select().ToList(a => new { + a.Id, a.Title, a.Type, + ccc = new { a.Id, a.Title }, + tp = a.Type, + tp2 = new { + a.Id, tp2 = a.Type.Name + }, + tp3 = new { + a.Id, + tp33 = new { + a.Id + } + } + + }); + + var t100 = g.mysql.Select().Where("").Where(a => a.Id > 0).Skip(100).Limit(200).Caching(50).ToList(); + var t101 = g.mysql.Select().As("b").Where("").Where(a => a.Id > 0).Skip(100).Limit(200).Caching(50).ToList(); + + + var t1111 = g.mysql.Select().ToList(a => new { a.Id, a.Title, a.Type }); + + var t2222 = g.mysql.Select().ToList(a => new { a.Id, a.Title, a.Type.Name }); + + var t3 = g.mysql.Insert(new[] { new TestInfo { }, new TestInfo { } }).IgnoreColumns(a => a.Title).ToSql(); + var t4 = g.mysql.Insert(new[] { new TestInfo { }, new TestInfo { } }).IgnoreColumns(a => new { a.Title, a.CreateTime }).ToSql(); + var t5 = g.mysql.Insert(new[] { new TestInfo { }, new TestInfo { } }).IgnoreColumns(a => new { a.Title, a.TypeGuid, a.CreateTime }).ToSql(); + var t6 = g.mysql.Insert(new[] { new TestInfo { }, new TestInfo { } }).InsertColumns(a => new { a.Title }).ToSql(); + + var t7 = g.mysql.Update().ToSql(); + var t8 = g.mysql.Update().Where(new TestInfo { }).ToSql(); + var t9 = g.mysql.Update().Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).ToSql(); + var t10 = g.mysql.Update().Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).Where(a => a.Title == "111").ToSql(); + var t11 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).ToSql(); + var t12 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).Where(a => a.Title == "111").ToSql(); + + var t13 = g.mysql.Update().Set(a => a.Title, "222111").ToSql(); + var t14 = g.mysql.Update().Set(a => a.Title, "222111").Where(new TestInfo { }).ToSql(); + var t15 = g.mysql.Update().Set(a => a.Title, "222111").Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).ToSql(); + var t16 = g.mysql.Update().Set(a => a.Title, "222111").Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).Where(a => a.Title == "111").ToSql(); + var t17 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).Set(a => a.Title, "222111").ToSql(); + var t18 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).Set(a => a.Title, "222111").Where(a => a.Title == "111").ToSql(); + + var t19 = g.mysql.Update().Set(a => a.TypeGuid + 222111).ToSql(); + var t20 = g.mysql.Update().Set(a => a.TypeGuid + 222111).Where(new TestInfo { }).ToSql(); + var t21 = g.mysql.Update().Set(a => a.TypeGuid + 222111).Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).ToSql(); + var t22 = g.mysql.Update().Set(a => a.TypeGuid + 222111).Where(new[] { new TestInfo { Id = 1 }, new TestInfo { Id = 2 } }).Where(a => a.Title == "111").ToSql(); + var t23 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).Set(a => a.TypeGuid + 222111).ToSql(); + var t24 = g.mysql.Update().SetSource(new[] { new TestInfo { Id = 1, Title = "111" }, new TestInfo { Id = 2, Title = "222" } }).Set(a => a.TypeGuid + 222111).Where(a => a.Title == "111").ToSql(); + + } + } + + [Table(Name = "xxx", SelectFilter = " a.id > 0")] + class TestInfo { + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + public int TypeGuid { get; set; } + public TestTypeInfo Type { get; set; } + public string Title { get; set; } + public DateTime CreateTime { get; set; } + } + + class TestTypeInfo { + public int Guid { get; set; } + public int ParentId { get; set; } + public TestTypeParentInfo Parent { get; set; } + public string Name { get; set; } + } + + class TestTypeParentInfo { + public int Id { get; set; } + public string Name { get; set; } + + public List Types { get; set; } + } +} diff --git a/FreeSql.Tests/g.cs b/FreeSql.Tests/g.cs new file mode 100644 index 00000000..1f7f39b0 --- /dev/null +++ b/FreeSql.Tests/g.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + + +public class g { + + public static IFreeSql mysql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10") + .Build(); + + public static IFreeSql sqlserver = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=shop;Pooling=true;Max Pool Size=10") + .Build(); +} diff --git a/FreeSql.sln b/FreeSql.sln new file mode 100644 index 00000000..dd642cc8 --- /dev/null +++ b/FreeSql.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql", "FreeSql\FreeSql.csproj", "{AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests", "FreeSql.Tests\FreeSql.Tests.csproj", "{AA88EB04-4788-4180-AE68-7FA5ED17D98C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C6A74E2A-6660-473D-8852-B1D8348DB4E9}" + ProjectSection(SolutionItems) = preProject + Docs\codefirst.md = Docs\codefirst.md + Docs\dbfirst.md = Docs\dbfirst.md + Docs\delete.md = Docs\delete.md + Docs\insert.md = Docs\insert.md + readme.md = readme.md + Docs\select.md = Docs\select.md + Docs\update.md = Docs\update.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "xxx", "..\..\新建文件夹 (9)\xxx.csproj", "{6DC39740-0B26-4029-AB75-D436A7F666A2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|x64.Build.0 = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Debug|x86.Build.0 = Debug|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|Any CPU.Build.0 = Release|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|x64.ActiveCfg = Release|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|x64.Build.0 = Release|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|x86.ActiveCfg = Release|Any CPU + {AF9C50EC-6EB6-494B-9B3B-7EDBA6FD0EBB}.Release|x86.Build.0 = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|x64.Build.0 = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Debug|x86.Build.0 = Debug|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|Any CPU.Build.0 = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|x64.ActiveCfg = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|x64.Build.0 = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|x86.ActiveCfg = Release|Any CPU + {AA88EB04-4788-4180-AE68-7FA5ED17D98C}.Release|x86.Build.0 = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|x64.Build.0 = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Debug|x86.Build.0 = Debug|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|Any CPU.Build.0 = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|x64.ActiveCfg = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|x64.Build.0 = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|x86.ActiveCfg = Release|Any CPU + {6DC39740-0B26-4029-AB75-D436A7F666A2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {089687FD-5D25-40AB-BA8A-A10D1E137F98} + EndGlobalSection +EndGlobal diff --git a/FreeSql/DataAnnotations/ColumnAttribute.cs b/FreeSql/DataAnnotations/ColumnAttribute.cs new file mode 100644 index 00000000..0fcabff9 --- /dev/null +++ b/FreeSql/DataAnnotations/ColumnAttribute.cs @@ -0,0 +1,32 @@ +using System; + +namespace FreeSql.DataAnnotations { + public class ColumnAttribute : Attribute { + + /// + /// 数据库列名 + /// + public string Name { get; set; } + /// + /// 指定数据库旧的列名,修改实体属性命名时,同时设置此参数为修改之前的值,CodeFirst才可以正确修改数据库字段;否则将视为【新增字段】 + /// + public string OldName { get; set; } + /// + /// 数据库类型,如: varchar(255) + /// + public string DbType { get; set; } + + /// + /// 主键 + /// + public bool IsPrimary { get; set; } + /// + /// 自增标识 + /// + public bool IsIdentity { get; set; } + /// + /// 是否可DBNull + /// + public bool IsNullable { get; set; } + } +} diff --git a/FreeSql/DataAnnotations/TableAttribute.cs b/FreeSql/DataAnnotations/TableAttribute.cs new file mode 100644 index 00000000..c2773633 --- /dev/null +++ b/FreeSql/DataAnnotations/TableAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace FreeSql.DataAnnotations { + public class TableAttribute : Attribute { + + /// + /// 数据库表名 + /// + public string Name { get; set; } + /// + /// 指定数据库旧的表名,修改实体命名时,同时设置此参数为修改之前的值,CodeFirst才可以正确修改数据库表;否则将视为【创建新表】 + /// + public string OldName { get; set; } + /// + /// 查询过滤SQL,实现类似 a.IsDeleted = 1 功能 + /// + public string SelectFilter { get; set; } + } +} diff --git a/FreeSql/DatabaseModel/DBColumnInfo.cs b/FreeSql/DatabaseModel/DBColumnInfo.cs new file mode 100644 index 00000000..b4351f7c --- /dev/null +++ b/FreeSql/DatabaseModel/DBColumnInfo.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.DatabaseModel { + public class DbColumnInfo { + /// + /// 所属表 + /// + public DbTableInfo Table { get; internal set; } + /// + /// 列名 + /// + public string Name { get; internal set; } + /// + /// 映射到 C# 类型 + /// + public Type CsType { get; internal set; } + /// + /// 数据库枚举类型int值 + /// + public int DbType { get; internal set; } + /// + /// 数据库类型,字符串,varchar + /// + public string DbTypeText { get; internal set; } + /// + /// 数据库类型,字符串,varchar(255) + /// + public string DbTypeTextFull { get; internal set; } + /// + /// 最大长度 + /// + public int MaxLength { get; internal set; } + /// + /// 主键 + /// + public bool IsPrimary { get; internal set; } + /// + /// 自增标识 + /// + public bool IsIdentity { get; internal set; } + /// + /// 是否可DBNull + /// + public bool IsNullable { get; internal set; } + /// + /// 备注 + /// + public string Coment { get; internal set; } + } +} diff --git a/FreeSql/DatabaseModel/DBTableInfo.cs b/FreeSql/DatabaseModel/DBTableInfo.cs new file mode 100644 index 00000000..f5251e64 --- /dev/null +++ b/FreeSql/DatabaseModel/DBTableInfo.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.DatabaseModel { + public class DbTableInfo { + /// + /// 唯一标识 + /// + public string Id { get; internal set; } + /// + /// SqlServer下是Owner、PostgreSQL下是Schema、MySql下是数据库名 + /// + public string Schema { get; internal set; } + /// + /// 表名 + /// + public string Name { get; internal set; } + /// + /// 表/视图 + /// + public DbTableType Type { get; set; } + /// + /// 列 + /// + public List Columns { get; internal set; } = new List(); + /// + /// 自增列 + /// + public List Identitys { get; internal set; } = new List(); + /// + /// 主键/组合 + /// + public List Primarys { get; internal set; } = new List(); + /// + /// 唯一键/组合 + /// + public List> Uniques { get; internal set; } = new List>(); + /// + /// 索引/组合 + /// + public List> Indexes { get; internal set; } = new List>(); + /// + /// 外键 + /// + public List Foreigns { get; internal set; } = new List(); + } + + public enum DbTableType { + TABLE, VIEW, StoreProcedure + } +} diff --git a/FreeSql/DatabaseModel/DbForeignInfo.cs b/FreeSql/DatabaseModel/DbForeignInfo.cs new file mode 100644 index 00000000..ef805e24 --- /dev/null +++ b/FreeSql/DatabaseModel/DbForeignInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.DatabaseModel { + public class DbForeignInfo { + public DbTableInfo Table { get; internal set; } + public List Columns { get; internal set; } = new List(); + public DbTableInfo ReferencedTable { get; internal set; } + public List ReferencedColumns { get; internal set; } = new List(); + + } +} diff --git a/FreeSql/Extensions/NpgsqlTypesExtensions.cs b/FreeSql/Extensions/NpgsqlTypesExtensions.cs new file mode 100644 index 00000000..2ca57bf7 --- /dev/null +++ b/FreeSql/Extensions/NpgsqlTypesExtensions.cs @@ -0,0 +1,39 @@ +using NpgsqlTypes; +using System; +using System.Collections; + +namespace NpgsqlTypes { + public static class FreeSqlExtensions { + + public static string To1010(this BitArray ba) { + char[] ret = new char[ba.Length]; + for (int a = 0; a < ba.Length; a++) ret[a] = ba[a] ? '1' : '0'; + return new string(ret); + } + + /// + /// 将 1010101010 这样的二进制字符串转换成 BitArray + /// + /// 1010101010 + /// + public static BitArray ToBitArray(this string _1010Str) { + if (_1010Str == null) return null; + BitArray ret = new BitArray(_1010Str.Length); + for (int a = 0; a < _1010Str.Length; a++) ret[a] = _1010Str[a] == '1'; + return ret; + } + + public static NpgsqlRange ToNpgsqlRange(this string that) { + var s = that; + if (string.IsNullOrEmpty(s) || s == "empty") return NpgsqlRange.Empty; + string s1 = s.Trim('(', ')', '[', ']'); + string[] ss = s1.Split(new char[] { ',' }, 2); + if (ss.Length != 2) return NpgsqlRange.Empty; + T t1 = default(T); + T t2 = default(T); + if (!string.IsNullOrEmpty(ss[0])) t1 = (T)Convert.ChangeType(ss[0], typeof(T)); + if (!string.IsNullOrEmpty(ss[1])) t2 = (T)Convert.ChangeType(ss[1], typeof(T)); + return new NpgsqlRange(t1, s[0] == '[', s[0] == '(', t2, s[s.Length - 1] == ']', s[s.Length - 1] == ')'); + } + } +} \ No newline at end of file diff --git a/FreeSql/Extensions/StringExtensions.cs b/FreeSql/Extensions/StringExtensions.cs new file mode 100644 index 00000000..a43aecee --- /dev/null +++ b/FreeSql/Extensions/StringExtensions.cs @@ -0,0 +1,31 @@ +public static class StringExtensions { + + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatMySql(this string that, params object[] args) => _mysqlAdo.Addslashes(that, args); + static FreeSql.MySql.MySqlAdo _mysqlAdo = new FreeSql.MySql.MySqlAdo(); + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatSqlServer(this string that, params object[] args) => _sqlserverAdo.Addslashes(that, args); + static FreeSql.SqlServer.SqlServerAdo _sqlserverAdo = new FreeSql.SqlServer.SqlServerAdo(); + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatPostgreSQL(this string that, params object[] args) => _postgresqlAdo.Addslashes(that, args); + static FreeSql.PostgreSQL.PostgreSQLAdo _postgresqlAdo = new FreeSql.PostgreSQL.PostgreSQLAdo(); +} + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute : Attribute { } +} \ No newline at end of file diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj new file mode 100644 index 00000000..e4e0aab1 --- /dev/null +++ b/FreeSql/FreeSql.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + + diff --git a/FreeSql/FreeSqlBuilder.cs b/FreeSql/FreeSqlBuilder.cs new file mode 100644 index 00000000..3a90ea62 --- /dev/null +++ b/FreeSql/FreeSqlBuilder.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql { + public class FreeSqlBuilder { + IDistributedCache _cache; + ILogger _logger; + DataType _dataType; + string _masterConnectionString; + string[] _slaveConnectionString; + + /// + /// 使用缓存,不指定默认使用内存 + /// + /// 缓存现实 + /// + public FreeSqlBuilder UseCache(IDistributedCache cache) { + _cache = cache; + return this; + } + + /// + /// 使用日志,不指定默认输出控制台 + /// + /// + /// + public FreeSqlBuilder UseLogger(ILogger logger) { + _logger = logger; + return this; + } + /// + /// 使用连接串 + /// + /// 数据库类型 + /// 数据库连接串 + /// + public FreeSqlBuilder UseConnectionString(DataType dataType, string connectionString) { + _dataType = dataType; + _masterConnectionString = connectionString; + return this; + } + /// + /// 使用从数据库,支持多个 + /// + /// 从数据库连接串 + /// + public FreeSqlBuilder UseSlave(params string[] slaveConnectionString) { + _slaveConnectionString = slaveConnectionString; + return this; + } + + public IFreeSql Build() { + switch(_dataType) { + case DataType.MySql: return new MySql.MySqlProvider(_cache, null, _masterConnectionString, _slaveConnectionString, _logger); + case DataType.SqlServer: return new SqlServer.SqlServerProvider(_cache, null, _masterConnectionString, _slaveConnectionString, _logger); + case DataType.PostgreSQL: return new MySql.MySqlProvider(_cache, null, _masterConnectionString, _slaveConnectionString, _logger); + } + return null; + } + } + + public enum DataType { MySql, SqlServer, PostgreSQL } +} diff --git a/FreeSql/FreeUtil.cs b/FreeSql/FreeUtil.cs new file mode 100644 index 00000000..fefe2ba6 --- /dev/null +++ b/FreeSql/FreeUtil.cs @@ -0,0 +1,34 @@ +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; + +public static class FreeUtil { + + private static DateTime dt1970 = new DateTime(1970, 1, 1); + private static ThreadLocal rnd = new ThreadLocal(); + private static readonly int __staticMachine = ((0x00ffffff & Environment.MachineName.GetHashCode()) + +#if NETSTANDARD1_5 || NETSTANDARD1_6 + 1 +#else + AppDomain.CurrentDomain.Id +#endif + ) & 0x00ffffff; + private static readonly int __staticPid = Process.GetCurrentProcess().Id; + private static int __staticIncrement = rnd.Value.Next(); + /// + /// 生成类似Mongodb的ObjectId有序、不重复Guid + /// + /// + public static Guid NewMongodbId() { + var now = DateTime.Now; + var uninxtime = (int)now.Subtract(dt1970).TotalSeconds; + int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; + var rand = rnd.Value.Next(0, int.MaxValue); + var guid = $"{uninxtime.ToString("x8").PadLeft(8, '0')}{__staticMachine.ToString("x8").PadLeft(8, '0').Substring(2, 6)}{__staticPid.ToString("x8").PadLeft(8, '0').Substring(6, 2)}{increment.ToString("x8").PadLeft(8, '0')}{rand.ToString("x8").PadLeft(8, '0')}"; + return Guid.Parse(guid); + } +} \ No newline at end of file diff --git a/FreeSql/Generator/TemplateEngin.cs b/FreeSql/Generator/TemplateEngin.cs new file mode 100644 index 00000000..4760937d --- /dev/null +++ b/FreeSql/Generator/TemplateEngin.cs @@ -0,0 +1,640 @@ +using Microsoft.CSharp; +using System; +using System.CodeDom.Compiler; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; + +namespace FreeSql.Generator { + public class TemplateEngin : IDisposable { + public interface ITemplateOutput { + /// + /// + /// + /// 返回内容 + /// 渲染对象 + /// 当前文件路径 + /// + /// + TemplateReturnInfo OuTpUt(StringBuilder tOuTpUt, IDictionary oPtIoNs, string rEfErErFiLeNaMe, TemplateEngin tEmPlAtEsEnDeR); + } + public class TemplateReturnInfo { + public Dictionary Blocks; + public StringBuilder Sb; + } + public delegate bool TemplateIf(object exp); + public delegate void TemplatePrint(params object[] parms); + + private static int _view = 0; + private static Regex _reg = new Regex(@"\{(\$TEMPLATE__CODE|\/\$TEMPLATE__CODE|import\s+|module\s+|extends\s+|block\s+|include\s+|for\s+|if\s+|#|\/for|elseif|else|\/if|\/block|\/module)([^\}]*)\}", RegexOptions.Compiled); + private static Regex _reg_forin = new Regex(@"^([\w_]+)\s*,?\s*([\w_]+)?\s+in\s+(.+)", RegexOptions.Compiled); + private static Regex _reg_foron = new Regex(@"^([\w_]+)\s*,?\s*([\w_]+)?,?\s*([\w_]+)?\s+on\s+(.+)", RegexOptions.Compiled); + private static Regex _reg_forab = new Regex(@"^([\w_]+)\s+([^,]+)\s*,\s*(.+)", RegexOptions.Compiled); + private static Regex _reg_miss = new Regex(@"\{\/?miss\}", RegexOptions.Compiled); + private static Regex _reg_code = new Regex(@"(\{%|%\})", RegexOptions.Compiled); + private static Regex _reg_syntax = new Regex(@"<(\w+)\s+@(if|for|else)\s*=""([^""]*)""", RegexOptions.Compiled); + private static Regex _reg_htmltag = new Regex(@"<\/?\w+[^>]*>", RegexOptions.Compiled); + private static Regex _reg_blank = new Regex(@"\s+", RegexOptions.Compiled); + private static Regex _reg_complie_undefined = new Regex(@"(当前上下文中不存在名称)?“(\w+)”", RegexOptions.Compiled); + + private Dictionary _cache = new Dictionary(); + private object _cache_lock = new object(); + private string _viewDir; + private string[] _usings; + private FileSystemWatcher _fsw = new FileSystemWatcher(); + + public TemplateEngin(string viewDir, params string[] usings) { + _viewDir = Utils.TranslateUrl(viewDir); + _usings = usings; + _fsw = new FileSystemWatcher(_viewDir); + _fsw.IncludeSubdirectories = true; + _fsw.Changed += ViewDirChange; + _fsw.Renamed += ViewDirChange; + _fsw.EnableRaisingEvents = true; + } + public void Dispose() { + _fsw.Dispose(); + } + void ViewDirChange(object sender, FileSystemEventArgs e) { + string filename = e.FullPath.ToLower(); + lock (_cache_lock) { + _cache.Remove(filename); + } + } + public TemplateReturnInfo RenderFile2(StringBuilder sb, IDictionary options, string filename, string refererFilename) { + if (filename[0] == '/' || string.IsNullOrEmpty(refererFilename)) refererFilename = _viewDir; + //else refererFilename = Path.GetDirectoryName(refererFilename); + string filename2 = Utils.TranslateUrl(filename, refererFilename); + ITemplateOutput tpl; + if (_cache.TryGetValue(filename2, out tpl) == false) { + string tplcode = File.Exists(filename2) == false ? string.Concat("文件不存在 ", filename) : Utils.ReadTextFile(filename2); + tpl = Parser(tplcode, _usings, options); + lock (_cache_lock) { + if (_cache.ContainsKey(filename2) == false) { + _cache.Add(filename2, tpl); + } + } + } + try { + return tpl.OuTpUt(sb, options, filename2, this); + } catch (Exception ex) { + TemplateReturnInfo ret = sb == null ? + new TemplateReturnInfo { Sb = new StringBuilder(), Blocks = new Dictionary() } : + new TemplateReturnInfo { Sb = sb, Blocks = new Dictionary() }; + ret.Sb.Append(refererFilename); + ret.Sb.Append(" -> "); + ret.Sb.Append(filename); + ret.Sb.Append("\r\n"); + ret.Sb.Append(ex.Message); + ret.Sb.Append("\r\n"); + ret.Sb.Append(ex.StackTrace); + return ret; + } + } + public string RenderFile(string filename, IDictionary options) { + TemplateReturnInfo ret = this.RenderFile2(null, options, filename, null); + return ret.Sb.ToString(); + } + private static ITemplateOutput Parser(string tplcode, string[] usings, IDictionary options) { + int view = Interlocked.Increment(ref _view); + StringBuilder sb = new StringBuilder(); + IDictionary options_copy = new Hashtable(); + foreach (DictionaryEntry options_de in options) options_copy[options_de.Key] = options_de.Value; + sb.AppendFormat(@" +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions;{1} + +//namespace TplDynamicCodeGenerate {{ + public class TplDynamicCodeGenerate_view{0} : FreeSql.Generator.TemplateEngin.ITemplateOutput {{ + public FreeSql.Generator.TemplateEngin.TemplateReturnInfo OuTpUt(StringBuilder tOuTpUt, IDictionary oPtIoNs, string rEfErErFiLeNaMe, FreeSql.Generator.TemplateEngin tEmPlAtEsEnDeR) {{ + FreeSql.Generator.TemplateEngin.TemplateReturnInfo rTn = tOuTpUt == null ? + new FreeSql.Generator.TemplateEngin.TemplateReturnInfo {{ Sb = (tOuTpUt = new StringBuilder()), Blocks = new Dictionary() }} : + new FreeSql.Generator.TemplateEngin.TemplateReturnInfo {{ Sb = tOuTpUt, Blocks = new Dictionary() }}; + Dictionary TPL__blocks = rTn.Blocks; + Stack TPL__blocks_stack = new Stack(); + int[] TPL__blocks_stack_peek; + List TPL__forc = new List(); + Func pRoCeSsOpTiOnS = new Func(delegate () {{ + IDictionary nEwoPtIoNs = new Hashtable(); + foreach (DictionaryEntry oPtIoNs_dE in oPtIoNs) + nEwoPtIoNs[oPtIoNs_dE.Key] = oPtIoNs_dE.Value; + foreach (IDictionary TPL__forc_dIc in TPL__forc) + foreach (DictionaryEntry TPL__forc_dIc_dE in TPL__forc_dIc) + nEwoPtIoNs[TPL__forc_dIc_dE.Key] = TPL__forc_dIc_dE.Value; + return nEwoPtIoNs; + }}); + FreeSql.Generator.TemplateEngin.TemplateIf tPlIf = delegate(object exp) {{ + if (exp is bool) return (bool)exp; + if (exp == null) return false; + if (exp is int && (int)exp == 0) return false; + if (exp is string && (string)exp == string.Empty) return false; + if (exp is long && (long)exp == 0) return false; + if (exp is short && (short)exp == 0) return false; + if (exp is byte && (byte)exp == 0) return false; + if (exp is double && (double)exp == 0) return false; + if (exp is float && (float)exp == 0) return false; + if (exp is decimal && (decimal)exp == 0) return false; + return true; + }}; + FreeSql.Generator.TemplateEngin.TemplatePrint print = delegate(object[] pArMs) {{ + if (pArMs == null || pArMs.Length == 0) return; + foreach (object pArMs_A in pArMs) if (pArMs_A != null) tOuTpUt.Append(pArMs_A); + }}; + FreeSql.Generator.TemplateEngin.TemplatePrint Print = print;", view, usings?.Any() == true ? $"\r\nusing {string.Join(";\r\nusing ", usings)};" : ""); + + #region {miss}...{/miss}块内容将不被解析 + string[] tmp_content_arr = _reg_miss.Split(tplcode); + if (tmp_content_arr.Length > 1) { + sb.AppendFormat(@" + string[] TPL__MISS = new string[{0}];", Math.Ceiling(1.0 * (tmp_content_arr.Length - 1) / 2)); + int miss_len = -1; + for (int a = 1; a < tmp_content_arr.Length; a += 2) { + sb.Append(string.Concat(@" + TPL__MISS[", ++miss_len, @"] = """, Utils.GetConstString(tmp_content_arr[a]), @""";")); + tmp_content_arr[a] = string.Concat("{#TPL__MISS[", miss_len, "]}"); + } + tplcode = string.Join("", tmp_content_arr); + } + #endregion + #region 扩展语法如
+ tplcode = htmlSyntax(tplcode, 3); //
+ //处理 {% %} 块 c#代码 + tmp_content_arr = _reg_code.Split(tplcode); + if (tmp_content_arr.Length == 1) { + tplcode = Utils.GetConstString(tplcode) + .Replace("{%", "{$TEMPLATE__CODE}") + .Replace("%}", "{/$TEMPLATE__CODE}"); + } else { + tmp_content_arr[0] = Utils.GetConstString(tmp_content_arr[0]); + for (int a = 1; a < tmp_content_arr.Length; a += 4) { + tmp_content_arr[a] = "{$TEMPLATE__CODE}"; + tmp_content_arr[a + 2] = "{/$TEMPLATE__CODE}"; + tmp_content_arr[a + 3] = Utils.GetConstString(tmp_content_arr[a + 3]); + } + tplcode = string.Join("", tmp_content_arr); + } + #endregion + sb.Append(@" + tOuTpUt.Append("""); + + string error = null; + int tpl_tmpid = 0; + int forc_i = 0; + string extends = null; + Stack codeTree = new Stack(); + Stack forEndRepl = new Stack(); + sb.Append(_reg.Replace(tplcode, delegate (Match m) { + string _0 = m.Groups[0].Value; + if (!string.IsNullOrEmpty(error)) return _0; + + string _1 = m.Groups[1].Value.Trim(' ', '\t'); + string _2 = m.Groups[2].Value + .Replace("\\\\", "\\") + .Replace("\\\"", "\""); + _2 = Utils.ReplaceSingleQuote(_2); + + switch (_1) { + #region $TEMPLATE__CODE-------------------------------------------------- + case "$TEMPLATE__CODE": + codeTree.Push(_1); + return @"""); +"; + case "/$TEMPLATE__CODE": + string pop = codeTree.Pop(); + if (pop != "$TEMPLATE__CODE") { + codeTree.Push(pop); + error = "编译出错,{% 与 %} 并没有配对"; + return _0; + } + return @" + tOuTpUt.Append("""; + #endregion + case "include": + return string.Format(@"""); +tEmPlAtEsEnDeR.RenderFile2(tOuTpUt, pRoCeSsOpTiOnS(), ""{0}"", rEfErErFiLeNaMe); + tOuTpUt.Append(""", _2); + case "import": + return _0; + case "module": + return _0; + case "/module": + return _0; + case "extends": + //{extends ../inc/layout.html} + if (string.IsNullOrEmpty(extends) == false) return _0; + extends = _2; + return string.Empty; + case "block": + codeTree.Push("block"); + return string.Format(@"""); +TPL__blocks_stack_peek = new int[] {{ tOuTpUt.Length, 0 }}; +TPL__blocks_stack.Push(TPL__blocks_stack_peek); +TPL__blocks.Add(""{0}"", TPL__blocks_stack_peek); +tOuTpUt.Append(""", _2.Trim(' ', '\t')); + case "/block": + codeTreeEnd(codeTree, "block"); + return @"""); +TPL__blocks_stack_peek = TPL__blocks_stack.Pop(); +TPL__blocks_stack_peek[1] = tOuTpUt.Length - TPL__blocks_stack_peek[0]; +tOuTpUt.Append("""; + + #region ##--------------------------------------------------------- + case "#": + if (_2[0] == '#') + return string.Format(@"""); + try {{ Print({0}); }} catch {{ }} + tOuTpUt.Append(""", _2.Substring(1)); + return string.Format(@"""); + Print({0}); + tOuTpUt.Append(""", _2); + #endregion + #region for-------------------------------------------------------- + case "for": + forc_i++; + int cur_tpl_tmpid = tpl_tmpid; + string sb_endRepl = string.Empty; + StringBuilder sbfor = new StringBuilder(); + sbfor.Append(@""");"); + Match mfor = _reg_forin.Match(_2); + if (mfor.Success) { + string mfor1 = mfor.Groups[1].Value.Trim(' ', '\t'); + string mfor2 = mfor.Groups[2].Value.Trim(' ', '\t'); + sbfor.AppendFormat(@" +//new Action(delegate () {{ + IDictionary TPL__tmp{0} = new Hashtable(); + TPL__forc.Add(TPL__tmp{0}); + var TPL__tmp{1} = {3}; + var TPL__tmp{2} = {4};", ++tpl_tmpid, ++tpl_tmpid, ++tpl_tmpid, mfor.Groups[3].Value, mfor1); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor1, cur_tpl_tmpid + 3)); + if (options_copy.Contains(mfor1) == false) options_copy[mfor1] = null; + if (!string.IsNullOrEmpty(mfor2)) { + sbfor.AppendFormat(@" + var TPL__tmp{1} = {0}; + {0} = 0;", mfor2, ++tpl_tmpid); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor2, tpl_tmpid)); + if (options_copy.Contains(mfor2) == false) options_copy[mfor2] = null; + } + sbfor.AppendFormat(@" + if (TPL__tmp{1} != null) + foreach (var TPL__tmp{0} in TPL__tmp{1}) {{", ++tpl_tmpid, cur_tpl_tmpid + 2); + if (!string.IsNullOrEmpty(mfor2)) + sbfor.AppendFormat(@" + TPL__tmp{1}[""{0}""] = ++ {0};", mfor2, cur_tpl_tmpid + 1); + sbfor.AppendFormat(@" + TPL__tmp{1}[""{0}""] = TPL__tmp{2}; + {0} = TPL__tmp{2}; + tOuTpUt.Append(""", mfor1, cur_tpl_tmpid + 1, tpl_tmpid); + codeTree.Push("for"); + forEndRepl.Push(sb_endRepl); + return sbfor.ToString(); + } + mfor = _reg_foron.Match(_2); + if (mfor.Success) { + string mfor1 = mfor.Groups[1].Value.Trim(' ', '\t'); + string mfor2 = mfor.Groups[2].Value.Trim(' ', '\t'); + string mfor3 = mfor.Groups[3].Value.Trim(' ', '\t'); + sbfor.AppendFormat(@" +//new Action(delegate () {{ + IDictionary TPL__tmp{0} = new Hashtable(); + TPL__forc.Add(TPL__tmp{0}); + var TPL__tmp{1} = {3}; + var TPL__tmp{2} = {4};", ++tpl_tmpid, ++tpl_tmpid, ++tpl_tmpid, mfor.Groups[4].Value, mfor1); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor1, cur_tpl_tmpid + 3)); + if (options_copy.Contains(mfor1) == false) options_copy[mfor1] = null; + if (!string.IsNullOrEmpty(mfor2)) { + sbfor.AppendFormat(@" + var TPL__tmp{1} = {0};", mfor2, ++tpl_tmpid); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor2, tpl_tmpid)); + if (options_copy.Contains(mfor2) == false) options_copy[mfor2] = null; + } + if (!string.IsNullOrEmpty(mfor3)) { + sbfor.AppendFormat(@" + var TPL__tmp{1} = {0}; + {0} = 0;", mfor3, ++tpl_tmpid); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor3, tpl_tmpid)); + if (options_copy.Contains(mfor3) == false) options_copy[mfor3] = null; + } + sbfor.AppendFormat(@" + if (TPL__tmp{2} != null) + foreach (DictionaryEntry TPL__tmp{1} in TPL__tmp{2}) {{ + {0} = TPL__tmp{1}.Key; + TPL__tmp{3}[""{0}""] = {0};", mfor1, ++tpl_tmpid, cur_tpl_tmpid + 2, cur_tpl_tmpid + 1); + if (!string.IsNullOrEmpty(mfor2)) + sbfor.AppendFormat(@" + {0} = TPL__tmp{1}.Value; + TPL__tmp{2}[""{0}""] = {0};", mfor2, tpl_tmpid, cur_tpl_tmpid + 1); + if (!string.IsNullOrEmpty(mfor3)) + sbfor.AppendFormat(@" + TPL__tmp{1}[""{0}""] = ++ {0};", mfor3, cur_tpl_tmpid + 1); + sbfor.AppendFormat(@" + tOuTpUt.Append("""); + codeTree.Push("for"); + forEndRepl.Push(sb_endRepl); + return sbfor.ToString(); + } + mfor = _reg_forab.Match(_2); + if (mfor.Success) { + string mfor1 = mfor.Groups[1].Value.Trim(' ', '\t'); + sbfor.AppendFormat(@" +//new Action(delegate () {{ + IDictionary TPL__tmp{0} = new Hashtable(); + TPL__forc.Add(TPL__tmp{0}); + var TPL__tmp{1} = {5}; + {5} = {3} - 1; + if ({5} == null) {5} = 0; + var TPL__tmp{2} = {4} + 1; + while (++{5} < TPL__tmp{2}) {{ + TPL__tmp{0}[""{5}""] = {5}; + tOuTpUt.Append(""", ++tpl_tmpid, ++tpl_tmpid, ++tpl_tmpid, mfor.Groups[2].Value, mfor.Groups[3].Value, mfor1); + sb_endRepl = string.Concat(sb_endRepl, string.Format(@" + {0} = TPL__tmp{1};", mfor1, cur_tpl_tmpid + 1)); + if (options_copy.Contains(mfor1) == false) options_copy[mfor1] = null; + codeTree.Push("for"); + forEndRepl.Push(sb_endRepl); + return sbfor.ToString(); + } + return _0; + case "/for": + if (--forc_i < 0) return _0; + codeTreeEnd(codeTree, "for"); + return string.Format(@"""); + }}{0} + TPL__forc.RemoveAt(TPL__forc.Count - 1); +//}})(); + tOuTpUt.Append(""", forEndRepl.Pop()); + #endregion + #region if--------------------------------------------------------- + case "if": + codeTree.Push("if"); + return string.Format(@"""); + if ({1}tPlIf({0})) {{ + tOuTpUt.Append(""", _2[0] == '!' ? _2.Substring(1) : _2, _2[0] == '!' ? '!' : ' '); + case "elseif": + codeTreeEnd(codeTree, "if"); + codeTree.Push("if"); + return string.Format(@"""); + }} else if ({1}tPlIf({0})) {{ + tOuTpUt.Append(""", _2[0] == '!' ? _2.Substring(1) : _2, _2[0] == '!' ? '!' : ' '); + case "else": + codeTreeEnd(codeTree, "if"); + codeTree.Push("if"); + return @"""); + } else { + tOuTpUt.Append("""; + case "/if": + codeTreeEnd(codeTree, "if"); + return @"""); + } + tOuTpUt.Append("""; + #endregion + } + return _0; + })); + + sb.Append(@""");"); + if (string.IsNullOrEmpty(extends) == false) { + sb.AppendFormat(@" +FreeSql.Generator.TemplateEngin.TemplateReturnInfo eXtEnDs_ReT = tEmPlAtEsEnDeR.RenderFile2(null, pRoCeSsOpTiOnS(), ""{0}"", rEfErErFiLeNaMe); +string rTn_Sb_string = rTn.Sb.ToString(); +foreach(string eXtEnDs_ReT_blocks_key in eXtEnDs_ReT.Blocks.Keys) {{ + if (rTn.Blocks.ContainsKey(eXtEnDs_ReT_blocks_key)) {{ + int[] eXtEnDs_ReT_blocks_value = eXtEnDs_ReT.Blocks[eXtEnDs_ReT_blocks_key]; + eXtEnDs_ReT.Sb.Remove(eXtEnDs_ReT_blocks_value[0], eXtEnDs_ReT_blocks_value[1]); + int[] rTn_blocks_value = rTn.Blocks[eXtEnDs_ReT_blocks_key]; + eXtEnDs_ReT.Sb.Insert(eXtEnDs_ReT_blocks_value[0], rTn_Sb_string.Substring(rTn_blocks_value[0], rTn_blocks_value[1])); + foreach(string eXtEnDs_ReT_blocks_keyb in eXtEnDs_ReT.Blocks.Keys) {{ + if (eXtEnDs_ReT_blocks_keyb == eXtEnDs_ReT_blocks_key) continue; + int[] eXtEnDs_ReT_blocks_valueb = eXtEnDs_ReT.Blocks[eXtEnDs_ReT_blocks_keyb]; + if (eXtEnDs_ReT_blocks_valueb[0] >= eXtEnDs_ReT_blocks_value[0]) + eXtEnDs_ReT_blocks_valueb[0] = eXtEnDs_ReT_blocks_valueb[0] - eXtEnDs_ReT_blocks_value[1] + rTn_blocks_value[1]; + }} + eXtEnDs_ReT_blocks_value[1] = rTn_blocks_value[1]; + }} +}} +return eXtEnDs_ReT; +", extends); + } else { + sb.Append(@" +return rTn;"); + } + sb.Append(@" + } + } +//} +"); + var str = "FreeSql.Generator.TemplateEngin.TemplatePrint Print = print;"; + int dim_idx = sb.ToString().IndexOf(str) + str.Length; + foreach (string dic_name in options_copy.Keys) { + sb.Insert(dim_idx, string.Format(@" + dynamic {0} = oPtIoNs[""{0}""];", dic_name)); + } + //Console.WriteLine(sb.ToString()); + return Complie(sb.ToString(), @"TplDynamicCodeGenerate_view" + view); + } + private static string codeTreeEnd(Stack codeTree, string tag) { + string ret = string.Empty; + Stack pop = new Stack(); + foreach (string ct in codeTree) { + if (ct == "import" || + ct == "include") { + pop.Push(1); + } else if (ct == tag) { + pop.Push(2); + break; + } else { + if (string.IsNullOrEmpty(tag) == false) pop.Clear(); + break; + } + } + if (pop.Count == 0 && string.IsNullOrEmpty(tag) == false) + return string.Concat("语法错误,{", tag, "} {/", tag, "} 并没配对"); + while (pop.Count > 0 && pop.Pop() > 0) codeTree.Pop(); + return ret; + } + #region htmlSyntax + private static string htmlSyntax(string tplcode, int num) { + + while (num-- > 0) { + string[] arr = _reg_syntax.Split(tplcode); + + if (arr.Length == 1) break; + for (int a = 1; a < arr.Length; a += 4) { + string tag = string.Concat('<', arr[a]); + string end = string.Concat("'); + int fc = 1; + for (int b = a; fc > 0 && b < arr.Length; b += 4) { + if (b > a && arr[a].ToLower() == arr[b].ToLower()) fc++; + int bpos = 0; + while (true) { + int fa = arr[b + 3].IndexOf(tag, bpos); + int fb = arr[b + 3].IndexOf(end, bpos); + if (b == a) { + var z = arr[b + 3].IndexOf("/>"); + if ((fb == -1 || z < fb) && z != -1) { + var y = arr[b + 3].Substring(0, z + 2); + if (_reg_htmltag.IsMatch(y) == false) + fb = z - end.Length + 2; + } + } + if (fa == -1 && fb == -1) break; + if (fa != -1 && (fa < fb || fb == -1)) { + fc++; + bpos = fa + tag.Length; + continue; + } + if (fb != -1) fc--; + if (fc <= 0) { + var a1 = arr[a + 1]; + var end3 = string.Concat("{/", a1, "}"); + if (a1.ToLower() == "else") { + if (_reg_blank.Replace(arr[a - 4 + 3], "").EndsWith("{/if}", StringComparison.CurrentCultureIgnoreCase) == true) { + var idx = arr[a - 4 + 3].IndexOf("{/if}"); + arr[a - 4 + 3] = string.Concat(arr[a - 4 + 3].Substring(0, idx), arr[a - 4 + 3].Substring(idx + 5)); + //如果 @else="有条件内容",则变换成 elseif 条件内容 + if (_reg_blank.Replace(arr[a + 2], "").Length > 0) a1 = "elseif"; + end3 = "{/if}"; + } else { + arr[a] = string.Concat("指令 @", arr[a + 1], "='", arr[a + 2], "' 没紧接着 if/else 指令之后,无效. <", arr[a]); + arr[a + 1] = arr[a + 2] = string.Empty; + } + } + if (arr[a + 1].Length > 0) { + if (_reg_blank.Replace(arr[a + 2], "").Length > 0 || a1.ToLower() == "else") { + arr[b + 3] = string.Concat(arr[b + 3].Substring(0, fb + end.Length), end3, arr[b + 3].Substring(fb + end.Length)); + arr[a] = string.Concat("{", a1, " ", arr[a + 2], "}<", arr[a]); + arr[a + 1] = arr[a + 2] = string.Empty; + } else { + arr[a] = string.Concat('<', arr[a]); + arr[a + 1] = arr[a + 2] = string.Empty; + } + } + break; + } + bpos = fb + end.Length; + } + } + if (fc > 0) { + arr[a] = string.Concat("不严谨的html格式,请检查 ", arr[a], " 的结束标签, @", arr[a + 1], "='", arr[a + 2], "' 指令无效. <", arr[a]); + arr[a + 1] = arr[a + 2] = string.Empty; + } + } + if (arr.Length > 0) tplcode = string.Join(string.Empty, arr); + } + return tplcode; + } + #endregion + #region Complie + private static ITemplateOutput Complie(string cscode, string typename) { + var assemly = _compiler.Value.CompileCode(cscode); + return assemly.CreateObject(typename) as ITemplateOutput; + } + static ConcurrentDictionary _compiler_objs = new ConcurrentDictionary(); + static Lazy _compiler = new Lazy(() => { + var dlls = Directory.GetFiles(Directory.GetParent(Type.GetType("IFreeSql, FreeSql").Assembly.Location).FullName, "*.dll"); + var compiler = new CSScriptLib.RoslynEvaluator(); + compiler.DisableReferencingFromCode = false; + compiler.DebugBuild = true; + foreach (var dll in dlls) { + var ass = Assembly.LoadFile(dll); + compiler.ReferenceAssembly(ass); + } + return compiler; + }); + + #endregion + + #region Utils + public class Utils { + public static string ReplaceSingleQuote(object exp) { + //将 ' 转换成 " + string exp2 = string.Concat(exp); + int quote_pos = -1; + while (true) { + int first_pos = quote_pos = exp2.IndexOf('\'', quote_pos + 1); + if (quote_pos == -1) break; + while (true) { + quote_pos = exp2.IndexOf('\'', quote_pos + 1); + if (quote_pos == -1) break; + int r_cout = 0; + for (int p = 1; true; p++) { + if (exp2[quote_pos - p] == '\\') r_cout++; + else break; + } + if (r_cout % 2 == 0/* && quote_pos - first_pos > 2*/) { + string str1 = exp2.Substring(0, first_pos); + string str2 = exp2.Substring(first_pos + 1, quote_pos - first_pos - 1); + string str3 = exp2.Substring(quote_pos + 1); + string str4 = str2.Replace("\"", "\\\""); + quote_pos += str4.Length - str2.Length; + exp2 = string.Concat(str1, "\"", str4, "\"", str3); + break; + } + } + if (quote_pos == -1) break; + } + return exp2; + } + public static string GetConstString(object obj) { + return string.Concat(obj) + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\r", "\\r") + .Replace("\n", "\\n"); + } + + public static string ReadTextFile(string path) { + byte[] bytes = ReadFile(path); + return Encoding.UTF8.GetString(bytes).TrimStart((char)65279); + } + public static byte[] ReadFile(string path) { + if (File.Exists(path)) { + string destFileName = Path.GetTempFileName(); + File.Copy(path, destFileName, true); + int read = 0; + byte[] data = new byte[1024]; + using (MemoryStream ms = new MemoryStream()) { + using (FileStream fs = new FileStream(destFileName, FileMode.OpenOrCreate, FileAccess.Read)) { + do { + read = fs.Read(data, 0, data.Length); + if (read <= 0) break; + ms.Write(data, 0, read); + } while (true); + } + File.Delete(destFileName); + data = ms.ToArray(); + } + return data; + } + return new byte[] { }; + } + public static string TranslateUrl(string url) { + return TranslateUrl(url, null); + } + public static string TranslateUrl(string url, string baseDir) { + if (string.IsNullOrEmpty(baseDir)) baseDir = AppContext.BaseDirectory + "/"; + if (string.IsNullOrEmpty(url)) return Path.GetDirectoryName(baseDir); + if (url.StartsWith("~/")) url = url.Substring(1); + if (url.StartsWith("/")) return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(baseDir), url.TrimStart('/'))); + if (url.StartsWith("\\")) return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(baseDir), url.TrimStart('\\'))); + if (url.IndexOf(":\\") != -1) return url; + return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(baseDir), url)); + } + } + #endregion + } +} diff --git a/FreeSql/Generator/TemplateGenerator.cs b/FreeSql/Generator/TemplateGenerator.cs new file mode 100644 index 00000000..4b0281f3 --- /dev/null +++ b/FreeSql/Generator/TemplateGenerator.cs @@ -0,0 +1,71 @@ +using FreeSql.DatabaseModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.Generator { + public class TemplateGenerator { + + public void Build(IDbFirst dbfirst, string templateDirectory, string outputDirectory, params string[] database) { + if (dbfirst == null) throw new ArgumentException("dbfirst 参数不能为 null"); + if (string.IsNullOrEmpty(templateDirectory) || Directory.Exists(templateDirectory) == false) throw new ArgumentException("templateDirectory 目录不存在"); + if (string.IsNullOrEmpty(templateDirectory)) throw new ArgumentException("outputDirectory 不能为 null"); + if (database == null || database.Any() == false) throw new ArgumentException("database 参数不能为空"); + if (Directory.Exists(outputDirectory) == false) Directory.CreateDirectory(outputDirectory); + templateDirectory = new DirectoryInfo(templateDirectory).FullName; + outputDirectory = new DirectoryInfo(outputDirectory).FullName; + if (templateDirectory.IndexOf(outputDirectory, StringComparison.CurrentCultureIgnoreCase) != -1) throw new ArgumentException("outputDirectory 目录不能设置在 templateDirectory 目录内"); + var tables = dbfirst.GetTablesByDatabase(database); + var tpl = new TemplateEngin(templateDirectory, "FreeSql", "FreeSql.DatabaseModel"); + BuildEachDirectory(templateDirectory, outputDirectory, tpl, dbfirst, tables); + tpl.Dispose(); + } + + void BuildEachDirectory(string templateDirectory, string outputDirectory, TemplateEngin tpl, IDbFirst dbfirst, List tables) { + if (Directory.Exists(outputDirectory) == false) Directory.CreateDirectory(outputDirectory); + var files = Directory.GetFiles(templateDirectory); + foreach (var file in files) { + var fi = new FileInfo(file); + if (string.Compare(fi.Extension, ".FreeSql", true) == 0) { + var outputExtension = "." + fi.Name.Split('.')[1]; + if (fi.Name.StartsWith("for-table.")) { + foreach (var table in tables) { + var result = tpl.RenderFile(file, new Dictionary() { { "table", table }, { "dbfirst", dbfirst } }); + var outputName = table.Name + outputExtension; + var mcls = Regex.Match(result, @"\s+class\s+(\w+)"); + if (mcls.Success) outputName = mcls.Groups[1].Value + outputExtension; + var outputStream = Encoding.UTF8.GetBytes(result); + var fullname = outputDirectory + "/" + outputName; + if (File.Exists(fullname)) File.Delete(fullname); + using (var outfs = File.Open(fullname, FileMode.OpenOrCreate, FileAccess.Write)) { + outfs.Write(outputStream, 0, outputStream.Length); + outfs.Close(); + } + } + continue; + } else { + var result = tpl.RenderFile(file, new Dictionary() { { "tables", tables }, { "dbfirst", dbfirst } }); + var outputName = fi.Name; + var mcls = Regex.Match(result, @"\s+class\s+(\w+)"); + if (mcls.Success) outputName = mcls.Groups[1].Value + outputExtension; + var outputStream = Encoding.UTF8.GetBytes(result); + var fullname = outputDirectory + "/" + outputName; + if (File.Exists(fullname)) File.Delete(fullname); + using (var outfs = File.Open(fullname, FileMode.OpenOrCreate, FileAccess.Write)) { + outfs.Write(outputStream, 0, outputStream.Length); + outfs.Close(); + } + } + } + File.Copy(file, outputDirectory + file.Replace(templateDirectory, ""), true); + } + var dirs = Directory.GetDirectories(templateDirectory); + foreach(var dir in dirs) { + BuildEachDirectory(dir, outputDirectory + dir.Replace(templateDirectory, ""), tpl, dbfirst, tables); + } + } + } +} diff --git a/FreeSql/Interface/Curd/IDelete.cs b/FreeSql/Interface/Curd/IDelete.cs new file mode 100644 index 00000000..ebb31e0e --- /dev/null +++ b/FreeSql/Interface/Curd/IDelete.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface IDelete where T1 : class { + /// + /// lambda表达式条件,仅支持实体基础成员(不包含导航对象) + /// + /// lambda表达式条件 + /// + IDelete Where(Expression> exp); + /// + /// 原生sql语法条件,Where("id = ?id", new { id = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + IDelete Where(string sql, object parms = null); + /// + /// 传入实体,将主键作为条件 + /// + /// 实体 + /// + IDelete Where(T1 item); + /// + /// 传入实体集合,将主键作为条件 + /// + /// 实体集合 + /// + IDelete Where(IEnumerable items); + /// + /// 子查询是否存在 + /// + /// + /// 子查询 + /// 不存在 + /// + IDelete WhereExists(ISelect select, bool notExists = false) where TEntity2 : class; + + /// + /// 返回即将执行的SQL语句 + /// + /// + string ToSql(); + /// + /// 执行SQL语句,返回影响的行数 + /// + /// + long ExecuteAffrows(); + /// + /// 执行SQL语句,返回被删除的记录 + /// + /// + List ExecuteDeleted(); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/IInsert.cs b/FreeSql/Interface/Curd/IInsert.cs new file mode 100644 index 00000000..113df414 --- /dev/null +++ b/FreeSql/Interface/Curd/IInsert.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface IInsert where T1 : class { + + /// + /// 追加准备插入的实体 + /// + /// 实体 + /// + IInsert AppendData(T1 source); + /// + /// 追加准备插入的实体集合 + /// + /// 实体集合 + /// + IInsert AppendData(IEnumerable source); + + /// + /// 只插入的列,InsertColumns(a => a.Name) | InsertColumns(a => new{a.Name,a.Time}) | InsertColumns(a => new[]{"name","time"}) + /// + /// lambda选择列 + /// + IInsert InsertColumns(Expression> columns); + /// + /// 忽略的列,IgnoreColumns(a => a.Name) | IgnoreColumns(a => new{a.Name,a.Time}) | IgnoreColumns(a => new[]{"name","time"}) + /// + /// lambda选择列 + /// + IInsert IgnoreColumns(Expression> columns); + + /// + /// 返回即将执行的SQL语句 + /// + /// + string ToSql(); + /// + /// 执行SQL语句,返回影响的行数 + /// + /// + long ExecuteAffrows(); + /// + /// 执行SQL语句,返回自增值 + /// + /// + long ExecuteIdentity(); + /// + /// 执行SQL语句,返回插入后的记录 + /// + /// + List ExecuteInserted(); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect0.cs b/FreeSql/Interface/Curd/ISelect/ISelect0.cs new file mode 100644 index 00000000..195bdc50 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect0.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql { + public interface ISelect0 { + + /// + /// 执行SQL查询,返回 T1 实体所有字段的记录,记录不存在时返回 Count 为 0 的列表 + /// + /// + List ToList(); + /// + /// 执行SQL查询,返回 field 指定字段的记录,并以元组或基础类型(int,string,long)接收,记录不存在时返回 Count 为 0 的列表 + /// + /// + /// + /// + List ToList(string field); + /// + /// 执行SQL查询,返回 T1 实体所有字段的第一条记录,记录不存在时返回 null + /// + /// + T1 ToOne(); + + /// + /// 返回即将执行的SQL语句 + /// + /// 指定字段 + /// + string ToSql(string field = null); + /// + /// 执行SQL查询,是否有记录 + /// + /// + bool Any(); + + /// + /// 查询的记录数量 + /// + /// + long Count(); + /// + /// 查询的记录数量,以参数out形式返回 + /// + /// 返回的变量 + /// + TSelect Count(out long count); + + /// + /// 指定从主库查询(默认查询从库) + /// + /// + TSelect Master(); + /// + /// 缓存查询结果 + /// + /// 缓存秒数 + /// 缓存key + /// + TSelect Caching(int seconds, string key = null); + + /// + /// 左联查询,使用导航属性自动生成SQL + /// + /// 表达式 + /// + TSelect LeftJoin(Expression> exp); + /// + /// 联接查询,使用导航属性自动生成SQL + /// + /// 表达式 + /// + TSelect InnerJoin(Expression> exp); + /// + /// 右联查询,使用导航属性自动生成SQL + /// + /// 表达式 + /// + TSelect RightJoin(Expression> exp); + /// + /// 左联查询,指定关联的实体类型 + /// + /// 关联的实体类型 + /// 表达式 + /// + TSelect LeftJoin(Expression> exp); + /// + /// 联接查询,指定关联的实体类型 + /// + /// 关联的实体类型 + /// 表达式 + /// + TSelect InnerJoin(Expression> exp); + /// + /// 右联查询,指定关联的实体类型 + /// + /// 关联的实体类型 + /// 表达式 + /// + TSelect RightJoin(Expression> exp); + + /// + /// 左联查询,使用原生sql语法,LeftJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + TSelect LeftJoin(string sql, object parms = null); + /// + /// 联接查询,使用原生sql语法,InnerJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + TSelect InnerJoin(string sql, object parms = null); + /// + /// 右联查询,使用原生sql语法,RightJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + TSelect RightJoin(string sql, object parms = null); + + /// + /// 原生sql语法条件,Where("id = ?id", new { id = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + TSelect Where(string sql, object parms = null); + /// + /// 原生sql语法条件,WhereIf(true, "id = ?id", new { id = 1 }) + /// + /// true 时生效 + /// sql语法条件 + /// 参数 + /// + TSelect WhereIf(bool condition, string sql, object parms = null); + + /// + /// 按原生sql语法分组,GroupBy("concat(name, ?cc)", new { cc = 1 }) + /// + /// sql语法 + /// 参数 + /// + TSelect GroupBy(string sql, object parms = null); + /// + /// 按原生sql语法聚合条件过滤,Having("count(name) = ?cc", new { cc = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + TSelect Having(string sql, object parms = null); + + /// + /// 按原生sql语法排序,OrderBy("count(name) + ?cc", new { cc = 1 }) + /// + /// sql语法 + /// 参数 + /// + TSelect OrderBy(string sql, object parms = null); + + /// + /// 查询向后偏移行数 + /// + /// + /// + TSelect Skip(int offset); + /// + /// 查询向后偏移行数 + /// + /// 行数 + /// + TSelect Offset(int offset); + /// + /// 查询多少条数据 + /// + /// + /// + TSelect Limit(int limit); + /// + /// 查询多少条数据 + /// + /// + /// + TSelect Take(int limit); + + /// + /// 分页 + /// + /// 第几页 + /// 每页多少 + /// + TSelect Page(int pageIndex, int pageSize); + } +} diff --git a/FreeSql/Interface/Curd/ISelect/ISelect1.cs b/FreeSql/Interface/Curd/ISelect/ISelect1.cs new file mode 100644 index 00000000..892df25e --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect1.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class { + + /// + /// 执行SQL查询,返回指定字段的记录,记录不存在时返回 Count 为 0 的列表 + /// + /// 返回类型 + /// 选择列 + /// + List ToList(Expression> select); + + /// + /// 求和 + /// + /// 返回类型 + /// 列 + /// + TMember Sum(Expression> column); + /// + /// 最小值 + /// + /// 返回类型 + /// 列 + /// + TMember Min(Expression> column); + /// + /// 最大值 + /// + /// 返回类型 + /// 列 + /// + TMember Max(Expression> column); + /// + /// 平均值 + /// + /// 返回类型 + /// 列 + /// + TMember Avg(Expression> column); + + /// + /// 指定别名 + /// + /// 别名 + /// + ISelect As(string alias = "a"); + + /// + /// 多表查询 + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) where T2 : class where T3 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class; + /// + /// 多表查询 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class; + + /// + /// 查询条件,Where(a => a.Id > 10),支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com") + /// + /// lambda表达式 + /// + ISelect Where(Expression> exp); + /// + /// 查询条件,Where(true, a => a.Id > 10),支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com") + /// + /// true 时生效 + /// lambda表达式 + /// + ISelect WhereIf(bool condition, Expression> exp); + /// + /// 多表条件查询 + /// + /// + /// lambda表达式 + /// + ISelect Where(Expression> exp) where T2 : class; + /// + /// 多表条件查询 + /// + /// + /// + /// lambda表达式 + /// + ISelect Where(Expression> exp) where T2 : class where T3 : class; + /// + /// 多表条件查询 + /// + /// + /// + /// + /// lambda表达式 + /// + ISelect Where(Expression> exp) where T2 : class where T3 : class where T4 : class; + /// + /// 多表条件查询 + /// + /// + /// + /// + /// lambda表达式 + /// + ISelect Where(Expression> exp) where T2 : class where T3 : class where T4 : class where T5 : class; + + /// + /// 模糊查询,选择多个列 OR,WhereLike(a => new[] { a.Title, a.Content }, "%sql%") + /// + /// lambda选择列 + /// 查询内容 + /// not like + /// + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + /// + /// 模糊查询,WhereLike(a => a.Title, "%sql") + /// + /// lambda选择列 + /// 查询内容 + /// not like + /// + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + /// + /// 按选择的列分组,GroupBy(a => a.Name) | GroupBy(a => new{a.Name,a.Time}) | GroupBy(a => new[]{"name","time"}) + /// + /// + /// + ISelect GroupBy(Expression> columns); + + /// + /// 按列排序,OrderBy(a => a.Time) + /// + /// + /// + /// + ISelect OrderBy(Expression> column); + /// + /// 按列倒向排序,OrderByDescending(a => a.Time) + /// + /// 列 + /// + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect10.cs b/FreeSql/Interface/Curd/ISelect/ISelect10.cs new file mode 100644 index 00000000..59d6a122 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect10.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect2.cs b/FreeSql/Interface/Curd/ISelect/ISelect2.cs new file mode 100644 index 00000000..24ab6fa0 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect2.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect3.cs b/FreeSql/Interface/Curd/ISelect/ISelect3.cs new file mode 100644 index 00000000..206c5ee3 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect3.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect4.cs b/FreeSql/Interface/Curd/ISelect/ISelect4.cs new file mode 100644 index 00000000..7ad2e68a --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect4.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect5.cs b/FreeSql/Interface/Curd/ISelect/ISelect5.cs new file mode 100644 index 00000000..9786b49d --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect5.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect6.cs b/FreeSql/Interface/Curd/ISelect/ISelect6.cs new file mode 100644 index 00000000..bcfc760c --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect6.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect7.cs b/FreeSql/Interface/Curd/ISelect/ISelect7.cs new file mode 100644 index 00000000..f471bded --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect7.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect8.cs b/FreeSql/Interface/Curd/ISelect/ISelect8.cs new file mode 100644 index 00000000..95b75e85 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect8.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelect9.cs b/FreeSql/Interface/Curd/ISelect/ISelect9.cs new file mode 100644 index 00000000..5c1bd101 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelect9.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelect : ISelect0, T1> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class { + + List ToList(Expression> select); + + TMember Sum(Expression> column); + TMember Min(Expression> column); + TMember Max(Expression> column); + TMember Avg(Expression> column); + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + + ISelect WhereLike(Expression> columns, string pattern, bool notLike = false); + ISelect WhereLike(Expression> column, string pattern, bool notLike = false); + + ISelect GroupBy(Expression> columns); + + ISelect OrderBy(Expression> column); + ISelect OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/ISelect/ISelectFrom.cs b/FreeSql/Interface/Curd/ISelect/ISelectFrom.cs new file mode 100644 index 00000000..98d60493 --- /dev/null +++ b/FreeSql/Interface/Curd/ISelect/ISelectFrom.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface ISelectFromExpression where T1 : class { + + ISelectFromExpression LeftJoin(Expression> exp); + ISelectFromExpression InnerJoin(Expression> exp); + ISelectFromExpression RightJoin(Expression> exp); + + /// + /// 查询条件,Where(a => a.Id > 10),支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com") + /// + /// lambda表达式 + /// + ISelectFromExpression Where(Expression> exp); + /// + /// 查询条件,Where(true, a => a.Id > 10),支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com") + /// + /// true 时生效 + /// lambda表达式 + /// + ISelectFromExpression WhereIf(bool condition, Expression> exp); + + /// + /// 模糊查询,选择多个列 OR,WhereLike(a => new[] { a.Title, a.Content }, "%sql%") + /// + /// lambda选择列 + /// 查询内容 + /// not like + /// + ISelectFromExpression WhereLike(Expression> columns, string pattern, bool notLike = false); + /// + /// 模糊查询,WhereLike(a => a.Title, "%sql") + /// + /// lambda选择列 + /// 查询内容 + /// not like + /// + ISelectFromExpression WhereLike(Expression> column, string pattern, bool notLike = false); + + /// + /// 按选择的列分组,GroupBy(a => a.Name) | GroupBy(a => new{a.Name,a.Time}) | GroupBy(a => new[]{"name","time"}) + /// + /// + /// + ISelectFromExpression GroupBy(Expression> columns); + + /// + /// 按列排序,OrderBy(a => a.Time) + /// + /// + /// + /// + ISelectFromExpression OrderBy(Expression> column); + /// + /// 按列倒向排序,OrderByDescending(a => a.Time) + /// + /// 列 + /// + ISelectFromExpression OrderByDescending(Expression> column); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/Curd/IUpdate.cs b/FreeSql/Interface/Curd/IUpdate.cs new file mode 100644 index 00000000..70df3a7e --- /dev/null +++ b/FreeSql/Interface/Curd/IUpdate.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql { + public interface IUpdate where T1 : class { + + /// + /// 更新数据,设置更新的实体 + /// + /// 实体 + /// + IUpdate SetSource(T1 source); + /// + /// 更新数据,设置更新的实体集合 + /// + /// 实体集合 + /// + IUpdate SetSource(IEnumerable source); + /// + /// 忽略的列,IgnoreColumns(a => a.Name) | IgnoreColumns(a => new{a.Name,a.Time}) | IgnoreColumns(a => new[]{"name","time"}) + /// + /// lambda选择列 + /// + IUpdate IgnoreColumns(Expression> columns); + + /// + /// 设置列的新值,Set(a => a.Name, "newvalue") + /// + /// + /// lambda选择列 + /// 新值 + /// + IUpdate Set(Expression> column, TMember value); + /// + /// 设置列的的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 + /// + /// + /// + /// + IUpdate Set(Expression> binaryExpression); + /// + /// 设置值,自定义SQL语法,SetRaw("title = ?title", new { title = "newtitle" }) + /// + /// sql语法 + /// 参数 + /// + IUpdate SetRaw(string sql, object parms = null); + + /// + /// lambda表达式条件,仅支持实体基础成员(不包含导航对象) + /// + /// lambda表达式条件 + /// + IUpdate Where(Expression> exp); + /// + /// 原生sql语法条件,Where("id = ?id", new { id = 1 }) + /// + /// sql语法条件 + /// 参数 + /// + IUpdate Where(string sql, object parms = null); + /// + /// 传入实体,将主键作为条件 + /// + /// 实体 + /// + IUpdate Where(T1 item); + /// + /// 传入实体集合,将主键作为条件 + /// + /// 实体集合 + /// + IUpdate Where(IEnumerable items); + /// + /// 子查询是否存在 + /// + /// + /// 子查询 + /// 不存在 + /// + IUpdate WhereExists(ISelect select, bool notExists = false) where TEntity2 : class; + + /// + /// 返回即将执行的SQL语句 + /// + /// + string ToSql(); + /// + /// 执行SQL语句,返回影响的行数 + /// + /// + long ExecuteAffrows(); + /// + /// 执行SQL语句,返回更新后的记录 + /// + /// + List ExecuteUpdated(); + } +} \ No newline at end of file diff --git a/FreeSql/Interface/IAdo.cs b/FreeSql/Interface/IAdo.cs new file mode 100644 index 00000000..facc2e02 --- /dev/null +++ b/FreeSql/Interface/IAdo.cs @@ -0,0 +1,138 @@ +using FreeSql.DatabaseModel; +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql { + public partial interface IAdo { + /// + /// 主库连接池 + /// + ObjectPool MasterPool { get; } + /// + /// 从库连接池 + /// + List> SlavePools { get; } + /// + /// 是否跟踪记录SQL执行性能日志 + /// + bool IsTracePerformance { get; set; } + + #region 事务 + /// + /// 开启事务(不支持异步),60秒未执行完将自动提交 + /// + /// 事务体 () => {} + void Transaction(Action handler); + /// + /// 开启事务(不支持异步) + /// + /// 事务体 () => {} + /// 超时,未执行完将自动提交 + void Transaction(Action handler, TimeSpan timeout); + /// + /// 当前线程的事务 + /// + DbTransaction TransactionCurrentThread { get; } + /// + /// 事务完成前预删除缓存 + /// + /// + void TransactionPreRemoveCache(params string[] keys); + #endregion + + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + /// + /// + void ExecuteReader(Action readerHander, CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + object[][] ExecuteArray(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + DataTable ExecuteDataTable(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 在【主库】执行 + /// + /// + /// + /// + int ExecuteNonQuery(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 在【主库】执行 + /// + /// + /// + /// + object ExecuteScalar(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + + /// + /// 执行SQL返回对象集合,Query<User>("select * from user where age > @age", new { age = 25 }) + /// + /// + /// + /// + /// + List Query(string cmdText, object parms = null); + + #region async + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + /// + /// + Task ExecuteReaderAsync(Func readerHander, CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + Task ExecuteArrayAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + /// + /// + /// + Task ExecuteDataTableAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 在【主库】执行 + /// + /// + /// + /// + Task ExecuteNonQueryAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + /// + /// 在【主库】执行 + /// + /// + /// + /// + Task ExecuteScalarAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms); + + /// + /// 执行SQL返回对象集合,Query<User>("select * from user where age > @age", new { age = 25 }) + /// + /// + /// + /// + /// + Task> QueryAsync(string cmdText, object parms = null); + #endregion + } +} diff --git a/FreeSql/Interface/ICache.cs b/FreeSql/Interface/ICache.cs new file mode 100644 index 00000000..5ef5a4bf --- /dev/null +++ b/FreeSql/Interface/ICache.cs @@ -0,0 +1,118 @@ +using System; +using System.Threading.Tasks; + +namespace FreeSql { + public interface ICache { + + /// + /// 缓存数据时序列化方法,若无设置则默认使用 Json.net + /// + Func Serialize { get; set; } + /// + /// 获取缓存数据时反序列化方法,若无设置则默认使用 Json.net + /// + Func Deserialize { get; set; } + + /// + /// 缓存可序列化数据 + /// + /// + /// 缓存键 + /// 可序列化数据 + /// 缓存秒数,<=0时永久缓存 + void Set(string key, T data, int timeoutSeconds = 0); + /// + /// 循环或批量获取缓存数据 + /// + /// + /// + /// + T Get(string key); + /// + /// 循环或批量获取缓存数据 + /// + /// + /// + /// + string Get(string key); + /// + /// 循环或批量删除缓存键 + /// + /// 缓存键[数组] + void Remove(params string[] keys); + /// + /// 缓存壳 + /// + /// 缓存类型 + /// 缓存键 + /// 缓存秒数 + /// 获取源数据的函数 + /// 序列化函数 + /// 反序列化函数 + /// + T Shell(string key, int timeoutSeconds, Func getData); + /// + /// 缓存壳(哈希表) + /// + /// 缓存类型 + /// 缓存键 + /// 字段 + /// 缓存秒数 + /// 获取源数据的函数 + /// 序列化函数 + /// 反序列化函数 + /// + T Shell(string key, string field, int timeoutSeconds, Func getData); + + /// + /// 缓存可序列化数据 + /// + /// + /// 缓存键 + /// 可序列化数据 + /// 缓存秒数,<=0时永久缓存 + Task SetAsync(string key, T data, int timeoutSeconds = 0); + /// + /// 循环或批量获取缓存数据 + /// + /// + /// + /// + Task GetAsync(string key); + /// + /// 循环或批量获取缓存数据 + /// + /// + /// + /// + Task GetAsync(string key); + /// + /// 循环或批量删除缓存键 + /// + /// 缓存键[数组] + Task RemoveAsync(params string[] keys); + /// + /// 缓存壳 + /// + /// 缓存类型 + /// 缓存键 + /// 缓存秒数 + /// 获取源数据的函数 + /// 序列化函数 + /// 反序列化函数 + /// + Task ShellAsync(string key, int timeoutSeconds, Func> getDataAsync); + /// + /// 缓存壳(哈希表) + /// + /// 缓存类型 + /// 缓存键 + /// 字段 + /// 缓存秒数 + /// 获取源数据的函数 + /// 序列化函数 + /// 反序列化函数 + /// + Task ShellAsync(string key, string field, int timeoutSeconds, Func> getDataAsync); + } +} diff --git a/FreeSql/Interface/ICodeFirst.cs b/FreeSql/Interface/ICodeFirst.cs new file mode 100644 index 00000000..a70e1944 --- /dev/null +++ b/FreeSql/Interface/ICodeFirst.cs @@ -0,0 +1,43 @@ +using System; + +namespace FreeSql { + public interface ICodeFirst { + + /// + /// 【开发环境必备】自动同步实体结构到数据库,程序运行中检查实体表是否存在,然后创建或修改 + /// + bool IsAutoSyncStructure { get; set; } + + /// + /// 将实体类型与数据库对比,返回DDL语句 + /// + /// + /// + string GetComparisonDDLStatements(); + /// + /// 将实体类型集合与数据库对比,返回DDL语句 + /// + /// + /// + string GetComparisonDDLStatements(params Type[] entityTypes); + /// + /// 同步实体类型到数据库 + /// + /// + /// + bool SyncStructure(); + /// + /// 同步实体类型集合到数据库 + /// + /// + /// + bool SyncStructure(params Type[] entityTypes); + + /// + /// 根据 System.Type 获取数据库信息 + /// + /// + /// + (int type, string dbtype, string dbtypeFull, bool? isnullable)? GetDbInfo(Type type); + } +} diff --git a/FreeSql/Interface/IDasql.cs b/FreeSql/Interface/IDasql.cs new file mode 100644 index 00000000..c56f9421 --- /dev/null +++ b/FreeSql/Interface/IDasql.cs @@ -0,0 +1,100 @@ +using FreeSql; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +public interface IFreeSql { + /// + /// 插入数据 + /// + /// + /// + IInsert Insert() where T1 : class; + /// + /// 插入数据,传入实体 + /// + /// + /// + /// + IInsert Insert(T1 source) where T1 : class; + /// + /// 插入数据,传入实体集合 + /// + /// + /// + /// + IInsert Insert(IEnumerable source) where T1 : class; + + /// + /// 修改数据 + /// + /// + /// + IUpdate Update() where T1 : class; + /// + /// 修改数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + /// + /// + /// 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + /// + IUpdate Update(object dywhere) where T1 : class; + + /// + /// 查询数据 + /// + /// + /// + ISelect Select() where T1 : class; + /// + /// 查询数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + /// + /// + /// 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + /// + ISelect Select(object dywhere) where T1 : class; + + /// + /// 删除数据 + /// + /// + /// + IDelete Delete() where T1 : class; + /// + /// 删除数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + /// + /// + /// 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + /// + IDelete Delete(object dywhere) where T1 : class; + + /// + /// 开启事务(不支持异步),60秒未执行完将自动提交 + /// + /// 事务体 () => {} + void Transaction(Action handler); + /// + /// 开启事务(不支持异步) + /// + /// 事务体 () => {} + /// 超时,未执行完将自动提交 + void Transaction(Action handler, TimeSpan timeout); + + /// + /// 缓存 + /// + ICache Cache { get; } + /// + /// 数据库访问对象 + /// + IAdo Ado { get; } + + /// + /// CodeFirst 模式开发相关方法 + /// + ICodeFirst CodeFirst { get; } + /// + /// DbFirst 模式开发相关方法 + /// + IDbFirst DbFirst { get; } +} \ No newline at end of file diff --git a/FreeSql/Interface/iDbFirst.cs b/FreeSql/Interface/iDbFirst.cs new file mode 100644 index 00000000..96604795 --- /dev/null +++ b/FreeSql/Interface/iDbFirst.cs @@ -0,0 +1,70 @@ +using FreeSql.DatabaseModel; +using System; +using System.Collections.Generic; + +namespace FreeSql { + public interface IDbFirst { + + /// + /// 获取所有数据库 + /// + /// + List GetDatabases(); + /// + /// 获取指定数据库的表信息,包括表、列详情、主键、唯一键、索引、外键 + /// + /// + /// + List GetTablesByDatabase(params string[] database); + + /// + /// 获取数据库枚举类型int值 + /// + /// + /// + int GetDbType(DbColumnInfo column); + + /// + /// 获取c#转换,(int)、(long) + /// + /// + /// + string GetCsConvert(DbColumnInfo column); + /// + /// 获取c#值 + /// + /// + /// + string GetCsTypeValue(DbColumnInfo column); + /// + /// 获取c#类型,int、long + /// + /// + /// + string GetCsType(DbColumnInfo column); + /// + /// 获取c#类型对象 + /// + /// + /// + Type GetCsTypeInfo(DbColumnInfo column); + /// + /// 获取ado.net读取方法, GetBoolean、GetInt64 + /// + /// + /// + string GetDataReaderMethod(DbColumnInfo column); + /// + /// 序列化 + /// + /// + /// + string GetCsStringify(DbColumnInfo column); + /// + /// 反序列化 + /// + /// + /// + string GetCsParse(DbColumnInfo column); + } +} diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs new file mode 100644 index 00000000..8c149cf3 --- /dev/null +++ b/FreeSql/Internal/CommonExpression.cs @@ -0,0 +1,256 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Internal { + internal abstract class CommonExpression { + + internal CommonUtils _common; + internal CommonExpression(CommonUtils common) { + _common = common; + } + + internal bool ReadAnonymousField(List _tables, StringBuilder field, ReadAnonymousTypeInfo parent, ref int index, Expression exp) { + switch (exp.NodeType) { + case ExpressionType.Quote: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand); + case ExpressionType.Lambda: return ReadAnonymousField(_tables, field, parent, ref index, (exp as LambdaExpression)?.Body); + case ExpressionType.Convert: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand); + case ExpressionType.Constant: + var constExp = exp as ConstantExpression; + field.Append(", ").Append(constExp?.Value).Append(" as").Append(++index); + return false; + case ExpressionType.MemberAccess: + var map = new List(); + ExpressionSelectColumn_MemberAccess(_tables, map, SelectTableInfoType.From, exp, true); + if (map.Count > 1) { + parent.Consturctor = map.First().Table.Table.Type.GetConstructor(new Type[0]); + parent.ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Properties; + } + for (var idx = 0; idx < map.Count; idx++) { + field.Append(", ").Append(map[idx].Table.Alias).Append(".").Append(_common.QuoteSqlName(map[idx].Column.Attribute.Name)).Append(" as").Append(++index); + if (map.Count > 1) parent.Childs.Add(new ReadAnonymousTypeInfo { CsName = map[idx].Column.CsName }); + } + return false; + case ExpressionType.New: + var newExp = exp as NewExpression; + parent.Consturctor = newExp.Type.GetConstructors()[0]; + parent.ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Arguments; + for (var a = 0; a < newExp.Members.Count; a++) { + var child = new ReadAnonymousTypeInfo { CsName = newExp.Members[a].Name }; + parent.Childs.Add(child); + ReadAnonymousField(_tables, field, child, ref index, newExp.Arguments[a]); + } + return true; + } + return false; + } + internal object ReadAnonymous(ReadAnonymousTypeInfo parent, object[] dr, ref int index) { + if (parent.Childs.Any() == false) return dr[++index]; + switch (parent.ConsturctorType) { + case ReadAnonymousTypeInfoConsturctorType.Arguments: + var args = new object[parent.Childs.Count]; + for (var a = 0; a < parent.Childs.Count; a++) { + args[a] = ReadAnonymous(parent.Childs[a], dr, ref index); + } + return parent.Consturctor.Invoke(args); + case ReadAnonymousTypeInfoConsturctorType.Properties: + var ret = parent.Consturctor.Invoke(null); + for (var b = 0; b < parent.Childs.Count; b++) { + Utils.FillPropertyValue(ret, parent.Childs[b].CsName, ReadAnonymous(parent.Childs[b], dr, ref index)); + } + return ret; + } + return null; + } + + internal string ExpressionConstant(ConstantExpression exp) => _common.FormatSql("{0}", exp?.Value); + + internal string ExpressionSelectColumn_MemberAccess(List _tables, List _selectColumnMap, SelectTableInfoType tbtype, Expression exp, bool isQuoteName) { + return ExpressionLambdaToSql(exp, _tables, _selectColumnMap, tbtype, isQuoteName); + } + + internal string[] ExpressionSelectColumns_MemberAccess_New_NewArrayInit(List _tables, Expression exp, bool isQuoteName) { + switch (exp?.NodeType) { + case ExpressionType.Quote: return ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, (exp as UnaryExpression)?.Operand, isQuoteName); + case ExpressionType.Lambda: return ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, (exp as LambdaExpression)?.Body, isQuoteName); + case ExpressionType.Convert: return ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, (exp as UnaryExpression)?.Operand, isQuoteName); + case ExpressionType.Constant: return new[] { ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, isQuoteName) }; + case ExpressionType.MemberAccess: return new[] { ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, isQuoteName) }; + case ExpressionType.New: + var newExp = exp as NewExpression; + if (newExp == null) break; + var newExpMembers = new string[newExp.Members.Count]; + for (var a = 0; a < newExpMembers.Length; a++) newExpMembers[a] = ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, newExp.Arguments[a], isQuoteName); + return newExpMembers; + case ExpressionType.NewArrayInit: + var newArr = exp as NewArrayExpression; + if (newArr == null) break; + var newArrMembers = new List(); + foreach (var newArrExp in newArr.Expressions) newArrMembers.AddRange(ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, newArrExp, isQuoteName)); + return newArrMembers.ToArray(); + } + return new string[0]; + } + + static readonly Dictionary dicExpressionOperator = new Dictionary() { + { ExpressionType.OrElse, "OR" }, + { ExpressionType.Or, "|" }, + { ExpressionType.AndAlso, "AND" }, + { ExpressionType.And, "&" }, + { ExpressionType.GreaterThan, ">" }, + { ExpressionType.GreaterThanOrEqual, ">=" }, + { ExpressionType.LessThan, "<" }, + { ExpressionType.LessThanOrEqual, "<=" }, + { ExpressionType.NotEqual, "<>" }, + { ExpressionType.Add, "+" }, + { ExpressionType.Subtract, "-" }, + { ExpressionType.Multiply, "*" }, + { ExpressionType.Divide, "/" }, + { ExpressionType.Modulo, "%" }, + { ExpressionType.Equal, "=" }, + }; + internal string ExpressionWhereLambdaNoneForeignObject(List _tables, List _selectColumnMap, Expression exp) { + return ExpressionLambdaToSql(exp, _tables, _selectColumnMap, SelectTableInfoType.From, true); + } + + internal string ExpressionWhereLambda(List _tables, Expression exp) { + return ExpressionLambdaToSql(exp, _tables, null, SelectTableInfoType.From, true); + } + internal void ExpressionJoinLambda(List _tables, SelectTableInfoType tbtype, Expression exp) { + var tbidx = _tables.Count; + var filter = ExpressionLambdaToSql(exp, _tables, null, tbtype, true); + if (_tables.Count > tbidx) { + _tables[tbidx].Type = tbtype; + _tables[tbidx].On = filter; + for (var a = tbidx + 1; a < _tables.Count; a++) + _tables[a].Type = SelectTableInfoType.From; + } else { + var find = _tables.Where((a, c) => c > 0 && a.Type == tbtype && string.IsNullOrEmpty(a.On)).LastOrDefault(); + if (find != null) find.On = filter; + } + } + + internal string ExpressionLambdaToSql(Expression exp, List _tables, List _selectColumnMap, SelectTableInfoType tbtype, bool isQuoteName) { + switch( exp.NodeType) { + case ExpressionType.Quote: return ExpressionLambdaToSql((exp as UnaryExpression)?.Operand, _tables, _selectColumnMap, tbtype, isQuoteName); + case ExpressionType.Lambda: return ExpressionLambdaToSql((exp as LambdaExpression)?.Body, _tables, _selectColumnMap, tbtype, isQuoteName); + case ExpressionType.Convert: return ExpressionLambdaToSql((exp as UnaryExpression)?.Operand, _tables, _selectColumnMap, tbtype, isQuoteName); + case ExpressionType.Constant: return _common.FormatSql("{0}", (exp as ConstantExpression)?.Value); + case ExpressionType.MemberAccess: + var expStack = new Stack(); + expStack.Push(exp); + MethodCallExpression callExp = null; + var exp2 = (exp as MemberExpression).Expression; + while (true) { + switch(exp2.NodeType) { + case ExpressionType.Constant: + expStack.Push(exp2); + break; + case ExpressionType.Parameter: + expStack.Push(exp2); + break; + case ExpressionType.MemberAccess: + expStack.Push(exp2); + exp2 = (exp2 as MemberExpression).Expression; + if (exp2 == null) break; + continue; + case ExpressionType.Call: + callExp = exp2 as MethodCallExpression; + expStack.Push(exp2); + exp2 = callExp.Object; + if (exp2 == null) break; + continue; + } + break; + } + if (expStack.First().NodeType != ExpressionType.Parameter) return _common.FormatSql("{0}", Expression.Lambda(exp).Compile().DynamicInvoke()); + if (callExp != null) return ExpressionLambdaToSqlCall(callExp, _tables, _selectColumnMap, tbtype, isQuoteName); + if (_tables == null) { + var pp = expStack.Pop() as ParameterExpression; + var memberExp = expStack.Pop() as MemberExpression; + var tb = _common.GetTableByEntity(pp.Type); + if (tb.ColumnsByCs.ContainsKey(memberExp.Member.Name) == false) throw new ArgumentException($"{tb.DbName} 找不到列 {memberExp.Member.Name}"); + if (_selectColumnMap != null) { + _selectColumnMap.Add(new SelectColumnInfo { Table = null, Column = tb.ColumnsByCs[memberExp.Member.Name] }); + } + var name = tb.ColumnsByCs[memberExp.Member.Name].Attribute.Name; + if (isQuoteName) name = _common.QuoteSqlName(name); + return name; + } + + TableInfo tb2 = null; + string alias2 = "", name2 = ""; + SelectTableInfo find2 = null; + while (expStack.Count > 0) { + exp2 = expStack.Pop(); + switch (exp2.NodeType) { + case ExpressionType.Constant: + throw new NotImplementedException("未现实 MemberAccess 下的 Constant"); + case ExpressionType.Parameter: + case ExpressionType.MemberAccess: + var tb2tmp = _common.GetTableByEntity(exp2.Type); + var mp2 = exp2 as MemberExpression; + if (tb2tmp != null) { + if (exp2.NodeType == ExpressionType.Parameter) alias2 = (exp2 as ParameterExpression).Name; + else alias2 = $"{alias2}__{mp2.Member.Name}"; + var find2s = _tables.Where((a2, c2) => a2.Table.CsName == tb2tmp.CsName).ToArray(); + if (find2s.Length > 1) find2s = _tables.Where((a2, c2) => a2.Table.CsName == tb2tmp.CsName && a2.Alias == alias2).ToArray(); + find2 = find2s.FirstOrDefault(); + if (find2 == null) _tables.Add(find2 = new SelectTableInfo { Table = tb2tmp, Alias = alias2, On = null, Type = tbtype }); + alias2 = find2.Alias; + tb2 = tb2tmp; + } + if (mp2 == null || expStack.Any()) continue; + if (tb2.ColumnsByCs.ContainsKey(mp2.Member.Name) == false) { //如果选的是对象,附加所有列 + if (_selectColumnMap != null) { + var tb3 = _common.GetTableByEntity(mp2.Type); + if (tb3 != null) { + var alias3 = $"{alias2}__{mp2.Member.Name}"; + var find3s = _tables.Where((a3, c3) => a3.Table.CsName == tb3.CsName).ToArray(); + if (find3s.Length > 1) find3s = _tables.Where((a3, c3) => a3.Table.CsName == tb3.CsName && a3.Alias == alias3).ToArray(); + var find3 = find3s.FirstOrDefault(); + if (find3 == null) _tables.Add(find3 = new SelectTableInfo { Table = tb3, Alias = alias3, On = null, Type = tbtype }); + alias3 = find3.Alias; + + foreach (var tb3c in tb3.Columns.Values) + _selectColumnMap.Add(new SelectColumnInfo { Table = find3, Column = tb3c }); + if (tb3.Columns.Any()) return ""; + } + } + throw new ArgumentException($"{tb2.DbName} 找不到列 {mp2.Member.Name}"); + } + var col2 = tb2.ColumnsByCs[mp2.Member.Name]; + if (_selectColumnMap != null && find2 != null) { + _selectColumnMap.Add(new SelectColumnInfo { Table = find2, Column = col2 }); + return ""; + } + name2 = tb2.ColumnsByCs[mp2.Member.Name].Attribute.Name; + break; + case ExpressionType.Call:break; + } + } + if (isQuoteName) name2 = _common.QuoteSqlName(name2); + return $"{alias2}.{name2}"; + case ExpressionType.Call: return ExpressionLambdaToSqlCall(exp as MethodCallExpression, _tables, _selectColumnMap, tbtype, isQuoteName); + } + if (dicExpressionOperator.TryGetValue(exp.NodeType, out var tryoper) == false) return ""; + var expBinary = exp as BinaryExpression; + if (expBinary == null) return ""; + var left = ExpressionLambdaToSql(expBinary.Left, _tables, _selectColumnMap, tbtype, isQuoteName); + var right = ExpressionLambdaToSql(expBinary.Right, _tables, _selectColumnMap, tbtype, isQuoteName); + if (left == "NULL") { + var tmp = right; + right = left; + left = tmp; + } + if (right == "NULL") tryoper = tryoper == "=" ? " IS " : " IS NOT "; + return $"{left} {tryoper} {right}"; + } + + internal abstract string ExpressionLambdaToSqlCall(MethodCallExpression exp, List _tables, List _selectColumnMap, SelectTableInfoType tbtype, bool isQuoteName); + + } +} diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs new file mode 100644 index 00000000..4145badf --- /dev/null +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs @@ -0,0 +1,267 @@ +using Microsoft.Extensions.Logging; +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; + +namespace FreeSql.Internal.CommonProvider { + abstract partial class AdoProvider : IAdo { + + protected abstract void ReturnConnection(ObjectPool pool, Object conn, Exception ex); + protected abstract DbCommand CreateCommand(); + protected abstract DbParameter[] GetDbParamtersByObject(string sql, object obj); + + public bool IsTracePerformance { get; set; } = string.Compare(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), "Development", true) == 0; + + public ObjectPool MasterPool { get; protected set; } + public List> SlavePools { get; } = new List>(); + protected ICache _cache { get; set; } + protected ILogger _log { get; set; } + protected int slaveUnavailables = 0; + private object slaveLock = new object(); + private Random slaveRandom = new Random(); + + public AdoProvider(ICache cache, ILogger log) { + this._cache = cache; + this._log = log; + } + + void LoggerException(ObjectPool pool, DbCommand cmd, Exception e, DateTime dt, string logtxt, bool isThrowException = true) { + if (IsTracePerformance) { + TimeSpan ts = DateTime.Now.Subtract(dt); + if (e == null && ts.TotalMilliseconds > 100) + _log.LogWarning($"{pool.Policy.Name}执行SQL语句耗时过长{ts.TotalMilliseconds}ms\r\n{cmd.CommandText}\r\n{logtxt}"); + } + + if (e == null) return; + string log = $"{pool.Policy.Name}数据库出错(执行SQL)〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\r\n{cmd.CommandText}\r\n"; + foreach (DbParameter parm in cmd.Parameters) + log += parm.ParameterName.PadRight(20, ' ') + " = " + (parm.Value ?? "NULL") + "\r\n"; + + log += e.Message; + _log.LogError(log); + + RollbackTransaction(); + cmd.Parameters.Clear(); + if (isThrowException) throw e; + } + + public List Query(string sql, object parms = null) => Query(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public List Query(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + var names = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var ds = new List(); + ExecuteReader(dr => { + if (names.Any() == false) + for (var a = 0; a < dr.FieldCount; a++) names.Add(dr.GetName(a), a); + object[] values = new object[dr.FieldCount]; + dr.GetValues(values); + ds.Add(values); + }, cmdType, cmdText, cmdParms); + var ret = new List(); + foreach (var row in ds) { + var read = Utils.ExecuteArrayRowReadClassOrTuple(typeof(T), names, row); + ret.Add(read.value == null ? default(T) : (T) read.value); + } + return ret; + } + public void ExecuteReader(Action readerHander, string sql, object parms = null) => ExecuteReader(readerHander, CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public void ExecuteReader(Action readerHander, CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + DateTime dt = DateTime.Now; + string logtxt = ""; + DateTime logtxt_dt = DateTime.Now; + var pool = this.MasterPool; + bool isSlave = false; + + //读写分离规则 + if (this.SlavePools.Any() && cmdText.StartsWith("SELECT ", StringComparison.CurrentCultureIgnoreCase)) { + var availables = slaveUnavailables == 0 ? + //查从库 + this.SlavePools : ( + //查主库 + slaveUnavailables == this.SlavePools.Count ? new List>() : + //查从库可用 + this.SlavePools.Where(sp => sp.IsAvailable).ToList()); + if (availables.Any()) { + isSlave = true; + pool = availables.Count == 1 ? availables[0] : availables[slaveRandom.Next(availables.Count)]; + } + } + + Object conn = null; + var pc = PrepareCommand(cmdType, cmdText, cmdParms, ref logtxt); + if (IsTracePerformance) logtxt += $"PrepareCommand: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + Exception ex = null; + try { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + if (isSlave) { + //从库查询切换,恢复 + bool isSlaveFail = false; + try { + if (pc.cmd.Connection == null) pc.cmd.Connection = (conn = pool.Get()).Value; + //if (slaveRandom.Next(100) % 2 == 0) throw new Exception("测试从库抛出异常"); + } catch { + isSlaveFail = true; + } + if (isSlaveFail) { + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(pool, conn, ex); //pool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(pool, pc.cmd, new Exception($"连接失败,准备切换其他可用服务器"), dt, logtxt, false); + pc.cmd.Parameters.Clear(); + ExecuteReader(readerHander, cmdType, cmdText, cmdParms); + return; + } + } else { + //主库查询 + if (pc.cmd.Connection == null) pc.cmd.Connection = (conn = pool.Get()).Value; + } + if (IsTracePerformance) { + logtxt += $"Open: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + logtxt_dt = DateTime.Now; + } + using (var dr = pc.cmd.ExecuteReader()) { + if (IsTracePerformance) logtxt += $"ExecuteReader: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + while (true) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + bool isread = dr.Read(); + if (IsTracePerformance) logtxt += $" dr.Read: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + if (isread == false) break; + + if (readerHander != null) { + object[] values = null; + if (IsTracePerformance) { + logtxt_dt = DateTime.Now; + values = new object[dr.FieldCount]; + dr.GetValues(values); + logtxt += $" dr.GetValues: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + logtxt_dt = DateTime.Now; + } + readerHander(dr); + if (IsTracePerformance) logtxt += $" readerHander: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms ({string.Join(",", values)})\r\n"; + } + } + if (IsTracePerformance) logtxt_dt = DateTime.Now; + dr.Close(); + } + if (IsTracePerformance) logtxt += $"ExecuteReader_dispose: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(pool, conn, ex); //pool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(pool, pc.cmd, ex, dt, logtxt); + pc.cmd.Parameters.Clear(); + } + public object[][] ExecuteArray(string sql, object parms = null) => ExecuteArray(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public object[][] ExecuteArray(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + List ret = new List(); + ExecuteReader(dr => { + object[] values = new object[dr.FieldCount]; + dr.GetValues(values); + ret.Add(values); + }, cmdType, cmdText, cmdParms); + return ret.ToArray(); + } + public DataTable ExecuteDataTable(string sql, object parms = null) => ExecuteDataTable(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public DataTable ExecuteDataTable(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + var ret = new DataTable(); + ExecuteReader(dr => { + if (ret.Columns.Count == 0) + for (var a = 0; a < dr.FieldCount; a++) ret.Columns.Add(dr.GetName(a)); + object[] values = new object[ret.Columns.Count]; + dr.GetValues(values); + ret.Rows.Add(values); + }, cmdType, cmdText, cmdParms); + return ret; + } + public int ExecuteNonQuery(string sql, object parms = null) => ExecuteNonQuery(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public int ExecuteNonQuery(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + DateTime dt = DateTime.Now; + string logtxt = ""; + DateTime logtxt_dt = DateTime.Now; + Object conn = null; + var pc = PrepareCommand(cmdType, cmdText, cmdParms, ref logtxt); + int val = 0; + Exception ex = null; + try { + if (pc.cmd.Connection == null) pc.cmd.Connection = (conn = this.MasterPool.Get()).Value; + val = pc.cmd.ExecuteNonQuery(); + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(MasterPool, conn, ex); //this.MasterPool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(this.MasterPool, pc.cmd, ex, dt, logtxt); + pc.cmd.Parameters.Clear(); + return val; + } + public object ExecuteScalar(string sql, object parms = null) => ExecuteScalar(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + public object ExecuteScalar(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + DateTime dt = DateTime.Now; + string logtxt = ""; + DateTime logtxt_dt = DateTime.Now; + Object conn = null; + var pc = PrepareCommand(cmdType, cmdText, cmdParms, ref logtxt); + object val = null; + Exception ex = null; + try { + if (pc.cmd.Connection == null) pc.cmd.Connection = (conn = this.MasterPool.Get()).Value; + val = pc.cmd.ExecuteScalar(); + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(MasterPool, conn, ex); //this.MasterPool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(this.MasterPool, pc.cmd, ex, dt, logtxt); + pc.cmd.Parameters.Clear(); + return val; + } + + private (DbTransaction tran, DbCommand cmd) PrepareCommand(CommandType cmdType, string cmdText, DbParameter[] cmdParms, ref string logtxt) { + var dt = DateTime.Now; + DbCommand cmd = CreateCommand(); + cmd.CommandType = cmdType; + cmd.CommandText = cmdText; + + if (cmdParms != null) { + foreach (var parm in cmdParms) { + if (parm == null) continue; + if (parm.Value == null) parm.Value = DBNull.Value; + cmd.Parameters.Add(parm); + } + } + + var tran = TransactionCurrentThread; + if (IsTracePerformance) logtxt += $" PrepareCommand_part1: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms cmdParms: {cmdParms.Length}\r\n"; + + if (tran != null) { + if (IsTracePerformance) dt = DateTime.Now; + cmd.Connection = tran.Connection; + cmd.Transaction = tran; + if (IsTracePerformance) logtxt += $" PrepareCommand_tran!=null: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + } + + if (IsTracePerformance) dt = DateTime.Now; + AutoCommitTransaction(); + if (IsTracePerformance) logtxt += $" AutoCommitTransaction: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + + return (tran, cmd); + } + } +} diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs new file mode 100644 index 00000000..ed08861d --- /dev/null +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs @@ -0,0 +1,215 @@ +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace FreeSql.Internal.CommonProvider { + partial class AdoProvider { + public Task> QueryAsync(string sql, object parms = null) => QueryAsync(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task> QueryAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + var names = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var ds = new List(); + await ExecuteReaderAsync(async dr => { + if (names.Any() == false) + for (var a = 0; a < dr.FieldCount; a++) names.Add(dr.GetName(a), a); + object[] values = new object[dr.FieldCount]; + for (int a = 0; a < values.Length; a++) if (!await dr.IsDBNullAsync(a)) values[a] = await dr.GetFieldValueAsync(a); + ds.Add(values); + }, cmdType, cmdText, cmdParms); + var ret = new List(); + foreach (var row in ds) { + var read = Utils.ExecuteArrayRowReadClassOrTuple(typeof(T), names, row); + ret.Add(read.value == null ? default(T) : (T) read.value); + } + return ret; + } + public Task ExecuteReaderAsync(Func readerHander, string sql, object parms = null) => ExecuteReaderAsync(readerHander, CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task ExecuteReaderAsync(Func readerHander, CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + DateTime dt = DateTime.Now; + string logtxt = ""; + DateTime logtxt_dt = DateTime.Now; + var pool = this.MasterPool; + bool isSlave = false; + + //读写分离规则 + if (this.SlavePools.Any() && cmdText.StartsWith("SELECT ", StringComparison.CurrentCultureIgnoreCase)) { + var availables = slaveUnavailables == 0 ? + //查从库 + this.SlavePools : ( + //查主库 + slaveUnavailables == this.SlavePools.Count ? new List>() : + //查从库可用 + this.SlavePools.Where(sp => sp.IsAvailable).ToList()); + if (availables.Any()) { + isSlave = true; + pool = availables.Count == 1 ? this.SlavePools[0] : availables[slaveRandom.Next(availables.Count)]; + } + } + + Object conn = null; + var cmd = PrepareCommandAsync(cmdType, cmdText, cmdParms, ref logtxt); + if (IsTracePerformance) logtxt += $"PrepareCommand: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + Exception ex = null; + try { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + if (isSlave) { + //从库查询切换,恢复 + bool isSlaveFail = false; + try { + if (cmd.Connection == null) cmd.Connection = (conn = await pool.GetAsync()).Value; + //if (slaveRandom.Next(100) % 2 == 0) throw new Exception("测试从库抛出异常"); + } catch { + isSlaveFail = true; + } + if (isSlaveFail) { + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(pool, conn, ex); //pool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(pool, cmd, new Exception($"连接失败,准备切换其他可用服务器"), dt, logtxt, false); + cmd.Parameters.Clear(); + await ExecuteReaderAsync(readerHander, cmdType, cmdText, cmdParms); + return; + } + } else { + //主库查询 + if (cmd.Connection == null) cmd.Connection = (conn = await pool.GetAsync()).Value; + } + if (IsTracePerformance) { + logtxt += $"Open: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + logtxt_dt = DateTime.Now; + } + using (var dr = await cmd.ExecuteReaderAsync()) { + if (IsTracePerformance) logtxt += $"ExecuteReader: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + while (true) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + bool isread = await dr.ReadAsync(); + if (IsTracePerformance) logtxt += $" dr.Read: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + if (isread == false) break; + + if (readerHander != null) { + object[] values = null; + if (IsTracePerformance) { + logtxt_dt = DateTime.Now; + values = new object[dr.FieldCount]; + for (int a = 0; a < values.Length; a++) if (!await dr.IsDBNullAsync(a)) values[a] = await dr.GetFieldValueAsync(a); + logtxt += $" dr.GetValues: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + logtxt_dt = DateTime.Now; + } + await readerHander(dr); + if (IsTracePerformance) logtxt += $" readerHander: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms ({string.Join(",", values)})\r\n"; + } + } + if (IsTracePerformance) logtxt_dt = DateTime.Now; + dr.Close(); + } + if (IsTracePerformance) logtxt += $"ExecuteReader_dispose: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(pool, conn, ex); //pool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(pool, cmd, ex, dt, logtxt); + cmd.Parameters.Clear(); + } + public Task ExecuteArrayAsync(string sql, object parms = null) => ExecuteArrayAsync(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task ExecuteArrayAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + List ret = new List(); + await ExecuteReaderAsync(async dr => { + object[] values = new object[dr.FieldCount]; + for (int a = 0; a < values.Length; a++) if (!await dr.IsDBNullAsync(a)) values[a] = await dr.GetFieldValueAsync(a); + ret.Add(values); + }, cmdType, cmdText, cmdParms); + return ret.ToArray(); + } + public Task ExecuteDataTableAsync(string sql, object parms = null) => ExecuteDataTableAsync(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task ExecuteDataTableAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + var ret = new DataTable(); + await ExecuteReaderAsync(async dr => { + if (ret.Columns.Count == 0) + for (var a = 0; a < dr.FieldCount; a++) ret.Columns.Add(dr.GetName(a)); + object[] values = new object[ret.Columns.Count]; + for (int a = 0; a < values.Length; a++) if (!await dr.IsDBNullAsync(a)) values[a] = await dr.GetFieldValueAsync(a); + ret.Rows.Add(values); + }, cmdType, cmdText, cmdParms); + return ret; + } + public Task ExecuteNonQueryAsync(string sql, object parms = null) => ExecuteNonQueryAsync(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task ExecuteNonQueryAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + DateTime dt = DateTime.Now; + string logtxt = ""; + Object conn = null; + var cmd = PrepareCommandAsync(cmdType, cmdText, cmdParms, ref logtxt); + var logtxt_dt = DateTime.Now; + int val = 0; + Exception ex = null; + try { + if (cmd.Connection == null) cmd.Connection = (conn = await this.MasterPool.GetAsync()).Value; + val = await cmd.ExecuteNonQueryAsync(); + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(MasterPool, conn, ex); //this.MasterPool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(this.MasterPool, cmd, ex, dt, logtxt); + cmd.Parameters.Clear(); + return val; + } + public Task ExecuteScalarAsync(string sql, object parms = null) => ExecuteScalarAsync(CommandType.Text, sql, GetDbParamtersByObject(sql, parms)); + async public Task ExecuteScalarAsync(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { + var dt = DateTime.Now; + var logtxt = ""; + Object conn = null; + var cmd = PrepareCommandAsync(cmdType, cmdText, cmdParms, ref logtxt); + var logtxt_dt = DateTime.Now; + object val = null; + Exception ex = null; + try { + if (cmd.Connection == null) cmd.Connection = (conn = await this.MasterPool.GetAsync()).Value; + val = await cmd.ExecuteScalarAsync(); + } catch (Exception ex2) { + ex = ex2; + } + + if (conn != null) { + if (IsTracePerformance) logtxt_dt = DateTime.Now; + ReturnConnection(MasterPool, conn, ex); //this.MasterPool.Return(conn, ex); + if (IsTracePerformance) logtxt += $"ReleaseConnection: {DateTime.Now.Subtract(logtxt_dt).TotalMilliseconds}ms Total: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms"; + } + LoggerException(this.MasterPool, cmd, ex, dt, logtxt); + cmd.Parameters.Clear(); + return val; + } + + private DbCommand PrepareCommandAsync(CommandType cmdType, string cmdText, DbParameter[] cmdParms, ref string logtxt) { + DateTime dt = DateTime.Now; + DbCommand cmd = CreateCommand(); + cmd.CommandType = cmdType; + cmd.CommandText = cmdText; + + if (cmdParms != null) { + foreach (var parm in cmdParms) { + if (parm == null) continue; + if (parm.Value == null) parm.Value = DBNull.Value; + cmd.Parameters.Add(parm); + } + } + + if (IsTracePerformance) logtxt += $" PrepareCommand_tran==null: {DateTime.Now.Subtract(dt).TotalMilliseconds}ms\r\n"; + + return cmd; + } + } +} diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs new file mode 100644 index 00000000..6807e86e --- /dev/null +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs @@ -0,0 +1,152 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading; + +namespace FreeSql.Internal.CommonProvider { + partial class AdoProvider { + + class Transaction2 { + internal Object Conn; + internal DbTransaction Transaction; + internal DateTime RunTime; + internal TimeSpan Timeout; + + public Transaction2(Object conn, DbTransaction tran, TimeSpan timeout) { + Conn = conn; + Transaction = tran; + RunTime = DateTime.Now; + Timeout = timeout; + } + } + + private Dictionary _trans = new Dictionary(); + private object _trans_lock = new object(); + + public DbTransaction TransactionCurrentThread => _trans.TryGetValue(Thread.CurrentThread.ManagedThreadId, out var conn) && conn.Transaction?.Connection != null ? conn.Transaction : null; + + private Dictionary> _preRemoveKeys = new Dictionary>(); + private object _preRemoveKeys_lock = new object(); + public string[] PreRemove(params string[] key) { + var tid = Thread.CurrentThread.ManagedThreadId; + List keys = null; + if (key == null || key.Any() == false) return _preRemoveKeys.TryGetValue(tid, out keys) ? keys.ToArray() : new string[0]; + _log.LogDebug($"线程{tid}事务预删除 {JsonConvert.SerializeObject(key)}"); + if (_preRemoveKeys.TryGetValue(tid, out keys) == false) + lock (_preRemoveKeys_lock) + if (_preRemoveKeys.TryGetValue(tid, out keys) == false) { + _preRemoveKeys.Add(tid, keys = new List(key)); + return key; + } + keys.AddRange(key); + return keys.ToArray(); + } + public void TransactionPreRemoveCache(params string[] key) => PreRemove(key); + + /// + /// 启动事务 + /// + public void BeginTransaction(TimeSpan timeout) { + int tid = Thread.CurrentThread.ManagedThreadId; + Transaction2 tran = null; + Object conn = null; + + try { + conn = MasterPool.Get(); + tran = new Transaction2(conn, conn.Value.BeginTransaction(), timeout); + } catch(Exception ex) { + _log.LogError($"数据库出错(开启事务){ex.Message} \r\n{ex.StackTrace}"); + throw ex; + } + if (_trans.ContainsKey(tid)) CommitTransaction(); + + lock (_trans_lock) + _trans.Add(tid, tran); + } + + /// + /// 自动提交事务 + /// + private void AutoCommitTransaction() { + if (_trans.Count > 0) { + Transaction2[] trans = null; + lock (_trans_lock) + trans = _trans.Values.Where(st2 => DateTime.Now.Subtract(st2.RunTime) > st2.Timeout).ToArray(); + foreach (Transaction2 tran in trans) CommitTransaction(true, tran); + } + } + private void CommitTransaction(bool isCommit, Transaction2 tran) { + if (tran == null || tran.Transaction == null || tran.Transaction.Connection == null) return; + + if (_trans.ContainsKey(tran.Conn.LastGetThreadId)) + lock (_trans_lock) + if (_trans.ContainsKey(tran.Conn.LastGetThreadId)) + _trans.Remove(tran.Conn.LastGetThreadId); + + var removeKeys = PreRemove(); + if (_preRemoveKeys.ContainsKey(tran.Conn.LastGetThreadId)) + lock (_preRemoveKeys_lock) + if (_preRemoveKeys.ContainsKey(tran.Conn.LastGetThreadId)) + _preRemoveKeys.Remove(tran.Conn.LastGetThreadId); + + Exception ex = null; + var f001 = isCommit ? "提交" : "回滚"; + try { + _log.LogDebug($"线程{tran.Conn.LastGetThreadId}事务{f001},批量删除缓存key {Newtonsoft.Json.JsonConvert.SerializeObject(removeKeys)}"); + _cache.Remove(removeKeys); + if (isCommit) tran.Transaction.Commit(); + else tran.Transaction.Rollback(); + } catch (Exception ex2) { + ex = ex2; + _log.LogError($"数据库出错({f001}事务):{ex.Message} {ex.StackTrace}"); + } finally { + ReturnConnection(MasterPool, tran.Conn, ex); //MasterPool.Return(tran.Conn, ex); + } + } + private void CommitTransaction(bool isCommit) { + if (_trans.TryGetValue(Thread.CurrentThread.ManagedThreadId, out var tran)) CommitTransaction(isCommit, tran); + } + /// + /// 提交事务 + /// + public void CommitTransaction() => CommitTransaction(true); + /// + /// 回滚事务 + /// + public void RollbackTransaction() => CommitTransaction(false); + + public void Dispose() { + Transaction2[] trans = null; + lock (_trans_lock) + trans = _trans.Values.ToArray(); + foreach (Transaction2 tran in trans) CommitTransaction(false, tran); + } + + /// + /// 开启事务(不支持异步),60秒未执行完将自动提交 + /// + /// 事务体 () => {} + public void Transaction(Action handler) { + Transaction(handler, TimeSpan.FromSeconds(60)); + } + /// + /// 开启事务(不支持异步) + /// + /// 事务体 () => {} + /// 超时,未执行完将自动提交 + public void Transaction(Action handler, TimeSpan timeout) { + try { + BeginTransaction(timeout); + handler(); + CommitTransaction(); + } catch (Exception ex) { + RollbackTransaction(); + throw ex; + } + } + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs new file mode 100644 index 00000000..4e1f81b0 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs @@ -0,0 +1,19 @@ +using System.Text.RegularExpressions; + +namespace FreeSql.Internal.CommonProvider { + partial class AdoProvider { + + public abstract object AddslashesProcessParam(object param); + public string Addslashes(string filter, params object[] parms) { + if (filter == null || parms == null) return string.Empty; + if (parms.Length == 0) return filter; + var nparms = new object[parms.Length]; + for (int a = 0; a < parms.Length; a++) { + if (parms[a] == null) + filter = Regex.Replace(filter, @"\s*(=|IN)\s*\{" + a + @"\}", " IS {" + a + "}", RegexOptions.IgnoreCase); + nparms[a] = AddslashesProcessParam(parms[a]); + } + try { string ret = string.Format(filter, nparms); return ret; } catch { return filter; } + } + } +} diff --git a/FreeSql/Internal/CommonProvider/CacheProvider.cs b/FreeSql/Internal/CommonProvider/CacheProvider.cs new file mode 100644 index 00000000..a64d80b6 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/CacheProvider.cs @@ -0,0 +1,176 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.Internal.CommonProvider { + class CacheProvider : ICache { + + public IDistributedCache Cache { get; private set; } + private bool CacheSupportMultiRemove = false; + private static DateTime dt1970 = new DateTime(1970, 1, 1); + + public CacheProvider(IDistributedCache cache, ILogger log) { + if (cache == null) cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions { })); + Cache = cache; + var key1 = $"testCacheSupportMultiRemoveFreeSql{Guid.NewGuid().ToString("N")}"; + var key2 = $"testCacheSupportMultiRemoveFreeSql{Guid.NewGuid().ToString("N")}"; + Cache.Set(key1, new byte[] { 65 }); + Cache.Set(key2, new byte[] { 65 }); + try { Cache.Remove($"{key1}|{key2}"); } catch { } // redis-cluster 不允许执行 multi keys 命令 + CacheSupportMultiRemove = Cache.Get(key1) == null && cache.Get(key2) == null; + if (CacheSupportMultiRemove == false) { + log.LogWarning("FreeSql Warning: 低性能, IDistributedCache 没现实批量删除缓存 Cache.Remove(\"key1|key2\")."); + Remove(key1, key2); + } + } + + public Func Serialize { get; set; } + public Func Deserialize { get; set; } + + Func JsonSerializerSettings = () => { + var st = new JsonSerializerSettings(); + st.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + st.DateFormatHandling = DateFormatHandling.IsoDateFormat; + st.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; + return st; + }; + string SerializeObject(object value) { + if (Serialize != null) return Serialize(value); + return JsonConvert.SerializeObject(value, this.JsonSerializerSettings()); + } + T DeserializeObject(string value) { + if (Deserialize != null) return (T) Deserialize(value, typeof(T)); + return JsonConvert.DeserializeObject(value, this.JsonSerializerSettings()); + } + + public void Set(string key, T data, int timeoutSeconds = 0) { + if (string.IsNullOrEmpty(key)) return; + Cache.Set(key, Encoding.UTF8.GetBytes(this.SerializeObject(data)), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(timeoutSeconds) }); + } + public T Get(string key) { + if (string.IsNullOrEmpty(key)) return default(T); + var value = Cache.Get(key); + if (value == null) return default(T); + return this.DeserializeObject(Encoding.UTF8.GetString(value)); + } + public string Get(string key) { + if (string.IsNullOrEmpty(key)) return null; + var value = Cache.Get(key); + if (value == null) return null; + return Encoding.UTF8.GetString(value); + } + + async public Task SetAsync(string key, T data, int timeoutSeconds = 0) { + if (string.IsNullOrEmpty(key)) return; + await Cache.SetAsync(key, Encoding.UTF8.GetBytes(this.SerializeObject(data)), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(timeoutSeconds) }); + } + async public Task GetAsync(string key) { + if (string.IsNullOrEmpty(key)) return default(T); + var value = await Cache.GetAsync(key); + if (value == null) return default(T); + return this.DeserializeObject(Encoding.UTF8.GetString(value)); + } + async public Task GetAsync(string key) { + if (string.IsNullOrEmpty(key)) return null; + var value = await Cache.GetAsync(key); + if (value == null) return null; + return Encoding.UTF8.GetString(value); + } + + public void Remove(params string[] keys) { + if (keys == null || keys.Length == 0) return; + var keysDistinct = keys.Distinct(); + if (CacheSupportMultiRemove) Cache.Remove(string.Join("|", keysDistinct)); + else foreach (var key in keysDistinct) Cache.Remove(key); + } + + async public Task RemoveAsync(params string[] keys) { + if (keys == null || keys.Length == 0) return; + var keysDistinct = keys.Distinct(); + if (CacheSupportMultiRemove) await Cache.RemoveAsync(string.Join("|", keysDistinct)); + else foreach (var key in keysDistinct) await Cache.RemoveAsync(key); + } + + public T Shell(string key, int timeoutSeconds, Func getData) { + if (timeoutSeconds <= 0) return getData(); + if (Cache == null) throw new Exception("缓存现实 IDistributedCache 为 null"); + var cacheValue = Cache.Get(key); + if (cacheValue != null) { + try { + var txt = Encoding.UTF8.GetString(cacheValue); + return DeserializeObject(txt); + } catch { + Cache.Remove(key); + throw; + } + } + var ret = getData(); + Cache.Set(key, Encoding.UTF8.GetBytes(SerializeObject(ret)), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(timeoutSeconds) }); + return ret; + } + + public T Shell(string key, string field, int timeoutSeconds, Func getData) { + if (timeoutSeconds <= 0) return getData(); + if (Cache == null) throw new Exception("缓存现实 IDistributedCache 为 null"); + var hashkey = $"{key}:{field}"; + var cacheValue = Cache.Get(hashkey); + if (cacheValue != null) { + try { + var txt = Encoding.UTF8.GetString(cacheValue); + var value = DeserializeObject<(T, long)>(txt); + if (DateTime.Now.Subtract(dt1970.AddSeconds(value.Item2)).TotalSeconds <= timeoutSeconds) return value.Item1; + } catch { + Cache.Remove(hashkey); + throw; + } + } + var ret = (getData(), (long) DateTime.Now.Subtract(dt1970).TotalSeconds); + Cache.Set(hashkey, Encoding.UTF8.GetBytes(SerializeObject(ret))); + return ret.Item1; + } + + async public Task ShellAsync(string key, int timeoutSeconds, Func> getDataAsync) { + if (timeoutSeconds <= 0) return await getDataAsync(); + if (Cache == null) throw new Exception("缓存现实 IDistributedCache 为 null"); + var cacheValue = await Cache.GetAsync(key); + if (cacheValue != null) { + try { + var txt = Encoding.UTF8.GetString(cacheValue); + return DeserializeObject(txt); + } catch { + await Cache.RemoveAsync(key); + throw; + } + } + var ret = await getDataAsync(); + await Cache.SetAsync(key, Encoding.UTF8.GetBytes(SerializeObject(ret)), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(timeoutSeconds) }); + return ret; + } + + async public Task ShellAsync(string key, string field, int timeoutSeconds, Func> getDataAsync) { + if (timeoutSeconds <= 0) return await getDataAsync(); + if (Cache == null) throw new Exception("缓存现实 IDistributedCache 为 null"); + var hashkey = $"{key}:{field}"; + var cacheValue = await Cache.GetAsync(hashkey); + if (cacheValue != null) { + try { + var txt = Encoding.UTF8.GetString(cacheValue); + var value = DeserializeObject<(T, long)>(txt); + if (DateTime.Now.Subtract(dt1970.AddSeconds(value.Item2)).TotalSeconds <= timeoutSeconds) return value.Item1; + } catch { + await Cache.RemoveAsync(hashkey); + throw; + } + } + var ret = (await getDataAsync(), (long) DateTime.Now.Subtract(dt1970).TotalSeconds); + await Cache.SetAsync(hashkey, Encoding.UTF8.GetBytes(SerializeObject(ret))); + return ret.Item1; + } + } +} diff --git a/FreeSql/Internal/CommonProvider/DeleteProvider.cs b/FreeSql/Internal/CommonProvider/DeleteProvider.cs new file mode 100644 index 00000000..35fbad2c --- /dev/null +++ b/FreeSql/Internal/CommonProvider/DeleteProvider.cs @@ -0,0 +1,52 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Internal.CommonProvider { + + abstract partial class DeleteProvider : IDelete where T1 : class { + protected IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + protected List _source = new List(); + protected TableInfo _table; + protected StringBuilder _where = new StringBuilder(); + protected int _whereTimes = 0; + protected List _params = new List(); + + public DeleteProvider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + _table = _commonUtils.GetTableByEntity(typeof(T1)); + _where.Append("DELETE FROM ").Append(_commonUtils.QuoteSqlName(_table.DbName)).Append(" WHERE "); + this.Where(_commonUtils.WhereObject(_table, "", dywhere)); + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); + } + + public long ExecuteAffrows() { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + return _orm.Ado.ExecuteNonQuery(CommandType.Text, sql, _params.ToArray()); + } + public abstract List ExecuteDeleted(); + + public IDelete Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambdaNoneForeignObject(null, null, exp?.Body)); + public IDelete Where(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this; + if (++_whereTimes > 1) _where.Append(" AND "); + _where.Append("(").Append(sql).Append(")"); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this; + } + public IDelete Where(T1 item) => this.Where(new[] { item }); + public IDelete Where(IEnumerable items) => this.Where(_commonUtils.WhereItems(_table, "", items)); + public IDelete WhereExists(ISelect select, bool notExists = false) where TEntity2 : class => this.Where($"{(notExists ? "NOT " : "")}EXISTS({select.ToSql("1")})"); + + public string ToSql() => _whereTimes <= 0 ? null : _where.ToString(); + } +} diff --git a/FreeSql/Internal/CommonProvider/InsertProvider.cs b/FreeSql/Internal/CommonProvider/InsertProvider.cs new file mode 100644 index 00000000..10bfd4d0 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/InsertProvider.cs @@ -0,0 +1,90 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Internal.CommonProvider { + + abstract partial class InsertProvider : IInsert where T1 : class { + protected IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + protected List _source = new List(); + protected Dictionary _ignore = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + protected TableInfo _table; + protected DbParameter[] _params; + + public InsertProvider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + _table = _commonUtils.GetTableByEntity(typeof(T1)); + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); + } + + public IInsert AppendData(T1 source) { + if (source != null) _source.Add(source); + return this; + } + public IInsert AppendData(IEnumerable source) { + if (source != null) _source.AddRange(source.Where(a => a != null)); + return this; + } + + public long ExecuteAffrows() => _orm.Ado.ExecuteNonQuery(CommandType.Text, this.ToSql(), _params); + public abstract long ExecuteIdentity(); + public abstract List ExecuteInserted(); + + public IInsert IgnoreColumns(Expression> columns) { + var cols = _commonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(null, columns?.Body, false).Distinct(); + _ignore.Clear(); + foreach (var col in cols) _ignore.Add(col, true); + return this; + } + public IInsert InsertColumns(Expression> columns) { + var cols = _commonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(null, columns?.Body, false).ToDictionary(a => a, a => true); + _ignore.Clear(); + foreach (var col in _table.Columns.Values) + if (cols.ContainsKey(col.Attribute.Name) == false) + _ignore.Add(col.Attribute.Name, true); + return this; + } + + public string ToSql() { + if (_source == null || _source.Any() == false) return null; + var sb = new StringBuilder(); + sb.Append("INSERT INTO ").Append(_commonUtils.QuoteSqlName(_table.DbName)).Append("("); + var colidx = 0; + foreach (var col in _table.Columns.Values) + if (col.Attribute.IsIdentity == false && _ignore.ContainsKey(col.Attribute.Name) == false) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)); + ++colidx; + } + if (colidx == 0) return null; + sb.Append(") VALUES"); + _params = new DbParameter[colidx * _source.Count]; + var didx = 0; + foreach (var d in _source) { + if (didx > 0) sb.Append(", "); + sb.Append("("); + var colidx2 = 0; + foreach (var col in _table.Columns.Values) + if (col.Attribute.IsIdentity == false && _ignore.ContainsKey(col.Attribute.Name) == false) { + if (colidx2 > 0) sb.Append(", "); + sb.Append("?").Append(col.CsName).Append(didx); + _params[didx * colidx + colidx2] = _commonUtils.AppendParamter(null, $"{col.CsName}{didx}", _table.Properties.TryGetValue(col.CsName, out var tryp) ? tryp.GetValue(d) : DBNull.Value); + ++colidx2; + } + sb.Append(")"); + ++didx; + } + return sb.ToString(); + } + } +} + diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs new file mode 100644 index 00000000..7963f4a8 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -0,0 +1,263 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select0Provider : ISelect0 where TSelect : class where T1 : class { + + protected int _limit, _skip; + protected string _select = "SELECT ", _orderby, _groupby, _having; + protected StringBuilder _where = new StringBuilder(); + protected List _params = new List(); + protected List _tables = new List(); + protected StringBuilder _join = new StringBuilder(); + protected (int seconds, string key) _cache = (0, null); + protected IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + + internal static void CopyData(Select0Provider from, object to) { + var toType = to?.GetType(); + if (toType == null) return; + toType.GetField("_limit", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._limit); + toType.GetField("_skip", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._skip); + toType.GetField("_select", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._select); + toType.GetField("_where", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, new StringBuilder().Append(from._where.ToString())); + toType.GetField("_params", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, new List(from._params.ToArray())); + toType.GetField("_tables", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, new List(from._tables.ToArray())); + toType.GetField("_join", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, new StringBuilder().Append(from._join.ToString())); + toType.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._cache); + //toType.GetField("_orm", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._orm); + //toType.GetField("_commonUtils", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._commonUtils); + //toType.GetField("_commonExpression", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._commonExpression); + } + + public Select0Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + _tables.Add(new SelectTableInfo { Table = _commonUtils.GetTableByEntity(typeof(T1)), Alias = "a", On = null, Type = SelectTableInfoType.From }); + this.Where(_commonUtils.WhereObject(_tables.First().Table, "a.", dywhere)); + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); + } + + public bool Any() { + this.Limit(1); + return this.ToList("1").FirstOrDefault() == 1; + } + public TSelect Caching(int seconds, string key = null) { + _cache = (seconds, key); + return this as TSelect; + } + public long Count() => this.ToList("count(1)").FirstOrDefault(); + public TSelect Count(out long count) { + count = this.Count(); + return this as TSelect; + } + + public TSelect GroupBy(string sql, object parms = null) { + _groupby = sql; + if (string.IsNullOrEmpty(_groupby)) return this as TSelect; + _groupby = string.Concat(" \r\nGROUP BY ", _groupby); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(_groupby, parms)); + return this as TSelect; + } + public TSelect Having(string sql, object parms = null) { + if (string.IsNullOrEmpty(_groupby) || string.IsNullOrEmpty(sql)) return this as TSelect; + _having = string.Concat(_having, " AND (", sql, ")"); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + + public TSelect LeftJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.LeftJoin); + public TSelect InnerJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.InnerJoin); + public TSelect RightJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.RightJoin); + public TSelect LeftJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.LeftJoin); + public TSelect InnerJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.InnerJoin); + public TSelect RightJoin(Expression> exp) => this.InternalJoin(exp?.Body, SelectTableInfoType.RightJoin); + + public TSelect InnerJoin(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this as TSelect; + _join.Append(" \r\nINNER JOIN ").Append(sql); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + public TSelect LeftJoin(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this as TSelect; + _join.Append(" \r\nLEFT JOIN ").Append(sql); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + + public TSelect Limit(int limit) { + _limit = limit; + return this as TSelect; + } + public TSelect Master() { + _select = " SELECT "; + return this as TSelect; + } + public TSelect Offset(int offset) => this.Skip(offset) as TSelect; + + public TSelect OrderBy(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) _orderby = null; + _orderby = string.Concat(string.IsNullOrEmpty(_orderby) ? " \r\nORDER BY " : "", _orderby, sql); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + public TSelect Page(int pageIndex, int pageSize) { + this.Skip(Math.Max(0, pageIndex - 1) * pageSize); + return this.Limit(pageSize) as TSelect; + } + + public TSelect RightJoin(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this as TSelect; + _join.Append(" \r\nRIGHT JOIN ").Append(sql); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + + public TSelect Skip(int offset) { + _skip = offset; + return this as TSelect; + } + public TSelect Take(int limit) => this.Limit(limit) as TSelect; + + public List ToList(string field) { + var sql = this.ToSql(field); + if (_cache.seconds > 0 && string.IsNullOrEmpty(_cache.key)) _cache.key = sql; + + return _orm.Cache.Shell(_cache.key, _cache.seconds, () => { + List ret = new List(); + Type type = typeof(TTuple); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql, _params.ToArray()); + foreach (var dr in ds) { + var read = Utils.ExecuteArrayRowReadClassOrTuple(type, null, dr); + ret.Add(read.value == null ? default(TTuple) : (TTuple)read.value); + } + return ret; + }); + } + public List ToList() { + return this.ToList(this.GetAllField()); + } + public T1 ToOne() { + this.Limit(1); + return ToList().FirstOrDefault(); + } + + protected List ToList((ReadAnonymousTypeInfo map, string field) af) { + var sql = this.ToSql(af.field); + if (_cache.seconds > 0 && string.IsNullOrEmpty(_cache.key)) _cache.key = $"{sql}{string.Join("|", _params.Select(a => a.Value))}"; + + var drarr = _orm.Cache.Shell(_cache.key, _cache.seconds, () => _orm.Ado.ExecuteArray(CommandType.Text, sql, _params.ToArray())); + var ret = new List(); + for (var a = 0; a < drarr.Length; a++) { + var dr = drarr[a]; + var index = -1; + ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, dr, ref index)); + } + return ret; + } + protected (ReadAnonymousTypeInfo map, string field) GetNewExpressionField(NewExpression newexp) { + var map = new ReadAnonymousTypeInfo(); + var field = new StringBuilder(); + var index = 0; + + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp); + return (map, map.Childs.Count > 0 ? field.Remove(0, 2).ToString() : null); + } + protected (ReadAnonymousTypeInfo map, string field) GetAllField() { + var type = typeof(T1); + var map = new ReadAnonymousTypeInfo { Consturctor = type.GetConstructor(new Type[0]), ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Properties }; + var field = new StringBuilder(); + var tb = _tables.First(); + var index = 0; + var ps = typeof(T1).GetProperties(); + foreach (var p in ps) { + var child = new ReadAnonymousTypeInfo { CsName = p.Name }; + if (tb.Table.ColumnsByCs.TryGetValue(p.Name, out var col)) { //普通字段 + if (index > 0) field.Append(", "); + field.Append(tb.Alias).Append(".").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as").Append(++index); + } else { + var tb2 = _tables.Where(a => a.Table.Type == p.PropertyType && a.Alias.Contains(p.Name)).FirstOrDefault(); + if (tb2 == null && ps.Where(pw => pw.PropertyType == p.PropertyType).Count() == 1) tb2 = _tables.Where(a => a.Table.Type == p.PropertyType).FirstOrDefault(); + if (tb2 == null) continue; + child.Consturctor = tb2.Table.Type.GetConstructor(new Type[0]); + child.ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Properties; + foreach (var col2 in tb2.Table.Columns.Values) { + if (index > 0) field.Append(", "); + field.Append(tb2.Alias).Append(".").Append(_commonUtils.QuoteSqlName(col2.Attribute.Name)).Append(" as").Append(++index); + child.Childs.Add(new ReadAnonymousTypeInfo { CsName = col2.CsName }); + } + } + map.Childs.Add(child); + } + return (map, field.ToString()); + } + public abstract string ToSql(string field = null); + + public TSelect Where(string sql, object parms = null) => this.WhereIf(true, sql, parms); + public TSelect WhereIf(bool condition, string sql, object parms = null) { + if (condition == false || string.IsNullOrEmpty(sql)) return this as TSelect; + _where.Append(" AND (").Append(sql).Append(")"); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this as TSelect; + } + #region common + + protected TMember InternalAvg(Expression exp) => this.ToList($"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true)})").FirstOrDefault(); + protected TMember InternalMax(Expression exp) => this.ToList($"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true)})").FirstOrDefault(); + protected TMember InternalMin(Expression exp) => this.ToList($"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true)})").FirstOrDefault(); + protected TMember InternalSum(Expression exp) => this.ToList($"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true)})").FirstOrDefault(); + + protected TSelect InternalGroupBy(Expression columns) => this.GroupBy(string.Join(", ", _commonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, columns, true))); + protected TSelect InternalJoin(Expression exp, SelectTableInfoType joinType) { + _commonExpression.ExpressionJoinLambda(_tables, joinType, exp); + return this as TSelect; + } + protected TSelect InternalJoin(Expression exp, SelectTableInfoType joinType) { + var tb = _commonUtils.GetTableByEntity(typeof(T2)); + if (tb == null) throw new ArgumentException("T2 类型错误"); + _tables.Add(new SelectTableInfo { Table = tb, Alias = $"IJ{_tables.Count}", On = null, Type = joinType }); + _commonExpression.ExpressionJoinLambda(_tables, joinType, exp); + return this as TSelect; + } + protected TSelect InternalOrderBy(Expression column) => this.OrderBy(_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true)); + protected TSelect InternalOrderByDescending(Expression column) => this.OrderBy($"{_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true)} DESC"); + + protected List InternalToList(Expression select) => this.ToList(this.GetNewExpressionField(select as NewExpression)); + + protected TSelect InternalWhere(Expression exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp)); + protected TSelect InternalWhereLikeOr(Expression columns, string pattern, bool notLike) { + if (string.IsNullOrEmpty(pattern)) return this as TSelect; + var cols = _commonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(_tables, columns, true); + if (cols.Any() == false) return this as TSelect; + var filter = ""; + foreach (var col in cols) { + if (string.IsNullOrEmpty(col)) continue; + filter += string.Concat(" OR ", _commonUtils.FormatSql($"{col} {(notLike ? "NOT LIKE" : "LIKE")} {{0}}", pattern)); + } + if (string.IsNullOrEmpty(filter)) return this as TSelect; + return this.Where(filter.Substring(4)); + } + protected TSelect InternalWhereLike(Expression column, string pattern, bool notLike) { + if (string.IsNullOrEmpty(pattern)) return this as TSelect; + string col = _commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true); + if (string.IsNullOrEmpty(col)) return this as TSelect; + return this.Where(_commonUtils.FormatSql($"{col} {(notLike ? "NOT LIKE" : "LIKE")} {{0}}", pattern)); + } + + protected TSelect InternalJoin(Expression exp) { + return this as TSelect; + } + #endregion + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select10Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select10Provider.cs new file mode 100644 index 00000000..01ef7649 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select10Provider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select10Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class + where T10 : class { + + public Select10Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs new file mode 100644 index 00000000..acac4d7b --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs @@ -0,0 +1,114 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select1Provider : Select0Provider, T1>, ISelect + where T1 : class { + public Select1Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + + } + + protected ISelect InternalFrom(Expression exp) { + if (exp.NodeType == ExpressionType.Call) { + var expCall = exp as MethodCallExpression; + var stockCall = new Stack(); + while (expCall != null) { + stockCall.Push(expCall); + expCall = expCall.Object as MethodCallExpression; + } + while (stockCall.Any()) { + expCall = stockCall.Pop(); + + switch (expCall.Method.Name) { + case "Where": this.InternalWhere(expCall.Arguments[0]); break; + case "WhereIf": + if (_commonExpression.ExpressionSelectColumn_MemberAccess(null, null, SelectTableInfoType.From, expCall.Arguments[0], false) == "1") + this.InternalWhere(expCall.Arguments[1]); + break; + case "WhereLike": + var whereLikeArg0 = (expCall.Arguments[0] as UnaryExpression).Operand as LambdaExpression; + var whereLikeArg1 = _commonExpression.ExpressionSelectColumn_MemberAccess(null, null, SelectTableInfoType.From, expCall.Arguments[1], false); + var whereLikeArg2 = _commonExpression.ExpressionSelectColumn_MemberAccess(null, null, SelectTableInfoType.From, expCall.Arguments[2], false) == "1"; + if (whereLikeArg0.ReturnType == typeof(string)) this.InternalWhereLike(whereLikeArg0, whereLikeArg1, whereLikeArg2); + else this.InternalWhereLikeOr(whereLikeArg0, whereLikeArg1, whereLikeArg2); + break; + case "GroupBy": this.InternalGroupBy(expCall.Arguments[0]); break; + case "OrderBy": this.InternalOrderBy(expCall.Arguments[0]); break; + case "OrderByDescending": this.InternalOrderByDescending(expCall.Arguments[0]); break; + + case "LeftJoin": this.InternalJoin(expCall.Arguments[0], SelectTableInfoType.LeftJoin); break; + case "InnerJoin": this.InternalJoin(expCall.Arguments[0], SelectTableInfoType.InnerJoin); break; + case "RightJoin": this.InternalJoin(expCall.Arguments[0], SelectTableInfoType.RightJoin); break; + + default: throw new NotImplementedException($"未现实 {expCall.Method.Name}"); + } + } + return this; + } + throw new NotImplementedException($"未现实 {exp}"); + } + + public ISelect As(string alias) { + var oldAs = _tables.First().Alias; + var newAs = string.IsNullOrEmpty(alias) ? "a" : alias; + if (oldAs != newAs) { + _tables.First().Alias = newAs; + var wh = _where.ToString(); + _where.Replace($" {oldAs}.", $" {newAs}."); + } + return this; + } + + public TMember Avg(Expression> column) => this.InternalAvg(column?.Body); + + public abstract ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) where T2 : class where T3 : class;// { this.InternalFrom(exp?.Body); var ret = new Select3Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class;// { this.InternalFrom(exp?.Body); var ret = new Select4Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class;// { this.InternalFrom(exp?.Body); var ret = new Select5Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class;// { this.InternalFrom(exp?.Body); var ret = new Select6Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class;// { this.InternalFrom(exp?.Body); var ret = new Select7Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class;// { this.InternalFrom(exp?.Body); var ret = new Select8Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class;// { this.InternalFrom(exp?.Body); var ret = new Select9Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public abstract ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class;// { this.InternalFrom(exp?.Body); var ret = new Select10Provider(_orm, _commonUtils, _commonExpression, null); Select0Provider, T1>.CopyData(this, ret); return ret; } + + public ISelect GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + public TMember Max(Expression> column) => this.InternalMax(column?.Body); + + public TMember Min(Expression> column) => this.InternalMin(column?.Body); + + public ISelect OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + public ISelect OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + public TMember Sum(Expression> column) => this.InternalSum(column?.Body); + + public List ToList(Expression> select) => this.InternalToList(select?.Body); + + public ISelect Where(Expression> exp) => this.InternalWhere(exp?.Body); + + public ISelect Where(Expression> exp) where T2 : class => this.InternalWhere(exp?.Body); + + public ISelect Where(Expression> exp) where T2 : class where T3 : class => this.InternalWhere(exp?.Body); + + public ISelect Where(Expression> exp) where T2 : class where T3 : class where T4 : class => this.InternalWhere(exp?.Body); + + public ISelect Where(Expression> exp) where T2 : class where T3 : class where T4 : class where T5 : class => this.InternalWhere(exp?.Body); + + public ISelect WhereIf(bool condition, Expression> exp) => condition ? this.InternalWhere(exp?.Body) : this; + + public ISelect WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + public ISelect WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select2Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select2Provider.cs new file mode 100644 index 00000000..1fa78873 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select2Provider.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select2Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class { + + public Select2Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select3Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select3Provider.cs new file mode 100644 index 00000000..1380feab --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select3Provider.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select3Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class { + + public Select3Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select4Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select4Provider.cs new file mode 100644 index 00000000..bd9faf34 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select4Provider.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select4Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class { + + public Select4Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select5Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select5Provider.cs new file mode 100644 index 00000000..b61789ae --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select5Provider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select5Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class { + + public Select5Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select6Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select6Provider.cs new file mode 100644 index 00000000..8466a6f3 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select6Provider.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select6Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class { + + public Select6Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select7Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select7Provider.cs new file mode 100644 index 00000000..6d552002 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select7Provider.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select7Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class { + + public Select7Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select8Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select8Provider.cs new file mode 100644 index 00000000..eb09f9bd --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select8Provider.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract partial class Select8Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class { + + public Select8Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select9Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select9Provider.cs new file mode 100644 index 00000000..5cdfcd8c --- /dev/null +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select9Provider.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.Internal.CommonProvider { + + abstract class Select9Provider : Select0Provider, T1>, ISelect + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + where T9 : class { + + public Select9Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9)); + } + + TMember ISelect.Avg(Expression> column) => this.InternalAvg(column?.Body); + + ISelect ISelect.GroupBy(Expression> columns) => this.InternalGroupBy(columns?.Body); + + TMember ISelect.Max(Expression> column) => this.InternalMax(column?.Body); + + TMember ISelect.Min(Expression> column) => this.InternalMin(column?.Body); + + ISelect ISelect.OrderBy(Expression> column) => this.InternalOrderBy(column?.Body); + + ISelect ISelect.OrderByDescending(Expression> column) => this.InternalOrderByDescending(column?.Body); + + TMember ISelect.Sum(Expression> column) => this.InternalSum(column?.Body); + + List ISelect.ToList(Expression> select) => this.InternalToList(select?.Body); + + ISelect ISelect.Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)); + + ISelect ISelect.WhereIf(bool condition, Expression> exp) => condition ? this.Where(_commonExpression.ExpressionWhereLambda(_tables, exp?.Body)) : this; + + ISelect ISelect.WhereLike(Expression> columns, string pattern, bool notLike) => this.InternalWhereLikeOr(columns?.Body, pattern, notLike); + + ISelect ISelect.WhereLike(Expression> column, string pattern, bool notLike) => this.InternalWhereLike(column?.Body, pattern, notLike); + } +} diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs new file mode 100644 index 00000000..29a21cc2 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -0,0 +1,172 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Internal.CommonProvider { + + abstract partial class UpdateProvider : IUpdate where T1 : class { + protected IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + protected List _source = new List(); + protected Dictionary _ignore = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + protected TableInfo _table; + protected StringBuilder _where = new StringBuilder(); + protected StringBuilder _set = new StringBuilder(); + protected List _params = new List(); + protected List _paramsSource = new List(); + + public UpdateProvider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + _table = _commonUtils.GetTableByEntity(typeof(T1)); + this.Where(_commonUtils.WhereObject(_table, "", dywhere)); + if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); + } + + public long ExecuteAffrows() { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + return _orm.Ado.ExecuteNonQuery(CommandType.Text, sql, _params.Concat(_paramsSource).ToArray()); + } + public abstract List ExecuteUpdated(); + + public IUpdate IgnoreColumns(Expression> columns) { + var cols = _commonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(null, columns?.Body, false).Distinct(); + _ignore.Clear(); + foreach (var col in cols) _ignore.Add(col, true); + return this; + } + + public IUpdate SetSource(T1 source) => this.SetSource(new[] { source }); + public IUpdate SetSource(IEnumerable source) { + if (source == null || source.Any() == false) return this; + _source.AddRange(source.Where(a => a != null)); + return this.Where(_source); + } + + public IUpdate Set(Expression> column, TMember value) { + var col = _commonExpression.ExpressionSelectColumn_MemberAccess(null, null, SelectTableInfoType.From, column?.Body, true); + if (string.IsNullOrEmpty(col)) return this; + _set.Append(", ").Append(col).Append(" = ?p_").Append(_params.Count); + _commonUtils.AppendParamter(_params, null, value); + //foreach (var t in _source) Utils.FillPropertyValue(t, tryf.CsName, value); + return this; + } + public IUpdate Set(Expression> binaryExpression) { + if (binaryExpression?.Body is BinaryExpression == false) return this; + var cols = new List(); + var expt = _commonExpression.ExpressionWhereLambdaNoneForeignObject(null, cols, binaryExpression); + if (cols.Any() == false) return this; + foreach (var col in cols) { + if (col.Column.Attribute.IsNullable) { + var repltype = col.Column.CsType; + if (repltype.FullName.StartsWith("System.Nullable`1[[System.")) repltype = repltype.GenericTypeArguments[0]; + var replval = Activator.CreateInstance(repltype); + if (replval == null) continue; + var replname = _commonUtils.QuoteSqlName(col.Column.Attribute.Name); + replval = _commonUtils.FormatSql("{0}", replval); + expt = expt.Replace(replname, _commonUtils.IsNull(replname, replval)); + } + } + _set.Append(", ").Append(_commonUtils.QuoteSqlName(cols.First().Column.Attribute.Name)).Append(" = ").Append(expt); + return this; + } + public IUpdate SetRaw(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this; + _set.Append(", ").Append(sql); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this; + } + + public IUpdate Where(Expression> expression) => this.Where(_commonExpression.ExpressionWhereLambdaNoneForeignObject(null, null, expression?.Body)); + public IUpdate Where(string sql, object parms = null) { + if (string.IsNullOrEmpty(sql)) return this; + _where.Append(" AND (").Append(sql).Append(")"); + if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); + return this; + } + public IUpdate Where(T1 item) => this.Where(new[] { item }); + public IUpdate Where(IEnumerable items) => this.Where(_commonUtils.WhereItems(_table, "", items)); + public IUpdate WhereExists(ISelect select, bool notExists = false) where TEntity2 : class => this.Where($"{(notExists ? "NOT " : "")}EXISTS({select.ToSql("1")})"); + + protected abstract void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys); + protected abstract void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d); + + public string ToSql() { + if (_where.Length == 0) return null; + + var sb = new StringBuilder(); + sb.Append("UPDATE ").Append(_commonUtils.QuoteSqlName(_table.DbName)).Append(" SET "); + + if (_set.Length > 0) { //指定 set 更新 + sb.Append(_set.ToString().Substring(2)); + + } else if (_source.Count == 1) { //保存 Source + _paramsSource.Clear(); + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (col.Attribute.IsIdentity == false && _ignore.ContainsKey(col.CsName) == false) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = ").Append(_commonUtils.QuoteParamterName($"p_{_paramsSource.Count}")); + _commonUtils.AppendParamter(_paramsSource, null, _table.Properties.TryGetValue(col.CsName, out var tryp) ? tryp.GetValue(_source.First()) : DBNull.Value); + ++colidx; + } + } + if (colidx == 0) return null; + + } else if (_source.Count > 1) { //批量保存 Source + if (_table.Primarys.Any() == false) return null; + + var caseWhen = new StringBuilder(); + caseWhen.Append("CASE "); + ToSqlCase(caseWhen, _table.Primarys); + //if (_table.Primarys.Length > 1) caseWhen.Append("CONCAT("); + //var pkidx = 0; + //foreach (var pk in _table.Primarys) { + // if (pkidx > 0) caseWhen.Append(", "); + // caseWhen.Append(_commonUtils.QuoteSqlName(pk.Attribute.Name)); + // ++pkidx; + //} + //if (_table.Primarys.Length > 1) caseWhen.Append(")"); + var cw = caseWhen.Append(" ").ToString(); + + _paramsSource.Clear(); + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (col.Attribute.IsIdentity == false && _ignore.ContainsKey(col.CsName) == false) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = ").Append(cw); + foreach (var d in _source) { + sb.Append(" \r\nWHEN "); + ToSqlWhen(sb, _table.Primarys, d); + //if (_table.Primarys.Length > 1) sb.Append("CONCAT("); + //pkidx = 0; + //foreach (var pk in _table.Primarys) { + // if (pkidx > 0) sb.Append(", "); + // sb.Append(_commonUtils.FormatSql("{0}", _table.Properties.TryGetValue(pk.CsName, out var tryp2) ? tryp2.GetValue(d) : null)); + // ++pkidx; + //} + //if (_table.Primarys.Length > 1) sb.Append(")"); + sb.Append(" THEN ").Append(_commonUtils.QuoteParamterName($"p_{_paramsSource.Count}")); + _commonUtils.AppendParamter(_paramsSource, null, _table.Properties.TryGetValue(col.CsName, out var tryp) ? tryp.GetValue(d) : DBNull.Value); + } + sb.Append(" END"); + ++colidx; + } + } + if (colidx == 0) return null; + } else + return null; + + sb.Append(" \r\nWHERE ").Append(_where.ToString().Substring(5)); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonUtils.cs b/FreeSql/Internal/CommonUtils.cs new file mode 100644 index 00000000..8e6f118b --- /dev/null +++ b/FreeSql/Internal/CommonUtils.cs @@ -0,0 +1,119 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace FreeSql.Internal { + internal abstract class CommonUtils { + + internal abstract DbParameter[] GetDbParamtersByObject(string sql, object obj); + internal abstract DbParameter AppendParamter(List _params, string parameterName, object value); + internal abstract string FormatSql(string sql, params object[] args); + internal abstract string QuoteSqlName(string name); + internal abstract string QuoteParamterName(string name); + internal abstract string IsNull(string sql, object value); + + internal ICodeFirst CodeFirst { get; set; } + internal TableInfo GetTableByEntity(Type entity) => Utils.GetTableByEntity(entity, this); + + internal string WhereObject(TableInfo table, string aliasAndDot, object dywhere) { + if (dywhere == null) return ""; + var type = dywhere.GetType(); + var primarys = table.Columns.Values.Where(a => a.Attribute.IsPrimary).ToArray(); + if (primarys.Length == 1 && type == primarys.First().CsType) { + return $"{aliasAndDot}{this.QuoteSqlName(primarys.First().Attribute.Name)} = {this.FormatSql("{0}", dywhere)}"; + } else if (primarys.Length > 0 && type.FullName == table.Type.FullName) { + var sb = new StringBuilder(); + var pkidx = 0; + foreach (var pk in primarys) { + var prop = type.GetProperty(pk.CsName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); + if (pkidx > 0) sb.Append(" AND "); + sb.Append(aliasAndDot).Append(this.QuoteSqlName(pk.Attribute.Name)); + sb.Append(this.FormatSql(" = {0}", prop.GetValue(dywhere))); + ++pkidx; + } + return sb.ToString(); + } else if (dywhere is IEnumerable) { + var sb = new StringBuilder(); + var ie = dywhere as IEnumerable; + var ieidx = 0; + foreach (var i in ie) { + var fw = WhereObject(table, aliasAndDot, i); + if (string.IsNullOrEmpty(fw)) continue; + if (ieidx > 0) sb.Append(" OR "); + sb.Append(fw); + ++ieidx; + } + return sb.ToString(); + } else { + var sb = new StringBuilder(); + var ps = type.GetProperties(); + var psidx = 0; + foreach (var p in ps) { + if (table.Columns.TryGetValue(p.Name, out var trycol) == false) continue; + if (psidx > 0) sb.Append(" AND "); + sb.Append(aliasAndDot).Append(this.QuoteSqlName(trycol.Attribute.Name)); + sb.Append(this.FormatSql(" = {0}", p.GetValue(dywhere))); + ++psidx; + } + if (psidx == 0) return ""; + return sb.ToString(); + } + } + + internal string WhereItems(TableInfo table, string aliasAndDot, IEnumerable items) { + if (items == null || items.Any() == false) return null; + if (table.Primarys.Any() == false) return null; + var its = items.Where(a => a != null).ToArray(); + + if (table.Primarys.Length == 1) { + var sbin = new StringBuilder(); + sbin.Append(aliasAndDot).Append(this.QuoteSqlName(table.Primarys.First().Attribute.Name)); + var indt = its.Select(a => table.Properties.TryGetValue(table.Primarys.First().CsName, out var trycol) ? this.FormatSql("{0}", trycol.GetValue(a)) : null).Where(a => a != null).ToArray(); + if (indt.Any() == false) return null; + if (indt.Length == 1) sbin.Append(" = ").Append(indt.First()); + else sbin.Append(" IN (").Append(string.Join(",", indt)).Append(")"); + return sbin.ToString(); + } + var dicpk = its.Length > 5 ? new Dictionary() : null; + var sb = its.Length > 5 ? null : new StringBuilder(); + var iidx = 0; + foreach (var item in its) { + var filter = ""; + for (var a = 0; a < table.Primarys.Length; a++) { + if (table.Properties.TryGetValue(table.Primarys[a].CsName, out var trycol) == false) continue; + filter += $" AND {aliasAndDot}{this.QuoteSqlName(table.Primarys[a].Attribute.Name)} = {this.FormatSql("{0}", trycol.GetValue(item))}"; + } + if (string.IsNullOrEmpty(filter)) continue; + if (sb != null) { + sb.Append(" OR ("); + sb.Append(filter.Substring(5)); + sb.Append(")"); + ++iidx; + } + if (dicpk != null) { + filter = filter.Substring(5); + if (dicpk.ContainsKey(filter) == false) { + dicpk.Add(filter, true); + ++iidx; + } + } + //++iidx; + } + if (iidx == 0) return null; + if (sb == null) { + sb = new StringBuilder(); + foreach (var fil in dicpk) { + sb.Append(" OR ("); + sb.Append(fil.Key); + sb.Append(")"); + } + } + return iidx == 1 ? sb.Remove(0, 5).Remove(sb.Length - 1, 1).ToString() : sb.Remove(0, 4).ToString(); + } + } +} diff --git a/FreeSql/Internal/Model/ColumnInfo.cs b/FreeSql/Internal/Model/ColumnInfo.cs new file mode 100644 index 00000000..7d942cf1 --- /dev/null +++ b/FreeSql/Internal/Model/ColumnInfo.cs @@ -0,0 +1,11 @@ +using FreeSql.DataAnnotations; +using System; + +namespace FreeSql.Internal.Model { + class ColumnInfo { + public TableInfo Table { get; set; } + public string CsName { get; set; } + public Type CsType { get; set; } + public ColumnAttribute Attribute { get; set; } + } +} \ No newline at end of file diff --git a/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs b/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs new file mode 100644 index 00000000..34972f82 --- /dev/null +++ b/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace FreeSql.Internal.Model { + class ReadAnonymousTypeInfo { + public string CsName { get; set; } + public ConstructorInfo Consturctor { get; set; } + public ReadAnonymousTypeInfoConsturctorType ConsturctorType { get; set; } + public List Childs = new List(); + } + enum ReadAnonymousTypeInfoConsturctorType { Arguments, Properties } +} diff --git a/FreeSql/Internal/Model/SelectColumnInfo.cs b/FreeSql/Internal/Model/SelectColumnInfo.cs new file mode 100644 index 00000000..689fb319 --- /dev/null +++ b/FreeSql/Internal/Model/SelectColumnInfo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.Internal.Model { + class SelectColumnInfo { + public ColumnInfo Column { get; set; } + public SelectTableInfo Table { get; set; } + } +} diff --git a/FreeSql/Internal/Model/SelectTableInfo.cs b/FreeSql/Internal/Model/SelectTableInfo.cs new file mode 100644 index 00000000..7f7f12fe --- /dev/null +++ b/FreeSql/Internal/Model/SelectTableInfo.cs @@ -0,0 +1,9 @@ +namespace FreeSql.Internal.Model { + class SelectTableInfo { + public TableInfo Table { get; set; } + public string Alias { get; set; } + public string On { get; set; } + public SelectTableInfoType Type { get; set; } + } + enum SelectTableInfoType { From, LeftJoin, InnerJoin, RightJoin } +} diff --git a/FreeSql/Internal/Model/TableInfo.cs b/FreeSql/Internal/Model/TableInfo.cs new file mode 100644 index 00000000..4c597512 --- /dev/null +++ b/FreeSql/Internal/Model/TableInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace FreeSql.Internal.Model { + class TableInfo { + public Type Type { get; set; } + public Dictionary Properties { get; set; } = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + public Dictionary Columns { get; set; } = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + public Dictionary ColumnsByCs { get; set; } = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + public ColumnInfo[] Primarys { get; set; } + public string CsName { get; set; } + public string DbName { get; set; } + public string DbOldName { get; set; } + public string SelectFilter { get; set; } + public List> Uniques { get; set; } = new List>(); + } +} \ No newline at end of file diff --git a/FreeSql/Internal/Utils.cs b/FreeSql/Internal/Utils.cs new file mode 100644 index 00000000..76c46087 --- /dev/null +++ b/FreeSql/Internal/Utils.cs @@ -0,0 +1,144 @@ +using FreeSql.DataAnnotations; +using FreeSql.Internal.Model; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; + +namespace FreeSql.Internal { + class Utils { + + static ConcurrentDictionary _cacheGetTableByEntity = new ConcurrentDictionary(); + internal static TableInfo GetTableByEntity(Type entity, CommonUtils common) { + if (_cacheGetTableByEntity.TryGetValue(entity.FullName, out var trytb)) return trytb; + if (common.CodeFirst.GetDbInfo(entity) != null) return null; + + var tbattr = entity.GetCustomAttributes(typeof(TableAttribute), false).LastOrDefault() as TableAttribute; + trytb = new TableInfo(); + trytb.Type = entity; + trytb.Properties = entity.GetProperties().ToDictionary(a => a.Name, a => a, StringComparer.CurrentCultureIgnoreCase); + trytb.CsName = entity.Name; + trytb.DbName = tbattr?.Name ?? entity.Name; + trytb.DbOldName = tbattr?.OldName; + trytb.SelectFilter = tbattr?.SelectFilter; + foreach (var p in trytb.Properties.Values) { + var tp = common.CodeFirst.GetDbInfo(p.PropertyType); + if (tp == null) continue; + var colattr = p.GetCustomAttributes(typeof(ColumnAttribute), false).LastOrDefault() as ColumnAttribute ?? new ColumnAttribute { + Name = p.Name, + DbType = tp.Value.dbtypeFull, + IsIdentity = false, + IsNullable = tp.Value.isnullable ?? false, + IsPrimary = false, + }; + if (string.IsNullOrEmpty(colattr.Name)) colattr.Name = p.Name; + if (string.IsNullOrEmpty(colattr.DbType)) colattr.DbType = tp.Value.dbtypeFull; + if (colattr.DbType.IndexOf("NOT NULL") == -1 && tp.Value.isnullable == false) colattr.DbType += " NOT NULL"; + + var col = new ColumnInfo { + Table = trytb, + CsName = p.Name, + CsType = p.PropertyType, + Attribute = colattr + }; + trytb.Columns.Add(colattr.Name, col); + trytb.ColumnsByCs.Add(p.Name, col); + } + trytb.Primarys = trytb.Columns.Values.Where(a => a.Attribute.IsPrimary).ToArray(); + _cacheGetTableByEntity.TryAdd(entity.FullName, trytb); + return trytb; + } + + internal static T[] GetDbParamtersByObject(string sql, object obj, string paramPrefix, Func constructorParamter) { + if (string.IsNullOrEmpty(sql) || obj == null) return new T[0]; + var ttype = typeof(T); + var type = obj.GetType(); + if (type == ttype) return new[] { (T)Convert.ChangeType(obj, type) }; + var ret = new List(); + var ps = type.GetProperties(); + foreach (var p in ps) { + if (sql.IndexOf($"{paramPrefix}{p.Name}", StringComparison.CurrentCultureIgnoreCase) == -1) continue; + var pvalue = p.GetValue(obj); + if (p.PropertyType == ttype) ret.Add((T)Convert.ChangeType(pvalue, ttype)); + else ret.Add(constructorParamter(p.Name, p.PropertyType, pvalue)); + } + return ret.ToArray(); + } + + internal static (object value, int dataIndex) ExecuteArrayRowReadClassOrTuple(Type type, Dictionary names, object[] row, int dataIndex = 0) { + if (type.Namespace == "System" && (type.FullName == "System.String" || type.IsValueType)) { //值类型,或者元组 + bool isTuple = type.Name.StartsWith("ValueTuple`"); + if (isTuple) { + var fs = type.GetFields(); + var types = new Type[fs.Length]; + var parms = new object[fs.Length]; + for (int a = 0; a < fs.Length; a++) { + types[a] = fs[a].FieldType; + var read = ExecuteArrayRowReadClassOrTuple(types[a], names, row, dataIndex); + if (read.dataIndex > dataIndex) dataIndex = read.dataIndex; + parms[a] = read.value; + } + var constructor = type.GetConstructor(types); + return (constructor?.Invoke(parms), dataIndex); + } + return (dataIndex >= row.Length || row[dataIndex] == DBNull.Value ? null : Convert.ChangeType(row[dataIndex], type), dataIndex + 1); + } + if (type == typeof(object) && names != null) { + dynamic expando = new System.Dynamic.ExpandoObject(); //动态类型字段 可读可写 + var expandodic = (IDictionary)expando; + foreach (var name in names) + expandodic[Utils.GetCsName(name.Key)] = row[name.Value]; + return (expando, names.Count); + } + //类注入属性 + var consturct = type.GetConstructor(new Type[0]); + var value = consturct.Invoke(new object[0]); + var ps = type.GetProperties(); + foreach(var p in ps) { + var tryidx = dataIndex; + if (names != null && names.TryGetValue(p.Name, out tryidx) == false) continue; + var read = ExecuteArrayRowReadClassOrTuple(p.PropertyType, names, row, tryidx); + if (read.dataIndex > dataIndex) dataIndex = read.dataIndex; + FillPropertyValue(value, p.Name, read.value); + //p.SetValue(value, read.value); + } + return (value, dataIndex); + } + + internal static void FillPropertyValue(object info, string memberAccessPath, object value) { + var current = info; + PropertyInfo prop = null; + var members = memberAccessPath.Split('.'); + for (var a = 0; a < members.Length; a++) { + var type = current.GetType(); + prop = type.GetProperty(members[a], BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); + if (prop == null) throw new Exception(string.Concat(type.FullName, " 没有定义属性 ", members[a])); + if (a < members.Length - 1) current = prop.GetValue(current); + } + if (value == null || value == DBNull.Value) { + prop.SetValue(current, null, null); + return; + } + var propType = prop.PropertyType; + if (propType.FullName.StartsWith("System.Nullable`1[")) propType = propType.GenericTypeArguments.First(); + if (propType.IsEnum) { + var valueStr = string.Concat(value); + if (string.IsNullOrEmpty(valueStr) == false) prop.SetValue(current, Enum.Parse(propType, valueStr), null); + return; + } + if (propType != value.GetType()) { + prop.SetValue(current, Convert.ChangeType(value, propType), null); + return; + } + prop.SetValue(current, value, null); + } + internal static string GetCsName(string name) { + name = Regex.Replace(name.TrimStart('@'), @"[^\w]", "_"); + return char.IsLetter(name, 0) ? name : string.Concat("_", name); + } + } +} \ No newline at end of file diff --git a/FreeSql/MySql/Curd/MySqlDelete.cs b/FreeSql/MySql/Curd/MySqlDelete.cs new file mode 100644 index 00000000..37ff0a0a --- /dev/null +++ b/FreeSql/MySql/Curd/MySqlDelete.cs @@ -0,0 +1,25 @@ +using FreeSql.Internal; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.MySql.Curd { + + class MySqlDelete : Internal.CommonProvider.DeleteProvider where T1 : class { + public MySqlDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteDeleted() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/MySql/Curd/MySqlInsert.cs b/FreeSql/MySql/Curd/MySqlInsert.cs new file mode 100644 index 00000000..01176154 --- /dev/null +++ b/FreeSql/MySql/Curd/MySqlInsert.cs @@ -0,0 +1,28 @@ +using FreeSql.Internal; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace FreeSql.MySql.Curd { + + class MySqlInsert : Internal.CommonProvider.InsertProvider where T1 : class { + public MySqlInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) { + } + + public override long ExecuteIdentity() => int.TryParse(string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, string.Concat(this.ToSql(), "; SELECT LAST_INSERT_ID();"), _params)), out var trylng) ? trylng : 0; + + public override List ExecuteInserted() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/MySql/Curd/MySqlSelect.cs b/FreeSql/MySql/Curd/MySqlSelect.cs new file mode 100644 index 00000000..a37f1886 --- /dev/null +++ b/FreeSql/MySql/Curd/MySqlSelect.cs @@ -0,0 +1,114 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.MySql.Curd { + + class MySqlSelect : FreeSql.Internal.CommonProvider.Select1Provider where T1 : class { + + internal static string ToSqlStatic(CommonUtils _commonUtils, string _select, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List _tables) { + var sb = new StringBuilder(); + sb.Append(_select).Append(field).Append(" \r\nFROM "); + var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray(); + var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray(); + for (var a = 0; a < tbsfrom.Length; a++) { + sb.Append(_commonUtils.QuoteSqlName(tbsfrom[a].Table.DbName)).Append(" ").Append(tbsfrom[a].Alias); + if (tbsjoin.Length > 0) { + //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1 + for (var b = 1; b < tbsfrom.Length; b++) + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbsfrom[b].Table.DbName)).Append(" ").Append(tbsfrom[b].Alias).Append(" ON 1 = 1"); + break; + } + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + foreach (var tb in tbsjoin) { + switch (tb.Type) { + case SelectTableInfoType.LeftJoin: + sb.Append(" \r\nLEFT JOIN "); + break; + case SelectTableInfoType.InnerJoin: + sb.Append(" \r\nINNER JOIN "); + break; + case SelectTableInfoType.RightJoin: + sb.Append(" \r\nRIGHT JOIN "); + break; + } + sb.Append(_commonUtils.QuoteSqlName(tb.Table.DbName)).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On); + } + if (_join.Length > 0) sb.Append(_join); + + var sbqf = new StringBuilder(); + foreach (var tb in _tables) { + if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false) + sbqf.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")"); + } + if (_where.Length > 0) { + sb.Append(" \r\nWHERE ").Append(_where.ToString().Substring(5)); + if (sbqf.Length > 0) sb.Append(sbqf.ToString()); + } else { + if (sbqf.Length > 0) sb.Append(" \r\nWHERE ").Append(sbqf.Remove(0, 5)); + } + if (string.IsNullOrEmpty(_groupby) == false) { + sb.Append(_groupby); + if (string.IsNullOrEmpty(_having) == false) + sb.Append(" \r\nHAVING ").Append(_having.Substring(5)); + } + sb.Append(_orderby); + if (_skip > 0 || _limit > 0) + sb.Append(" \r\nlimit ").Append(Math.Max(0, _skip)).Append(",").Append(_limit > 0 ? _limit : -1); + + return sb.ToString(); + } + + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new MySqlSelect(_orm, _commonUtils, _commonExpression, null); MySqlSelect.CopyData(this, ret); return ret; } + public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select2Provider where T1 : class where T2 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select3Provider where T1 : class where T2 : class where T3 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select4Provider where T1 : class where T2 : class where T3 : class where T4 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select5Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select6Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select7Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select8Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select9Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class MySqlSelect : FreeSql.Internal.CommonProvider.Select10Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class { + public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => MySqlSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } +} diff --git a/FreeSql/MySql/Curd/MySqlUpdate.cs b/FreeSql/MySql/Curd/MySqlUpdate.cs new file mode 100644 index 00000000..0da7600e --- /dev/null +++ b/FreeSql/MySql/Curd/MySqlUpdate.cs @@ -0,0 +1,49 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.MySql.Curd { + + class MySqlUpdate : Internal.CommonProvider.UpdateProvider where T1 : class { + + public MySqlUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteUpdated() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) { + if (_table.Primarys.Length > 1) caseWhen.Append("CONCAT("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) caseWhen.Append(", "); + caseWhen.Append(_commonUtils.QuoteSqlName(pk.Attribute.Name)); + ++pkidx; + } + if (_table.Primarys.Length > 1) caseWhen.Append(")"); + } + + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) { + if (_table.Primarys.Length > 1) sb.Append("CONCAT("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) sb.Append(", "); + sb.Append(_commonUtils.FormatSql("{0}", _table.Properties.TryGetValue(pk.CsName, out var tryp2) ? tryp2.GetValue(d) : null)); + ++pkidx; + } + if (_table.Primarys.Length > 1) sb.Append(")"); + } + } +} diff --git a/FreeSql/MySql/MySqlAdo/MySqlAdo.cs b/FreeSql/MySql/MySqlAdo/MySqlAdo.cs new file mode 100644 index 00000000..ebcee181 --- /dev/null +++ b/FreeSql/MySql/MySqlAdo/MySqlAdo.cs @@ -0,0 +1,63 @@ +using FreeSql.Internal; +using Microsoft.Extensions.Logging; +using MySql.Data.MySqlClient; +using SafeObjectPool; +using System; +using System.Collections; +using System.Data.Common; +using System.Text; +using System.Threading; + +namespace FreeSql.MySql { + class MySqlAdo : FreeSql.Internal.CommonProvider.AdoProvider { + CommonUtils _util; + + public MySqlAdo() : base(null, null) { } + public MySqlAdo(CommonUtils util, ICache cache, ILogger log, string masterConnectionString, string[] slaveConnectionStrings) : base(cache, log) { + this._util = util; + MasterPool = new MySqlConnectionPool("主库", masterConnectionString, null, null); + if (slaveConnectionStrings != null) { + foreach (var slaveConnectionString in slaveConnectionStrings) { + var slavePool = new MySqlConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + } + } + } + public override object AddslashesProcessParam(object param) { + if (param == null) return "NULL"; + if (param is bool || param is bool?) + return (bool)param ? 1 : 0; + else if (param is string) + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (param is Enum) + return ((Enum)param).ToInt64(); + else if (decimal.TryParse(string.Concat(param), out var trydec)) + return param; + else if (param is DateTime) { + DateTime dt = (DateTime)param; + return string.Concat("'", dt.ToString("yyyy-MM-dd HH:mm:ss"), "'"); + } else if (param is DateTime?) { + DateTime? dt = param as DateTime?; + return string.Concat("'", dt.Value.ToString("yyyy-MM-dd HH:mm:ss"), "'"); + } else if (param is IEnumerable) { + var sb = new StringBuilder(); + var ie = param as IEnumerable; + foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z)); + return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString(); + } else { + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + //if (param is string) return string.Concat('N', nparms[a]); + } + } + + protected override DbCommand CreateCommand() { + return new MySqlCommand(); + } + + protected override void ReturnConnection(ObjectPool pool, Object conn, Exception ex) { + (pool as MySqlConnectionPool).Return(conn, ex); + } + + protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} diff --git a/FreeSql/MySql/MySqlAdo/MySqlAdoExtensions.cs b/FreeSql/MySql/MySqlAdo/MySqlAdoExtensions.cs new file mode 100644 index 00000000..14d0353e --- /dev/null +++ b/FreeSql/MySql/MySqlAdo/MySqlAdoExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Reflection; + +public static class MySqlAdoExtensions { + public static object GetEnum(this IDataReader dr, int index) { + string value = dr.GetString(index); + Type t = typeof(T); + foreach (var f in t.GetFields()) + if (f.GetCustomAttribute()?.Description == value || f.Name == value) return Enum.Parse(t, f.Name); + return null; + } + + public static string ToDescriptionOrString(this Enum item) { + string name = item.ToString(); + DescriptionAttribute desc = item.GetType().GetField(name)?.GetCustomAttribute(); + return desc?.Description ?? name; + } + public static long ToInt64(this Enum item) { + return Convert.ToInt64(item); + } + public static IEnumerable ToSet(this long value) { + List ret = new List(); + if (value == 0) return ret; + Type t = typeof(T); + foreach (FieldInfo f in t.GetFields()) { + if (f.FieldType != t) continue; + object o = Enum.Parse(t, f.Name); + long v = (long) o; + if ((value & v) == v) ret.Add((T) o); + } + return ret; + } +} \ No newline at end of file diff --git a/FreeSql/MySql/MySqlAdo/MySqlConnectionPool.cs b/FreeSql/MySql/MySqlAdo/MySqlConnectionPool.cs new file mode 100644 index 00000000..cc7f80cc --- /dev/null +++ b/FreeSql/MySql/MySqlAdo/MySqlConnectionPool.cs @@ -0,0 +1,125 @@ +using MySql.Data.MySqlClient; +using SafeObjectPool; +using System; +using System.Data; +using System.Data.Common; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.MySql { + + class MySqlConnectionPool : ObjectPool { + + internal Action availableHandler; + internal Action unavailableHandler; + + public MySqlConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) { + var policy = new MySqlConnectionPoolPolicy { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) { + if (exception != null && exception is MySqlException) { + try { if ((obj.Value as MySqlConnection).Ping() == false) obj.Value.Open(); } catch { base.SetUnavailable(exception); } + } + base.Return(obj, isRecreate); + } + } + + class MySqlConnectionPoolPolicy : IPolicy { + + internal MySqlConnectionPool _pool; + public string Name { get; set; } = "MySql MySqlConnection 对象池"; + public int PoolSize { get; set; } = 100; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 5; + + private string _connectionString; + public string ConnectionString { + get => _connectionString; + set { + _connectionString = value ?? ""; + Match m = Regex.Match(_connectionString, @"Max\s*pool\s*size\s*=\s*(\d+)", RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100; + PoolSize = poolsize; + + var initConns = new Object[poolsize]; + for (var a = 0; a < poolsize; a++) try { initConns[a] = _pool.Get(); } catch { } + foreach (var conn in initConns) _pool.Return(conn); + } + } + + + public bool OnCheckAvailable(Object obj) { + if ((obj.Value as MySqlConnection).Ping() == false) obj.Value.Open(); + return (obj.Value as MySqlConnection).Ping(); + } + + public DbConnection OnCreate() { + var conn = new MySqlConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGet(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (obj.Value as MySqlConnection).Ping() == false) { + + try { + obj.Value.Open(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + async public Task OnGetAsync(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (obj.Value as MySqlConnection).Ping() == false) { + + try { + await obj.Value.OpenAsync(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + public void OnGetTimeout() { + + } + + public void OnReturn(Object obj) { + + } + + public void OnAvailable() { + _pool.availableHandler?.Invoke(); + } + + public void OnUnavailable() { + _pool.unavailableHandler?.Invoke(); + } + } +} diff --git a/FreeSql/MySql/MySqlAdo/MygisTypes.cs b/FreeSql/MySql/MySqlAdo/MygisTypes.cs new file mode 100644 index 00000000..d30047be --- /dev/null +++ b/FreeSql/MySql/MySqlAdo/MygisTypes.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +public struct MygisCoordinate2D : IEquatable { + public double X { get; } + public double Y { get; } + public MygisCoordinate2D(double x, double y) { X = x; Y = y; } + + public bool Equals(MygisCoordinate2D c) => X == c.X && Y == c.Y; + public override int GetHashCode() => X.GetHashCode() ^ MygisGeometry.RotateShift(Y.GetHashCode(), sizeof(int) / 2); + public override bool Equals(object obj) => obj is MygisCoordinate2D && Equals((MygisCoordinate2D) obj); + public static bool operator ==(MygisCoordinate2D left, MygisCoordinate2D right) => Equals(left, right); + public static bool operator !=(MygisCoordinate2D left, MygisCoordinate2D right) => !Equals(left, right); +} + +public abstract class MygisGeometry { + protected abstract int GetLenHelper(); + internal int GetLen(bool includeSRID) => 5 + (SRID == 0 || !includeSRID ? 0 : 4) + GetLenHelper(); + public uint SRID { get; set; } = 0; + internal static int RotateShift(int val, int shift) => (val << shift) | (val >> (sizeof(int) - shift)); + public override string ToString() => this.AsText(); + public string AsText() { + if (this is MygisPoint) { + var obj = this as MygisPoint; + return $"POINT({obj.X} {obj.Y})"; + } + if (this is MygisLineString) { + var obj = this as MygisLineString; + return obj?.PointCount > 0 ? $"LINESTRING({string.Join(",", obj.Select(a => $"{a.X} {a.Y}"))})" : null; + } + if (this is MygisPolygon) { + var obj = (this as MygisPolygon).Where(z => z.Count() > 1 && z.First().Equals(z.Last())); + return obj.Any() ? $"POLYGON(({string.Join("),(", obj.Select(c => string.Join(",", c.Select(a => $"{a.X} {a.Y}"))))}))" : null; + } + if (this is MygisMultiPoint) { + var obj = this as MygisMultiPoint; + return obj?.PointCount > 0 ? $"MULTIPOINT({string.Join(",", obj.Select(a => $"{a.X} {a.Y}"))})" : null; + } + if (this is MygisMultiLineString) { + var obj = this as MygisMultiLineString; + return obj.LineCount > 0 ? $"MULTILINESTRING(({string.Join("),(", obj.Select(c => string.Join(",", c.Select(a => $"{a.X} {a.Y}"))))}))" : null; + } + if (this is MygisMultiPolygon) { + var obj = (this as MygisMultiPolygon)?.Where(z => z.Where(y => y.Count() > 1 && y.First().Equals(y.Last())).Any()); + return obj.Any() ? $"MULTIPOLYGON((({string.Join(")),((", obj.Select(d => string.Join("),(", d.Select(c => string.Join(",", c.Select(a => $"{a.X} {a.Y}"))))))})))" : null; + } + return base.ToString(); + } + static readonly Regex regexMygisPoint = new Regex(@"\s*(-?\d+\.?\d*)\s+(-?\d+\.?\d*)\s*"); + static readonly Regex regexSplit1 = new Regex(@"\)\s*,\s*\("); + static readonly Regex regexSplit2 = new Regex(@"\)\s*\)\s*,\s*\(\s*\("); + public static MygisGeometry Parse(string wkt) { + if (string.IsNullOrEmpty(wkt)) return null; + wkt = wkt.Trim(); + if (wkt.StartsWith("point", StringComparison.CurrentCultureIgnoreCase)) return ParsePoint(wkt.Substring(5).Trim('(', ')')); + else if (wkt.StartsWith("linestring", StringComparison.CurrentCultureIgnoreCase)) return new MygisLineString(ParseLineString(wkt.Substring(10).Trim('(', ')'))); + else if (wkt.StartsWith("polygon", StringComparison.CurrentCultureIgnoreCase)) return new MygisPolygon(ParsePolygon(wkt.Substring(7).Trim('(', ')'))); + else if (wkt.StartsWith("multipoint", StringComparison.CurrentCultureIgnoreCase)) return new MygisMultiPoint(ParseLineString(wkt.Substring(10).Trim('(', ')'))); + else if (wkt.StartsWith("multilinestring", StringComparison.CurrentCultureIgnoreCase)) return new MygisMultiLineString(ParseMultiLineString(wkt.Substring(15).Trim('(', ')'))); + else if (wkt.StartsWith("multipolygon", StringComparison.CurrentCultureIgnoreCase)) return new MygisMultiPolygon(ParseMultiPolygon(wkt.Substring(12).Trim('(', ')'))); + throw new NotImplementedException($"MygisGeometry.Parse 未现实 \"{wkt}\""); + } + static MygisPoint ParsePoint(string str) { + var m = regexMygisPoint.Match(str); + if (m.Success == false) return null; + return new MygisPoint(double.TryParse(m.Groups[1].Value, out var tryd) ? tryd : 0, double.TryParse(m.Groups[2].Value, out tryd) ? tryd : 0); + } + static MygisCoordinate2D[] ParseLineString(string str) { + var ms = regexMygisPoint.Matches(str); + var points = new MygisCoordinate2D[ms.Count]; + for (var a = 0; a < ms.Count; a++) points[a] = new MygisCoordinate2D(double.TryParse(ms[a].Groups[1].Value, out var tryd) ? tryd : 0, double.TryParse(ms[a].Groups[2].Value, out tryd) ? tryd : 0); + return points; + } + static MygisCoordinate2D[][] ParsePolygon(string str) { + return regexSplit1.Split(str).Select(s => ParseLineString(s)).Where(a => a.Length > 1 && a.First().Equals(a.Last())).ToArray(); + } + static MygisLineString[] ParseMultiLineString(string str) { + return regexSplit1.Split(str).Select(s => new MygisLineString(ParseLineString(s))).ToArray(); + } + static MygisPolygon[] ParseMultiPolygon(string str) { + return regexSplit2.Split(str).Select(s => new MygisPolygon(ParsePolygon(s))).ToArray(); + } +} + +public class MygisPoint : MygisGeometry, IEquatable { + MygisCoordinate2D _coord; + protected override int GetLenHelper() => 16; + public double X => _coord.X; + public double Y => _coord.Y; + + public MygisPoint(double x, double y) { + _coord = new MygisCoordinate2D(x, y); + } + + public bool Equals(MygisPoint other) => !ReferenceEquals(other, null) && _coord.Equals(other._coord); + public override bool Equals(object obj) => Equals(obj as MygisPoint); + public static bool operator ==(MygisPoint x, MygisPoint y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisPoint x, MygisPoint y) => !(x == y); + public override int GetHashCode() => X.GetHashCode() ^ RotateShift(Y.GetHashCode(), sizeof(int) / 2); +} + +public class MygisLineString : MygisGeometry, IEquatable, IEnumerable { + readonly MygisCoordinate2D[] _points; + protected override int GetLenHelper() => 4 + _points.Length * 16; + public IEnumerator GetEnumerator() => ((IEnumerable) _points).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public MygisCoordinate2D this[int index] => _points[index]; + public int PointCount => _points.Length; + + public MygisLineString(IEnumerable points) { + _points = points.ToArray(); + } + public MygisLineString(MygisCoordinate2D[] points) { + _points = points; + } + + public bool Equals(MygisLineString other) { + if (ReferenceEquals(other, null)) return false; + if (_points.Length != other._points.Length) return false; + for (var i = 0; i < _points.Length; i++) + if (!_points[i].Equals(other._points[i])) return false; + return true; + } + public override bool Equals(object obj) => Equals(obj as MygisLineString); + public static bool operator ==(MygisLineString x, MygisLineString y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisLineString x, MygisLineString y) => !(x == y); + public override int GetHashCode() { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + foreach (var t in _points) ret ^= RotateShift(t.GetHashCode(), ret % sizeof(int)); + return ret; + } +} + +public class MygisPolygon : MygisGeometry, IEquatable, IEnumerable> { + readonly MygisCoordinate2D[][] _rings; + protected override int GetLenHelper() => 4 + _rings.Length * 4 + TotalPointCount * 16; + public MygisCoordinate2D this[int ringIndex, int pointIndex] => _rings[ringIndex][pointIndex]; + public MygisCoordinate2D[] this[int ringIndex] => _rings[ringIndex]; + public IEnumerator> GetEnumerator() => ((IEnumerable>) _rings).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public int RingCount => _rings.Length; + public int TotalPointCount => _rings.Sum(r => r.Length); + + public MygisPolygon(MygisCoordinate2D[][] rings) { + _rings = rings; + } + public MygisPolygon(IEnumerable> rings) { + _rings = rings.Select(x => x.ToArray()).ToArray(); + } + + public bool Equals(MygisPolygon other) { + if (ReferenceEquals(other, null)) return false; + if (_rings.Length != other._rings.Length) return false; + for (var i = 0; i < _rings.Length; i++) { + if (_rings[i].Length != other._rings[i].Length) return false; + for (var j = 0; j < _rings[i].Length; j++) + if (!_rings[i][j].Equals(other._rings[i][j])) return false; + } + return true; + } + public override bool Equals(object obj) => Equals(obj as MygisPolygon); + public static bool operator ==(MygisPolygon x, MygisPolygon y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisPolygon x, MygisPolygon y) => !(x == y); + public override int GetHashCode() { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + for (var i = 0; i < _rings.Length; i++) + for (var j = 0; j < _rings[i].Length; j++) + ret ^= RotateShift(_rings[i][j].GetHashCode(), ret % sizeof(int)); + return ret; + } +} + +public class MygisMultiPoint : MygisGeometry, IEquatable, IEnumerable { + readonly MygisCoordinate2D[] _points; + protected override int GetLenHelper() => 4 + _points.Length * 21; + public IEnumerator GetEnumerator() => ((IEnumerable) _points).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public MygisCoordinate2D this[int indexer] => _points[indexer]; + public int PointCount => _points.Length; + + public MygisMultiPoint(MygisCoordinate2D[] points) { + _points = points; + } + public MygisMultiPoint(IEnumerable points) { + _points = points.Select(x => new MygisCoordinate2D(x.X, x.Y)).ToArray(); + } + public MygisMultiPoint(IEnumerable points) { + _points = points.ToArray(); + } + + public bool Equals(MygisMultiPoint other) { + if (ReferenceEquals(other, null)) return false; + if (_points.Length != other._points.Length) return false; + for (var i = 0; i < _points.Length; i++) + if (!_points[i].Equals(other._points[i])) return false; + return true; + } + public override bool Equals(object obj) => Equals(obj as MygisMultiPoint); + public static bool operator ==(MygisMultiPoint x, MygisMultiPoint y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisMultiPoint x, MygisMultiPoint y) => !(x == y); + public override int GetHashCode() { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + for (var i = 0; i < _points.Length; i++) ret ^= RotateShift(_points[i].GetHashCode(), ret % sizeof(int)); + return ret; + } +} + +public sealed class MygisMultiLineString : MygisGeometry, + IEquatable, IEnumerable { + readonly MygisLineString[] _lineStrings; + protected override int GetLenHelper() { + var n = 4; + for (var i = 0; i < _lineStrings.Length; i++) n += _lineStrings[i].GetLen(false); + return n; + } + public IEnumerator GetEnumerator() => ((IEnumerable) _lineStrings).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public MygisLineString this[int index] => _lineStrings[index]; + public int LineCount => _lineStrings.Length; + + internal MygisMultiLineString(MygisCoordinate2D[][] pointArray) { + _lineStrings = new MygisLineString[pointArray.Length]; + for (var i = 0; i < pointArray.Length; i++) + _lineStrings[i] = new MygisLineString(pointArray[i]); + } + public MygisMultiLineString(MygisLineString[] linestrings) { + _lineStrings = linestrings; + } + public MygisMultiLineString(IEnumerable linestrings) { + _lineStrings = linestrings.ToArray(); + } + public MygisMultiLineString(IEnumerable> pointList) { + _lineStrings = pointList.Select(x => new MygisLineString(x)).ToArray(); + } + + public bool Equals(MygisMultiLineString other) { + if (ReferenceEquals(other, null)) return false; + if (_lineStrings.Length != other._lineStrings.Length) return false; + for (var i = 0; i < _lineStrings.Length; i++) + if (_lineStrings[i] != other._lineStrings[i]) return false; + return true; + } + public override bool Equals(object obj) => Equals(obj as MygisMultiLineString); + public static bool operator ==(MygisMultiLineString x, MygisMultiLineString y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisMultiLineString x, MygisMultiLineString y) => !(x == y); + public override int GetHashCode() { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + for (var i = 0; i < _lineStrings.Length; i++) ret ^= RotateShift(_lineStrings[i].GetHashCode(), ret % sizeof(int)); + return ret; + } +} + +public class MygisMultiPolygon : MygisGeometry, IEquatable, IEnumerable { + readonly MygisPolygon[] _polygons; + protected override int GetLenHelper() { + var n = 4; + for (var i = 0; i < _polygons.Length; i++) n += _polygons[i].GetLen(false); + return n; + } + public IEnumerator GetEnumerator() => ((IEnumerable) _polygons).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public MygisPolygon this[int index] => _polygons[index]; + public int PolygonCount => _polygons.Length; + + public MygisMultiPolygon(MygisPolygon[] polygons) { + _polygons = polygons; + } + public MygisMultiPolygon(IEnumerable polygons) { + _polygons = polygons.ToArray(); + } + public MygisMultiPolygon(IEnumerable>> ringList) { + _polygons = ringList.Select(x => new MygisPolygon(x)).ToArray(); + } + + public bool Equals(MygisMultiPolygon other) { + if (ReferenceEquals(other, null)) return false; + if (_polygons.Length != other._polygons.Length) return false; + for (var i = 0; i < _polygons.Length; i++) if (_polygons[i] != other._polygons[i]) return false; + return true; + } + public override bool Equals(object obj) => obj is MygisMultiPolygon && Equals((MygisMultiPolygon) obj); + public static bool operator ==(MygisMultiPolygon x, MygisMultiPolygon y) => ReferenceEquals(x, null) ? ReferenceEquals(y, null) : x.Equals(y); + public static bool operator !=(MygisMultiPolygon x, MygisMultiPolygon y) => !(x == y); + public override int GetHashCode() { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + for (var i = 0; i < _polygons.Length; i++) ret ^= RotateShift(_polygons[i].GetHashCode(), ret % sizeof(int)); + return ret; + } +} + +public static partial class MygisTypesExtensions { + /// + /// 测量两个经纬度的距离,返回单位:米 + /// + /// 经纬坐标1 + /// 经纬坐标2 + /// 返回距离(单位:米) + public static double Distance(this MygisPoint that, MygisPoint point) { + double radLat1 = (double)(that.Y) * Math.PI / 180d; + double radLng1 = (double)(that.X) * Math.PI / 180d; + double radLat2 = (double)(point.Y) * Math.PI / 180d; + double radLng2 = (double)(point.X) * Math.PI / 180d; + return 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin((radLat1 - radLat2) / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin((radLng1 - radLng2) / 2), 2))) * 6378137; + } +} \ No newline at end of file diff --git a/FreeSql/MySql/MySqlCodeFirst.cs b/FreeSql/MySql/MySqlCodeFirst.cs new file mode 100644 index 00000000..602da7fa --- /dev/null +++ b/FreeSql/MySql/MySqlCodeFirst.cs @@ -0,0 +1,184 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.MySql { + + class MySqlCodeFirst : ICodeFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public MySqlCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public bool IsAutoSyncStructure { get; set; } = true; + + static object _dicCsToDbLock = new object(); + static Dictionary _dicCsToDb = new Dictionary() { + { typeof(bool).FullName, (MySqlDbType.Bit, "bit","bit(1) NOT NULL", null, false) },{ typeof(bool?).FullName, (MySqlDbType.Bit, "bit","bit(1)", null, true) }, + + { typeof(sbyte).FullName, (MySqlDbType.Byte, "tinyint", "tinyint(3) NOT NULL", false, false) },{ typeof(sbyte?).FullName, (MySqlDbType.Byte, "tinyint", "tinyint(3)", false, true) }, + { typeof(short).FullName, (MySqlDbType.Int16, "smallint","smallint(6) NOT NULL", false, false) },{ typeof(short?).FullName, (MySqlDbType.Int16, "smallint", "smallint(6)", false, true) }, + { typeof(int).FullName, (MySqlDbType.Int32, "int", "int(11) NOT NULL", false, false) },{ typeof(int?).FullName, (MySqlDbType.Int32, "int", "int(11)", false, true) }, + { typeof(long).FullName, (MySqlDbType.Int64, "bigint","bigint(20) NOT NULL", false, false) },{ typeof(long?).FullName, (MySqlDbType.Int64, "bigint","bigint(20)", false, true) }, + + { typeof(byte).FullName, (MySqlDbType.UByte, "tinyint","tinyint(3) unsigned NOT NULL", true, false) },{ typeof(byte?).FullName, (MySqlDbType.UByte, "tinyint","tinyint(3) unsigned", true, true) }, + { typeof(ushort).FullName, (MySqlDbType.UInt16, "smallint","smallint(5) unsigned NOT NULL", true, false) },{ typeof(ushort?).FullName, (MySqlDbType.UInt16, "smallint", "smallint(5) unsigned", true, true) }, + { typeof(uint).FullName, (MySqlDbType.UInt32, "int", "int(10) unsigned NOT NULL", true, false) },{ typeof(uint?).FullName, (MySqlDbType.UInt32, "int", "int(10) unsigned", true, true) }, + { typeof(ulong).FullName, (MySqlDbType.UInt64, "bigint", "bigint(20) unsigned NOT NULL", true, false) },{ typeof(ulong?).FullName, (MySqlDbType.UInt64, "bigint", "bigint(20) unsigned", true, true) }, + + { typeof(double).FullName, (MySqlDbType.Double, "double", "double NOT NULL", false, false) },{ typeof(double?).FullName, (MySqlDbType.Double, "double", "double", false, true) }, + { typeof(float).FullName, (MySqlDbType.Float, "float","float NOT NULL", false, false) },{ typeof(float?).FullName, (MySqlDbType.Float, "float","float", false, true) }, + { typeof(decimal).FullName, (MySqlDbType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false) },{ typeof(decimal?).FullName, (MySqlDbType.Decimal, "decimal", "decimal(10,2)", false, true) }, + + { typeof(TimeSpan).FullName, (MySqlDbType.Time, "time","time NOT NULL", false, false) },{ typeof(TimeSpan?).FullName, (MySqlDbType.Time, "time", "time",false, true) }, + { typeof(DateTime).FullName, (MySqlDbType.DateTime, "datetime", "datetime NOT NULL", false, false) },{ typeof(DateTime?).FullName, (MySqlDbType.DateTime, "datetime", "datetime", false, true) }, + + { typeof(byte[]).FullName, (MySqlDbType.VarBinary, "varbinary", "varbinary(255)", false, null) }, + { typeof(string).FullName, (MySqlDbType.VarChar, "varchar", "varchar(255)", false, null) }, + + { typeof(Guid).FullName, (MySqlDbType.VarChar, "char", "char(36)", false, false) },{ typeof(Guid?).FullName, (MySqlDbType.VarChar, "char", "char(36)", false, true) }, + + { typeof(MygisPoint).FullName, (MySqlDbType.Geometry, "point", "point", false, null) }, + { typeof(MygisLineString).FullName, (MySqlDbType.Geometry, "linestring", "linestring", false, null) }, + { typeof(MygisPolygon).FullName, (MySqlDbType.Geometry, "polygon", "polygon", false, null) }, + { typeof(MygisMultiPoint).FullName, (MySqlDbType.Geometry, "multipoint","multipoint", false, null) }, + { typeof(MygisMultiLineString).FullName, (MySqlDbType.Geometry, "multilinestring","multilinestring", false, null) }, + { typeof(MygisMultiPolygon).FullName, (MySqlDbType.Geometry, "multipolygon", "multipolygon", false, null) }, + }; + + public (int type, string dbtype, string dbtypeFull, bool? isnullable)? GetDbInfo(Type type) { + if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new (int, string, string, bool?)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable)); + var enumType = type.IsEnum ? type : null; + if (enumType == null && type.FullName.StartsWith("System.Nullable`1[") && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First(); + if (enumType != null) { + var names = string.Join(",", Enum.GetNames(enumType).Select(a => _commonUtils.FormatSql("{0}", a))); + var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ? + (MySqlDbType.Set, "set", $"set({names}){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true) : + (MySqlDbType.Enum, "enum", $"enum({names}){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true); + if (_dicCsToDb.ContainsKey(type.FullName) == false) { + lock (_dicCsToDbLock) { + if (_dicCsToDb.ContainsKey(type.FullName) == false) + _dicCsToDb.Add(type.FullName, newItem); + } + } + return ((int)newItem.Item1, newItem.Item2, newItem.Item3, newItem.Item5); + } + return null; + } + + public string GetComparisonDDLStatements() => this.GetComparisonDDLStatements(typeof(TEntity)); + public string GetComparisonDDLStatements(params Type[] entityTypes) { + string database = ""; + using (var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5))) { + database = conn.Value.Database; + } + var sb = new StringBuilder(); + foreach (var entityType in entityTypes) { + if (sb.Length > 0) sb.Append("\r\n"); + var tb = _commonUtils.GetTableByEntity(entityType); + var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名 + if (tboldname?.Length == 1) tboldname = new[] { database, tboldname[0] }; + + var isRenameTable = false; + var tbname = tb.DbName.Split(new[] { '.' }, 2); + if (tbname.Length == 1) tbname = new[] { database, tbname[0] }; + if (_orm.Ado.ExecuteScalar(CommandType.Text, "SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}".FormatMySql(tbname)) == null) { //表不存在 + + if (tboldname != null && _orm.Ado.ExecuteScalar(CommandType.Text, "SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}".FormatMySql(tboldname)) != null) { //旧表存在 + //修改表名 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}")).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(";\r\n"); + isRenameTable = true; + + } else { + //创建表 + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ("); + foreach (var tbcol in tb.Columns.Values) { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" "); + sb.Append(tbcol.Attribute.DbType.ToUpper()); + if (tbcol.Attribute.IsIdentity && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + sb.Append(","); + } + if (tb.Primarys.Any() == false) + sb.Remove(sb.Length - 1, 1); + else { + sb.Append(" \r\n PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append(")"); + } + sb.Append("\r\n) Engine=InnoDB CHARACTER SET utf8;\r\n"); + continue; + } + } + //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 + var addcols = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + foreach (var tbcol in tb.Columns) addcols.Add(tbcol.Value.Attribute.Name, tbcol.Value); + var surplus = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var dbcols = new List(); + var sql = @"select +a.column_name, +a.column_type, +case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', +case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity' +from information_schema.columns a +where a.table_schema in ({0}) and a.table_name in ({1})".FormatMySql(isRenameTable ? tboldname : tbname); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + foreach (var row in ds) { + string column = string.Concat(row[0]); + string sqlType = string.Concat(row[1]); + bool is_nullable = string.Concat(row[2]) == "1"; + bool is_identity = string.Concat(row[3]) == "1"; + bool is_unsigned = sqlType.EndsWith(" unsigned"); + + if (addcols.TryGetValue(column, out var trycol)) { + if ((trycol.Attribute.DbType.IndexOf(" unsigned", StringComparison.CurrentCultureIgnoreCase) != -1) != is_unsigned || + Regex.Replace(trycol.Attribute.DbType, @"\([^\)]+\)", m => Regex.Replace(m.Groups[0].Value, @"\s", "")).StartsWith(sqlType, StringComparison.CurrentCultureIgnoreCase) == false || + (trycol.Attribute.DbType.IndexOf("NOT NULL", StringComparison.CurrentCultureIgnoreCase) == -1) != is_nullable || + trycol.Attribute.IsIdentity != is_identity) { + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(column)).Append(" ").Append(trycol.Attribute.DbType.ToUpper()); + if (trycol.Attribute.IsIdentity && trycol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + } + addcols.Remove(column); + } else + surplus.Add(column, true); //记录剩余字段 + } + foreach (var addcol in addcols.Values) { + if (string.IsNullOrEmpty(addcol.Attribute.OldName) == false && surplus.ContainsKey(addcol.Attribute.OldName)) { //修改列名 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" CHANGE COLUMN ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.OldName)).Append(" ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(" ").Append(addcol.Attribute.DbType.ToUpper()); + if (addcol.Attribute.IsIdentity && addcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + + } else { //添加列 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(" ").Append(addcol.Attribute.DbType.ToUpper()); + if (addcol.Attribute.IsIdentity && addcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + } + } + } + return sb.Length == 0 ? null : sb.ToString(); + } + + public bool SyncStructure() => this.SyncStructure(typeof(TEntity)); + public bool SyncStructure(params Type[] entityTypes) { + var ddl = this.GetComparisonDDLStatements(entityTypes); + if (string.IsNullOrEmpty(ddl)) return true; + try { + return _orm.Ado.ExecuteNonQuery(CommandType.Text, ddl) > 0; + } catch { + return false; + } + } + + } +} \ No newline at end of file diff --git a/FreeSql/MySql/MySqlDbFirst.cs b/FreeSql/MySql/MySqlDbFirst.cs new file mode 100644 index 00000000..942c52ba --- /dev/null +++ b/FreeSql/MySql/MySqlDbFirst.cs @@ -0,0 +1,367 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; + +namespace FreeSql.MySql { + class MySqlDbFirst : IDbFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public MySqlDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public int GetDbType(DbColumnInfo column) => (int)GetMySqlDbType(column); + MySqlDbType GetMySqlDbType(DbColumnInfo column) { + var is_unsigned = column.DbTypeTextFull.ToLower().EndsWith(" unsigned"); + switch (column.DbTypeText.ToLower()) { + case "bit": return MySqlDbType.Bit; + + case "tinyint": return is_unsigned ? MySqlDbType.UByte : MySqlDbType.Byte; + case "smallint": return is_unsigned ? MySqlDbType.UInt16 : MySqlDbType.Int16; + case "mediumint": return is_unsigned ? MySqlDbType.UInt24 : MySqlDbType.Int24; + case "int": return is_unsigned ? MySqlDbType.UInt32 : MySqlDbType.Int32; + case "bigint": return is_unsigned ? MySqlDbType.UInt64 : MySqlDbType.Int64; + + case "real": + case "double": return MySqlDbType.Double; + case "float": return MySqlDbType.Float; + case "numeric": + case "decimal": return MySqlDbType.Decimal; + + case "year": return MySqlDbType.Year; + case "time": return MySqlDbType.Time; + case "date": return MySqlDbType.Date; + case "timestamp": return MySqlDbType.Timestamp; + case "datetime": return MySqlDbType.DateTime; + + case "tinyblob": return MySqlDbType.TinyBlob; + case "blob": return MySqlDbType.Blob; + case "mediumblob": return MySqlDbType.MediumBlob; + case "longblob": return MySqlDbType.LongBlob; + + case "binary": return MySqlDbType.Binary; + case "varbinary": return MySqlDbType.VarBinary; + + case "tinytext": return MySqlDbType.TinyText; + case "text": return MySqlDbType.Text; + case "mediumtext": return MySqlDbType.MediumText; + case "longtext": return MySqlDbType.LongText; + + case "char": return column.MaxLength == 36 ? MySqlDbType.Guid : MySqlDbType.String; + case "varchar": return MySqlDbType.VarChar; + + case "set": return MySqlDbType.Set; + case "enum": return MySqlDbType.Enum; + + case "point": return MySqlDbType.Geometry; + case "linestring": return MySqlDbType.Geometry; + case "polygon": return MySqlDbType.Geometry; + case "geometry": return MySqlDbType.Geometry; + case "multipoint": return MySqlDbType.Geometry; + case "multilinestring": return MySqlDbType.Geometry; + case "multipolygon": return MySqlDbType.Geometry; + case "geometrycollection": return MySqlDbType.Geometry; + default: return MySqlDbType.String; + } + } + + static readonly Dictionary _dicDbToCs = new Dictionary() { + { (int)MySqlDbType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") }, + + { (int)MySqlDbType.Byte, ("(sbyte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetByte") }, + { (int)MySqlDbType.Int16, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") }, + { (int)MySqlDbType.Int24, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Int32, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Int64, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") }, + + { (int)MySqlDbType.UByte, ("(byte?)", "byte.Parse({0})", "{0}.ToString()", "byte?", typeof(byte), typeof(byte?), "{0}.Value", "GetByte") }, + { (int)MySqlDbType.UInt16, ("(ushort?)", "ushort.Parse({0})", "{0}.ToString()", "ushort?", typeof(ushort), typeof(ushort?), "{0}.Value", "GetInt16") }, + { (int)MySqlDbType.UInt24, ("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.UInt32, ("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.UInt64, ("(ulong?)", "ulong.Parse({0})", "{0}.ToString()", "ulong?", typeof(ulong), typeof(ulong?), "{0}.Value", "GetInt64") }, + + { (int)MySqlDbType.Double, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, + { (int)MySqlDbType.Float, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + { (int)MySqlDbType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + + { (int)MySqlDbType.Year, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Time, ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") }, + { (int)MySqlDbType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)MySqlDbType.Timestamp, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)MySqlDbType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + + { (int)MySqlDbType.TinyBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.Blob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.MediumBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.LongBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)MySqlDbType.Binary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)MySqlDbType.TinyText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.MediumText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.LongText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)MySqlDbType.Guid, ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid?", typeof(Guid), typeof(Guid?), "{0}", "GetString") }, + { (int)MySqlDbType.String, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.VarString, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)MySqlDbType.Set, ("(long?)", "long.Parse({0})", "{0}.ToInt64().ToString()", "Set", typeof(bool), typeof(Enum), "{0}", "GetInt64") }, + { (int)MySqlDbType.Enum, ("(long?)", "long.Parse({0})", "{0}.ToInt64().ToString()", "Enum", typeof(bool), typeof(Enum), "{0}", "GetInt64") }, + + { (int)MySqlDbType.Geometry, ("(MygisGeometry)", "MygisGeometry.Parse({0}.Replace(StringifySplit, \"|\"))", "{0}.AsText().Replace(\"|\", StringifySplit)", "MygisGeometry", typeof(MygisGeometry), typeof(MygisGeometry), "{0}", "GetString") }, + }; + + public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null; + public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null; + public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + + public List GetDatabases() { + var sql = @"select schema_name from information_schema.schemata where schema_name not in ('information_schema', 'mysql', 'performance_schema')"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList(); + } + + public List GetTablesByDatabase(params string[] database) { + var loc1 = new List(); + var loc2 = new Dictionary(); + var loc3 = new Dictionary>(); + + if (database == null || database.Any() == false) return loc1; + var databaseIn = string.Join(",", database.Select(a => "{0}".FormatMySql(a))); + var sql = string.Format(@"select +concat(a.table_schema, '.', a.table_name) 'id', +a.table_schema 'schema', +a.table_name 'table', +a.table_type 'type' +from information_schema.tables a +where a.table_schema in ({0})", databaseIn); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var loc6 = new List(); + var loc66 = new List(); + foreach (var row in ds) { + var table_id = string.Concat(row[0]); + var schema = string.Concat(row[1]); + var table = string.Concat(row[2]); + var type = string.Concat(row[3]) == "VIEW" ? DbTableType.VIEW : DbTableType.TABLE; + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + schema = ""; + } + loc2.Add(table_id, new DbTableInfo { Id = table_id, Schema = schema, Name = table, Type = type }); + loc3.Add(table_id, new Dictionary()); + switch (type) { + case DbTableType.TABLE: + case DbTableType.VIEW: + loc6.Add(table.Replace("'", "''")); + break; + case DbTableType.StoreProcedure: + loc66.Add(table.Replace("'", "''")); + break; + } + } + if (loc6.Count == 0) return loc1; + var loc8 = "'" + string.Join("','", loc6.ToArray()) + "'"; + var loc88 = "'" + string.Join("','", loc66.ToArray()) + "'"; + + sql = string.Format(@"select +concat(a.table_schema, '.', a.table_name), +a.column_name, +a.data_type, +ifnull(a.character_maximum_length, 0) 'len', +a.column_type, +case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', +case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity', +a.column_comment 'comment' +from information_schema.columns a +where a.table_schema in ({1}) and a.table_name in ({0}) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string type = string.Concat(row[2]); + //long max_length = long.Parse(string.Concat(row[3])); + string sqlType = string.Concat(row[4]); + var m_len = Regex.Match(sqlType, @"\w+\((\d+)"); + int max_length = m_len.Success ? int.Parse(m_len.Groups[1].Value) : -1; + bool is_nullable = string.Concat(row[5]) == "1"; + bool is_identity = string.Concat(row[6]) == "1"; + string comment = string.Concat(row[7]); + if (max_length == 0) max_length = -1; + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + } + loc3[table_id].Add(column, new DbColumnInfo { + Name = column, + MaxLength = max_length, + IsIdentity = is_identity, + IsNullable = is_nullable, + IsPrimary = false, + DbTypeText = type, + DbTypeTextFull = sqlType, + Table = loc2[table_id], + Coment = comment + }); + loc3[table_id][column].DbType = this.GetDbType(loc3[table_id][column]); + loc3[table_id][column].CsType = this.GetCsTypeInfo(loc3[table_id][column]); + } + + sql = string.Format(@"select +concat(a.constraint_schema, '.', a.table_name) 'table_id', +a.column_name, +concat(a.constraint_schema, '/', a.table_name, '/', a.constraint_name) 'index_id', +1 'IsUnique', +case when constraint_name = 'PRIMARY' then 1 else 0 end 'IsPrimaryKey', +0 'IsClustered', +0 'IsDesc' +from information_schema.key_column_usage a +where a.constraint_schema in ({1}) and a.table_name in ({0}) and isnull(position_in_unique_constraint) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var indexColumns = new Dictionary>>(); + var uniqueColumns = new Dictionary>>(); + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string index_id = string.Concat(row[2]); + bool is_unique = string.Concat(row[3]) == "1"; + bool is_primary_key = string.Concat(row[4]) == "1"; + bool is_clustered = string.Concat(row[5]) == "1"; + int is_desc = int.Parse(string.Concat(row[6])); + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + } + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key; + + Dictionary> loc10 = null; + List loc11 = null; + if (!indexColumns.TryGetValue(table_id, out loc10)) + indexColumns.Add(table_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + if (is_unique) { + if (!uniqueColumns.TryGetValue(table_id, out loc10)) + uniqueColumns.Add(table_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + } + } + foreach (string table_id in indexColumns.Keys) { + foreach (var columns in indexColumns[table_id].Values) + loc2[table_id].Indexes.Add(columns); + } + foreach (string table_id in uniqueColumns.Keys) { + foreach (var columns in uniqueColumns[table_id].Values) { + columns.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc2[table_id].Uniques.Add(columns); + } + } + + sql = string.Format(@"select +concat(a.constraint_schema, '.', a.table_name) 'table_id', +a.column_name, +concat(a.constraint_schema, '/', a.constraint_name) 'FKId', +concat(a.referenced_table_schema, '.', a.referenced_table_name) 'ref_table_id', +1 'IsForeignKey', +a.referenced_column_name 'ref_column' +from information_schema.key_column_usage a +where a.constraint_schema in ({1}) and a.table_name in ({0}) and not isnull(position_in_unique_constraint) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var fkColumns = new Dictionary>(); + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string fk_id = string.Concat(row[2]); + string ref_table_id = string.Concat(row[3]); + bool is_foreign_key = string.Concat(row[4]) == "1"; + string referenced_column = string.Concat(row[5]); + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + ref_table_id = ref_table_id.Substring(ref_table_id.IndexOf('.') + 1); + } + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc2.ContainsKey(ref_table_id) == false) continue; + var loc10 = loc2[ref_table_id]; + var loc11 = loc3[ref_table_id][referenced_column]; + + Dictionary loc12 = null; + DbForeignInfo loc13 = null; + if (!fkColumns.TryGetValue(table_id, out loc12)) + fkColumns.Add(table_id, loc12 = new Dictionary()); + if (!loc12.TryGetValue(fk_id, out loc13)) + loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc10 }); + loc13.Columns.Add(loc9); + loc13.ReferencedColumns.Add(loc11); + } + foreach (var table_id in fkColumns.Keys) + foreach (var fk in fkColumns[table_id].Values) + loc2[table_id].Foreigns.Add(fk); + + foreach (var table_id in loc3.Keys) { + foreach (var loc5 in loc3[table_id].Values) { + loc2[table_id].Columns.Add(loc5); + if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5); + if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5); + } + } + foreach (var loc4 in loc2.Values) { + if (loc4.Primarys.Count == 0 && loc4.Uniques.Count > 0) { + foreach (var loc5 in loc4.Uniques[0]) { + loc5.IsPrimary = true; + loc4.Primarys.Add(loc5); + } + } + loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc4.Columns.Sort((c1, c2) => { + int compare = c2.IsPrimary.CompareTo(c1.IsPrimary); + if (compare == 0) { + bool b1 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c1.Name) != null) != null; + bool b2 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c2.Name) != null) != null; + compare = b2.CompareTo(b1); + } + if (compare == 0) compare = c1.Name.CompareTo(c2.Name); + return compare; + }); + loc1.Add(loc4); + } + loc1.Sort((t1, t2) => { + var ret = t1.Schema.CompareTo(t2.Schema); + if (ret == 0) ret = t1.Name.CompareTo(t2.Name); + return ret; + }); + + loc2.Clear(); + loc3.Clear(); + return loc1; + } + } +} \ No newline at end of file diff --git a/FreeSql/MySql/MySqlExpression.cs b/FreeSql/MySql/MySqlExpression.cs new file mode 100644 index 00000000..ffc782f4 --- /dev/null +++ b/FreeSql/MySql/MySqlExpression.cs @@ -0,0 +1,159 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.MySql { + class MySqlExpression : CommonExpression { + + public MySqlExpression(CommonUtils common) : base(common) { } + + internal override string ExpressionLambdaToSqlCall(MethodCallExpression exp, List _tables, List _selectColumnMap, SelectTableInfoType tbtype, bool isQuoteName) { + if (exp.Object.Type.FullName == "System.String") { + var left = ExpressionLambdaToSql(exp.Object, _tables, _selectColumnMap, tbtype, isQuoteName); + switch (exp.Method.Name) { + case "StartsWith": + case "EndsWith": + case "Contains": + var args0Value = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (args0Value == "NULL") return $"({left}) IS NULL"; + if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"concat('%', {args0Value})")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"concat({args0Value}, '%')")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) like concat('%', {args0Value}, '%')"; + case "ToLower": return $"lower({left})"; + case "ToUpper": return $"upper({left})"; + case "Substring": return $"substr({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)} + 1, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Length": return $"char_length({left})"; + case "IndexOf": + var indexOfFindStr = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"(locate({left}, {indexOfFindStr}, ParseLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName) + 1) - 1)"; + return $"(locate({left}, {indexOfFindStr}) - 1)"; + case "PadLeft": return $"lpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PadRight": return $"rpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Trim": + case "TrimStart": + case "TrimEnd": + if (exp.Arguments.Count == 0) { + if (exp.Method.Name == "Trim") return $"trim({left})"; + if (exp.Method.Name == "TrimStart") return $"ltrim({left})"; + if (exp.Method.Name == "TrimStart") return $"rtrim({left})"; + } + foreach (var argsTrim01 in exp.Arguments) { + if (exp.Method.Name == "Trim") left = $"trim({ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(leading {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(trailing {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + } + return left; + case "Replace": return $"replace({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "CompareTo": return $"strcmp({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + } + } + + if (exp.Object.Type.FullName == "System.Math") { + switch (exp.Method.Name) { + case "Abs": return $"abs({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sign": return $"sign({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Floor": return $"floor({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Round": + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Exp": return $"exp({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log": return $"log({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log10": return $"log10({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Pow": return $"pow({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sqrt": return $"sqrt({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PI": return $"pi()"; + case "Cos": return $"cos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sin": return $"sin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Tan": return $"tan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Acos": return $"acos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Asin": return $"asin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan": return $"atan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan2": return $"atan2({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Truncate": return $"truncate({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, 0)"; + } + } + + //dayofweek = DayOfWeek + //dayofmonth = Day + //dayofyear = DayOfYear + //month = Month + //year = Year + //hour = Hour + //minute = Minute + //second = Second + /* + * date_add(date,interval expr type) + date_sub(date,interval expr type) + adddate(date,interval expr type) + subdate(date,interval expr type) + 对日期时间进行加减法运算 + (adddate()和subdate()是date_add()和date_sub()的同义词,也 + 可以用运算符+和-而不是函数 + date是一个datetime或date值,expr对date进行加减法的一个表 + 达式字符串type指明表达式expr应该如何被解释 +  [type值 含义 期望的expr格式]: +  second 秒 seconds +  minute 分钟 minutes +  hour 时间 hours +  day 天 days +  month 月 months +  year 年 years +  minute_second 分钟和秒 "minutes:seconds" +  hour_minute 小时和分钟 "hours:minutes" +  day_hour 天和小时 "days hours" +  year_month 年和月 "years-months" +  hour_second 小时, 分钟, "hours:minutes:seconds" +  day_minute 天, 小时, 分钟 "days hours:minutes" +  day_second 天, 小时, 分钟, 秒 "days + hours:minutes:seconds" + expr中允许任何标点做分隔符,如果所有是date值时结果是一个 +date值,否则结果是一个datetime值) + 如果type关键词不完整,则mysql从右端取值,day_second因为缺 +少小时分钟等于minute_second) + 如果增加month、year_month或year,天数大于结果月份的最大天 +数则使用最大天数) +mysql> select "1997-12-31 23:59:59" + interval 1 second; + +  -> 1998-01-01 00:00:00 +mysql> select interval 1 day + "1997-12-31"; +  -> 1998-01-01 +mysql> select "1998-01-01" - interval 1 second; +  -> 1997-12-31 23:59:59 +mysql> select date_add("1997-12-31 23:59:59",interval 1 +second); +  -> 1998-01-01 00:00:00 +mysql> select date_add("1997-12-31 23:59:59",interval 1 +day); +  -> 1998-01-01 23:59:59 +mysql> select date_add("1997-12-31 23:59:59",interval +"1:1" minute_second); +  -> 1998-01-01 00:01:00 +mysql> select date_sub("1998-01-01 00:00:00",interval "1 +1:1:1" day_second); +  -> 1997-12-30 22:58:59 +mysql> select date_add("1998-01-01 00:00:00", interval "-1 +10" day_hour); +  -> 1997-12-30 14:00:00 +mysql> select date_sub("1998-01-02", interval 31 day); +  -> 1997-12-02 +mysql> select extract(year from "1999-07-02"); +  -> 1999 +mysql> select extract(year_month from "1999-07-02 +01:02:03"); +  -> 199907 +mysql> select extract(day_minute from "1999-07-02 +01:02:03"); +  -> 20102 + */ + + //convert + var xxx = DateTime.Now.ToString(""); + + + throw new Exception($"MySqlExpression 未现实函数表达式 {exp} 解析"); + } + } +} diff --git a/FreeSql/MySql/MySqlProvider.cs b/FreeSql/MySql/MySqlProvider.cs new file mode 100644 index 00000000..50a942eb --- /dev/null +++ b/FreeSql/MySql/MySqlProvider.cs @@ -0,0 +1,50 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.MySql.Curd; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; + +namespace FreeSql.MySql { + + class MySqlProvider : IFreeSql { + + public ISelect Select() where T1 : class => new MySqlSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public ISelect Select(object dywhere) where T1 : class => new MySqlSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IInsert Insert() where T1 : class => new MySqlInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + public IInsert Insert(T1 source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(IEnumerable source) where T1 : class => this.Insert().AppendData(source); + public IUpdate Update() where T1 : class => new MySqlUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IUpdate Update(object dywhere) where T1 : class => new MySqlUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IDelete Delete() where T1 : class => new MySqlDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IDelete Delete(object dywhere) where T1 : class => new MySqlDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public IAdo Ado { get; } + public ICache Cache { get; } + public ICodeFirst CodeFirst { get; } + public IDbFirst DbFirst { get; } + public MySqlProvider(IDistributedCache cache, IConfiguration cacheStrategy, string masterConnectionString, string[] slaveConnectionString, ILogger log) { + CacheStrategy = cacheStrategy; + if (log == null) log = new LoggerFactory(new[] { new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() }).CreateLogger("FreeSql.MySql"); + + this.InternalCommonUtils = new MySqlUtils(this); + this.InternalCommonExpression = new MySqlExpression(this.InternalCommonUtils); + + this.Cache = new CacheProvider(cache, log); + this.Ado = new MySqlAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + + this.DbFirst = new MySqlDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.InternalCommonUtils.CodeFirst = this.CodeFirst = new MySqlCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + } + + internal CommonUtils InternalCommonUtils { get; } + internal CommonExpression InternalCommonExpression { get; } + internal IConfiguration CacheStrategy { get; private set; } + + public void Transaction(Action handler) => Ado.Transaction(handler); + + public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout); + } +} diff --git a/FreeSql/MySql/MySqlUtils.cs b/FreeSql/MySql/MySqlUtils.cs new file mode 100644 index 00000000..6fc0f3aa --- /dev/null +++ b/FreeSql/MySql/MySqlUtils.cs @@ -0,0 +1,48 @@ +using FreeSql.Internal; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Data.Common; + +namespace FreeSql.MySql { + + class MySqlUtils : CommonUtils { + IFreeSql _orm; + public MySqlUtils(IFreeSql mysql) { + _orm = mysql; + } + + internal override DbParameter AppendParamter(List _params, string parameterName, object value) { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + MySqlParameter ret = null; + if (value == null) ret = new MySqlParameter { ParameterName = $"{parameterName}", Value = DBNull.Value }; + else { + var type = value.GetType(); + ret = new MySqlParameter { + ParameterName = parameterName, + Value = value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.MySqlDbType = (MySqlDbType)tp.Value; + } + _params?.Add(ret); + return ret; + } + + internal override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, "?", (name, type, value) => { + var cp = new MySqlParameter { + ParameterName = name, + Value = value ?? DBNull.Value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) cp.MySqlDbType = (MySqlDbType)tp.Value; + return cp; + }); + + internal override string FormatSql(string sql, params object[] args) => sql?.FormatMySql(args); + internal override string QuoteSqlName(string name) => $"`{name.Trim('`').Replace(".", "`.`")}`"; + internal override string QuoteParamterName(string name) => $"?{name}"; + internal override string IsNull(string sql, object value) => $"ifnull({sql}, {value})"; + } +} diff --git a/FreeSql/PostgreSQL/Curd/PostgreSQLDelete.cs b/FreeSql/PostgreSQL/Curd/PostgreSQLDelete.cs new file mode 100644 index 00000000..4473fab8 --- /dev/null +++ b/FreeSql/PostgreSQL/Curd/PostgreSQLDelete.cs @@ -0,0 +1,25 @@ +using FreeSql.Internal; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.PostgreSQL.Curd { + + class PostgreSQLDelete : Internal.CommonProvider.DeleteProvider where T1 : class { + public PostgreSQLDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteDeleted() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/PostgreSQL/Curd/PostgreSQLInsert.cs b/FreeSql/PostgreSQL/Curd/PostgreSQLInsert.cs new file mode 100644 index 00000000..470fa434 --- /dev/null +++ b/FreeSql/PostgreSQL/Curd/PostgreSQLInsert.cs @@ -0,0 +1,30 @@ +using FreeSql.Internal; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; + +namespace FreeSql.PostgreSQL.Curd { + + class PostgreSQLInsert : Internal.CommonProvider.InsertProvider where T1 : class { + public PostgreSQLInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) { + } + + public override long ExecuteIdentity() => + int.TryParse(string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, string.Concat(this.ToSql(), " RETURNING ", _commonUtils.QuoteSqlName(_table.Columns.Where(a => a.Value.Attribute.IsIdentity).FirstOrDefault().Value.Attribute.Name)), _params)), out var trylng) ? trylng : 0; + + public override List ExecuteInserted() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/PostgreSQL/Curd/PostgreSQLSelect.cs b/FreeSql/PostgreSQL/Curd/PostgreSQLSelect.cs new file mode 100644 index 00000000..e74bdc7a --- /dev/null +++ b/FreeSql/PostgreSQL/Curd/PostgreSQLSelect.cs @@ -0,0 +1,116 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.PostgreSQL.Curd { + + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select1Provider where T1 : class { + + internal static string ToSqlStatic(CommonUtils _commonUtils, string _select, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List _tables) { + var sb = new StringBuilder(); + sb.Append(_select).Append(field).Append(" \r\nFROM "); + var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray(); + var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray(); + for (var a = 0; a < tbsfrom.Length; a++) { + sb.Append(_commonUtils.QuoteSqlName(tbsfrom[a].Table.DbName)).Append(" ").Append(tbsfrom[a].Alias); + if (tbsjoin.Length > 0) { + //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1 + for (var b = 1; b < tbsfrom.Length; b++) + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbsfrom[b].Table.DbName)).Append(" ").Append(tbsfrom[b].Alias).Append(" ON 1 = 1"); + break; + } + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + foreach (var tb in tbsjoin) { + switch (tb.Type) { + case SelectTableInfoType.LeftJoin: + sb.Append(" \r\nLEFT JOIN "); + break; + case SelectTableInfoType.InnerJoin: + sb.Append(" \r\nINNER JOIN "); + break; + case SelectTableInfoType.RightJoin: + sb.Append(" \r\nRIGHT JOIN "); + break; + } + sb.Append(_commonUtils.QuoteSqlName(tb.Table.DbName)).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On); + } + if (_join.Length > 0) sb.Append(_join); + + var sbqf = new StringBuilder(); + foreach (var tb in _tables) { + if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false) + sbqf.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")"); + } + if (_where.Length > 0) { + sb.Append(" \r\nWHERE ").Append(_where.ToString().Substring(5)); + if (sbqf.Length > 0) sb.Append(sbqf.ToString()); + } else { + if (sbqf.Length > 0) sb.Append(" \r\nWHERE ").Append(sbqf.Remove(0, 5)); + } + if (string.IsNullOrEmpty(_groupby) == false) { + sb.Append(_groupby); + if (string.IsNullOrEmpty(_having) == false) + sb.Append(" \r\nHAVING ").Append(_having.Substring(5)); + } + sb.Append(_orderby); + if (_limit > 0) + sb.Append(" \r\nlimit ").Append(_limit); + if (_skip > 0) + sb.Append(" \r\noffset ").Append(_skip); + + return sb.ToString(); + } + + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new PostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); PostgreSQLSelect.CopyData(this, ret); return ret; } + public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select2Provider where T1 : class where T2 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select3Provider where T1 : class where T2 : class where T3 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select4Provider where T1 : class where T2 : class where T3 : class where T4 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select5Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select6Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select7Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select8Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select9Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class PostgreSQLSelect : FreeSql.Internal.CommonProvider.Select10Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class { + public PostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => PostgreSQLSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } +} diff --git a/FreeSql/PostgreSQL/Curd/PostgreSQLUpdate.cs b/FreeSql/PostgreSQL/Curd/PostgreSQLUpdate.cs new file mode 100644 index 00000000..c1eb1d4a --- /dev/null +++ b/FreeSql/PostgreSQL/Curd/PostgreSQLUpdate.cs @@ -0,0 +1,49 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.PostgreSQL.Curd { + + class PostgreSQLUpdate : Internal.CommonProvider.UpdateProvider where T1 : class { + + public PostgreSQLUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteUpdated() { + var sb = new StringBuilder(); + sb.Append(this.ToSql()).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + return _orm.Ado.Query(sb.ToString()); + } + + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) { + if (_table.Primarys.Length > 1) caseWhen.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) caseWhen.Append(", "); + caseWhen.Append(_commonUtils.QuoteSqlName(pk.Attribute.Name)).Append("::varchar"); + ++pkidx; + } + if (_table.Primarys.Length > 1) caseWhen.Append(")"); + } + + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) { + if (_table.Primarys.Length > 1) sb.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) sb.Append(", "); + sb.Append(_commonUtils.FormatSql("{0}", _table.Properties.TryGetValue(pk.CsName, out var tryp2) ? tryp2.GetValue(d) : null)).Append("::varchar"); + ++pkidx; + } + if (_table.Primarys.Length > 1) sb.Append(")"); + } + } +} diff --git a/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLAdo.cs b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLAdo.cs new file mode 100644 index 00000000..9967b07a --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLAdo.cs @@ -0,0 +1,61 @@ +using FreeSql.Internal; +using Microsoft.Extensions.Logging; +using Npgsql; +using SafeObjectPool; +using System; +using System.Collections; +using System.Data.Common; +using System.Text; +using System.Threading; + +namespace FreeSql.PostgreSQL { + class PostgreSQLAdo : FreeSql.Internal.CommonProvider.AdoProvider { + CommonUtils _util; + + public PostgreSQLAdo() : base(null, null) { } + public PostgreSQLAdo(CommonUtils util, ICache cache, ILogger log, string masterConnectionString, string[] slaveConnectionStrings) : base(cache, log) { + this._util = util; + MasterPool = new PostgreSQLConnectionPool("主库", masterConnectionString, null, null); + if (slaveConnectionStrings != null) { + foreach (var slaveConnectionString in slaveConnectionStrings) { + var slavePool = new PostgreSQLConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + } + } + } + public override object AddslashesProcessParam(object param) { + if (param == null) return "NULL"; + if (param is bool || param is bool?) + return (bool)param ? "'t'" : "'f'"; + else if (param is string || param is Enum) + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (decimal.TryParse(string.Concat(param), out var trydec)) + return param; + else if (param is DateTime) { + DateTime dt = (DateTime)param; + return string.Concat("'", dt.ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "'"); + } else if (param is DateTime?) { + DateTime? dt = param as DateTime?; + return string.Concat("'", dt.Value.ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "'"); + } else if (param is IEnumerable) { + var sb = new StringBuilder(); + var ie = param as IEnumerable; + foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z)); + return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString(); + } else { + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + //if (param is string) return string.Concat('N', nparms[a]); + } + } + + protected override DbCommand CreateCommand() { + return new NpgsqlCommand(); + } + + protected override void ReturnConnection(ObjectPool pool, Object conn, Exception ex) { + (pool as PostgreSQLConnectionPool).Return(conn, ex); + } + + protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} \ No newline at end of file diff --git a/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLConnectionPool.cs b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLConnectionPool.cs new file mode 100644 index 00000000..7aca8408 --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLConnectionPool.cs @@ -0,0 +1,153 @@ +using Npgsql; +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.PostgreSQL { + + public class PostgreSQLConnectionPool : ObjectPool { + + internal Action availableHandler; + internal Action unavailableHandler; + + public PostgreSQLConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) { + var policy = new PostgreSQLConnectionPoolPolicy { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) { + if (exception != null && exception is NpgsqlException) { + + if (exception is System.IO.IOException) { + + base.SetUnavailable(exception); + + } else if (obj.Value.Ping() == false) { + + base.SetUnavailable(exception); + } + } + base.Return(obj, isRecreate); + } + } + + public class PostgreSQLConnectionPoolPolicy : IPolicy { + + internal PostgreSQLConnectionPool _pool; + public string Name { get; set; } = "PostgreSQL NpgsqlConnection 对象池"; + public int PoolSize { get; set; } = 100; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 5; + + private string _connectionString; + public string ConnectionString { + get => _connectionString; + set { + _connectionString = value ?? ""; + Match m = Regex.Match(_connectionString, @"Maximum\s*pool\s*size\s*=\s*(\d+)", RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100; + PoolSize = poolsize; + + var initConns = new Object[poolsize]; + for (var a = 0; a < poolsize; a++) try { initConns[a] = _pool.Get(); } catch { } + foreach (var conn in initConns) _pool.Return(conn); + } + } + + + public bool OnCheckAvailable(Object obj) { + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + var cmd = obj.Value.CreateCommand(); + cmd.CommandText = "select 1"; + cmd.ExecuteNonQuery(); + return true; + } + + public DbConnection OnCreate() { + var conn = new NpgsqlConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGet(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) { + + try { + obj.Value.Open(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + async public Task OnGetAsync(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) { + + try { + await obj.Value.OpenAsync(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + public void OnGetTimeout() { + + } + + public void OnReturn(Object obj) { + + } + + public void OnAvailable() { + _pool.availableHandler?.Invoke(); + } + + public void OnUnavailable() { + _pool.unavailableHandler?.Invoke(); + } + } + + public static class DbConnectionExtensions { + + public static bool Ping(this DbConnection that) { + try { + var cmd = that.CreateCommand(); + cmd.CommandText = "select 1"; + cmd.ExecuteNonQuery(); + return true; + } catch { + try { that.Close(); } catch { } + return false; + } + } + } +} diff --git a/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesConverter.cs b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesConverter.cs new file mode 100644 index 00000000..cd0fc698 --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesConverter.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json.Linq; +using Npgsql; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; + +namespace Newtonsoft.Json { + public class PostgreSQLTypesConverter : JsonConverter { + private static readonly Type typeof_BitArray = typeof(BitArray); + + private static readonly Type typeof_NpgsqlPoint = typeof(NpgsqlPoint); + private static readonly Type typeof_NpgsqlLine = typeof(NpgsqlLine); + private static readonly Type typeof_NpgsqlLSeg = typeof(NpgsqlLSeg); + private static readonly Type typeof_NpgsqlBox = typeof(NpgsqlBox); + private static readonly Type typeof_NpgsqlPath = typeof(NpgsqlPath); + private static readonly Type typeof_NpgsqlPolygon = typeof(NpgsqlPolygon); + private static readonly Type typeof_NpgsqlCircle = typeof(NpgsqlCircle); + + private static readonly Type typeof_Cidr = typeof((IPAddress, int)); + private static readonly Type typeof_IPAddress = typeof(IPAddress); + private static readonly Type typeof_PhysicalAddress = typeof(PhysicalAddress); + + private static readonly Type typeof_String = typeof(string); + + private static readonly Type typeof_NpgsqlRange_int = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_long = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_decimal = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_DateTime = typeof(NpgsqlRange); + public override bool CanConvert(Type objectType) { + Type ctype = objectType.IsArray ? objectType.GetElementType() : objectType; + var ctypeGenericType1 = ctype.GenericTypeArguments.FirstOrDefault(); + + if (ctype == typeof_BitArray) return true; + + if (ctype == typeof_NpgsqlPoint || ctypeGenericType1 == typeof_NpgsqlPoint) return true; + if (ctype == typeof_NpgsqlLine || ctypeGenericType1 == typeof_NpgsqlLine) return true; + if (ctype == typeof_NpgsqlLSeg || ctypeGenericType1 == typeof_NpgsqlLSeg) return true; + if (ctype == typeof_NpgsqlBox || ctypeGenericType1 == typeof_NpgsqlBox) return true; + if (ctype == typeof_NpgsqlPath || ctypeGenericType1 == typeof_NpgsqlPath) return true; + if (ctype == typeof_NpgsqlPolygon || ctypeGenericType1 == typeof_NpgsqlPolygon) return true; + if (ctype == typeof_NpgsqlCircle || ctypeGenericType1 == typeof_NpgsqlCircle) return true; + + if (ctype == typeof_Cidr || ctypeGenericType1 == typeof_Cidr) return true; + if (ctype == typeof_IPAddress) return true; + if (ctype == typeof_PhysicalAddress) return true; + + if (ctype == typeof_NpgsqlRange_int || ctypeGenericType1 == typeof_NpgsqlRange_int) return true; + if (ctype == typeof_NpgsqlRange_long || ctypeGenericType1 == typeof_NpgsqlRange_long) return true; + if (ctype == typeof_NpgsqlRange_decimal || ctypeGenericType1 == typeof_NpgsqlRange_decimal) return true; + if (ctype == typeof_NpgsqlRange_DateTime || ctypeGenericType1 == typeof_NpgsqlRange_DateTime) return true; + + return false; + } + private object YieldJToken(Type ctype, JToken jt, int rank) { + if (jt.Type == JTokenType.Null) return null; + if (rank == 0) { + var ctypeGenericType1 = ctype.GenericTypeArguments.FirstOrDefault();//ctype.Namespace == "System" && ctype.Name.StartsWith("Nullable`") ? ctype.GenericTypeArguments.FirstOrDefault() : null; + if (ctype == typeof_BitArray) return jt.ToString().ToBitArray(); + + if (ctype == typeof_NpgsqlPoint || ctypeGenericType1 == typeof_NpgsqlPoint) return NpgsqlPoint.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlLine || ctypeGenericType1 == typeof_NpgsqlLine) return NpgsqlLine.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlLSeg || ctypeGenericType1 == typeof_NpgsqlLSeg) return NpgsqlLSeg.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlBox || ctypeGenericType1 == typeof_NpgsqlBox) return NpgsqlBox.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlPath || ctypeGenericType1 == typeof_NpgsqlPath) return NpgsqlPath.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlPolygon || ctypeGenericType1 == typeof_NpgsqlPolygon) return NpgsqlPolygon.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlCircle || ctypeGenericType1 == typeof_NpgsqlCircle) return NpgsqlCircle.Parse(jt.ToString()); + + if (ctype == typeof_Cidr || ctypeGenericType1 == typeof_Cidr) { + var cidrArgs = jt.ToString().Split(new[] { '/' }, 2); + return (IPAddress.Parse(cidrArgs.First()), cidrArgs.Length >= 2 ? int.TryParse(cidrArgs[1], out var tryCdirSubnet) ? tryCdirSubnet : 0 : 0); + } + if (ctype == typeof_IPAddress) return IPAddress.Parse(jt.ToString()); + if (ctype == typeof_PhysicalAddress) return PhysicalAddress.Parse(jt.ToString()); + + if (ctype == typeof_NpgsqlRange_int || ctypeGenericType1 == typeof_NpgsqlRange_int) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_long || ctypeGenericType1 == typeof_NpgsqlRange_long) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_decimal || ctypeGenericType1 == typeof_NpgsqlRange_decimal) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_DateTime || ctypeGenericType1 == typeof_NpgsqlRange_DateTime) return jt.ToString().ToNpgsqlRange(); + + return null; + } + + var jtarr = jt.ToArray(); + var ret = Array.CreateInstance(ctype, jtarr.Length); + var jtarrIdx = 0; + foreach (var a in jtarr) { + var t2 = YieldJToken(ctype, a, rank - 1); + ret.SetValue(t2, jtarrIdx++); + } + return ret; + } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + int rank = objectType.IsArray ? objectType.GetArrayRank() : 0; + Type ctype = objectType.IsArray ? objectType.GetElementType() : objectType; + + var ret = YieldJToken(ctype, JToken.Load(reader), rank); + if (ret != null && rank > 0) return ret; + return ret; + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + Type objectType = value.GetType(); + if (objectType.IsArray) { + int rank = objectType.GetArrayRank(); + int[] indices = new int[rank]; + GetJObject(value as Array, indices, 0).WriteTo(writer); + } else + GetJObject(value).WriteTo(writer); + } + public static JToken GetJObject(object value) { + if (value is BitArray) return JToken.FromObject((value as BitArray)?.To1010()); + if (value is IPAddress) return JToken.FromObject((value as IPAddress)?.ToString()); + if (value is ValueTuple || value is ValueTuple?) { + ValueTuple? cidrValue = (ValueTuple?)value; + return JToken.FromObject(cidrValue == null ? null : $"{cidrValue.Value.Item1.ToString()}/{cidrValue.Value.Item2.ToString()}"); + } + return JToken.FromObject(value?.ToString()); + } + public static JToken GetJObject(Array value, int[] indices, int idx) { + if (idx == indices.Length) { + return GetJObject(value.GetValue(indices)); + } + JArray ja = new JArray(); + if (indices.Length == 1) { + foreach(object a in value) + ja.Add(GetJObject(a)); + return ja; + } + int lb = value.GetLowerBound(idx); + int ub = value.GetUpperBound(idx); + for (int b = lb; b <= ub; b++) { + indices[idx] = b; + ja.Add(GetJObject(value, indices, idx + 1)); + } + return ja; + } + } +} \ No newline at end of file diff --git a/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesExtensions.cs b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesExtensions.cs new file mode 100644 index 00000000..a09009a7 --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLAdo/PostgreSQLTypesExtensions.cs @@ -0,0 +1,18 @@ +using NpgsqlTypes; +using System; + +public static partial class PostgreSQLTypesExtensions { + /// + /// 测量两个经纬度的距离,返回单位:米 + /// + /// 经纬坐标1 + /// 经纬坐标2 + /// 返回距离(单位:米) + public static double Distance(this NpgsqlPoint that, NpgsqlPoint point) { + double radLat1 = (double)(that.Y) * Math.PI / 180d; + double radLng1 = (double)(that.X) * Math.PI / 180d; + double radLat2 = (double)(point.Y) * Math.PI / 180d; + double radLng2 = (double)(point.X) * Math.PI / 180d; + return 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin((radLat1 - radLat2) / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin((radLng1 - radLng2) / 2), 2))) * 6378137; + } +} \ No newline at end of file diff --git a/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs b/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs new file mode 100644 index 00000000..668979e6 --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs @@ -0,0 +1,188 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; + +namespace FreeSql.PostgreSQL { + + class PostgreSQLCodeFirst : ICodeFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public PostgreSQLCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public bool IsAutoSyncStructure { get; set; } = true; + + static readonly Dictionary _dicCsToDb = new Dictionary() { + + { "System.Int16", (NpgsqlDbType.Smallint, "int2","int2 NOT NULL", false, false) },{ "System.Nullable`1[System.Int16]", (NpgsqlDbType.Smallint, "int2", "int2", false, true) }, + { "System.Int32", (NpgsqlDbType.Integer, "int4","int4 NOT NULL", false, false) },{ "System.Nullable`1[System.Int32]", (NpgsqlDbType.Integer, "int4", "int4", false, true) }, + { "System.Int64", (NpgsqlDbType.Bigint, "int8","int8 NOT NULL", false, false) },{ "System.Nullable`1[System.Int64]", (NpgsqlDbType.Bigint, "int8", "int8", false, true) }, + + { "System.Single", (NpgsqlDbType.Real, "float4","float4 NOT NULL", false, false) },{ "System.Nullable`1[System.Single]", (NpgsqlDbType.Real, "float4", "float4", false, true) }, + { "System.Double", (NpgsqlDbType.Double, "float8","float8 NOT NULL", false, false) },{ "System.Nullable`1[System.Double]", (NpgsqlDbType.Double, "float8", "float8", false, true) }, + { "System.Decimal", (NpgsqlDbType.Numeric, "numeric", "numeric(10,2) NOT NULL", false, false) },{ "System.Nullable`1[System.Decimal]", (NpgsqlDbType.Numeric, "numeric", "numeric(10,2)", false, true) }, + + { "System.String", (NpgsqlDbType.Varchar, "varchar", "varchar(255)", false, null) }, + + { "System.TimeSpan", (NpgsqlDbType.Time, "time","time NOT NULL", false, false) },{ "System.Nullable`1[System.TimeSpan]", (NpgsqlDbType.Time, "time", "time",false, true) }, + { "System.DateTime", (NpgsqlDbType.Timestamp, "timestamp", "timestamp NOT NULL", false, false) },{ "System.Nullable`1[System.DateTime]", (NpgsqlDbType.Timestamp, "timestamp", "timestamp", false, true) }, + + { "System.Boolean", (NpgsqlDbType.Boolean, "bool","bool NOT NULL", null, false) },{ "System.Nullable`1[System.Boolean]", (NpgsqlDbType.Bit, "bool","bool", null, true) }, + { "System.Byte[]", (NpgsqlDbType.Bytea, "bytea", "bytea", false, null) }, + { "System.BitArray", (NpgsqlDbType.Varbit, "varbit", "varbit(255)", false, null) }, + + { "NpgsqlTypes.NpgsqlPoint", (NpgsqlDbType.Point, "point", "point", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlPoint]", (NpgsqlDbType.Point, "point", "point", false, true) }, + { "NpgsqlTypes.NpgsqlLine", (NpgsqlDbType.Line, "line", "line", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlLine]", (NpgsqlDbType.Line, "line", "line", false, true) }, + { "NpgsqlTypes.NpgsqlLSeg", (NpgsqlDbType.LSeg, "lseg", "lseg", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlLSeg]", (NpgsqlDbType.LSeg, "lseg", "lseg", false, true) }, + { "NpgsqlTypes.NpgsqlBox", (NpgsqlDbType.Box, "box", "box", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlBox]", (NpgsqlDbType.Box, "box", "box", false, true) }, + { "NpgsqlTypes.NpgsqlPath", (NpgsqlDbType.Path, "path", "path", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlPath]", (NpgsqlDbType.Path, "path", "path", false, true) }, + { "NpgsqlTypes.NpgsqlPolygon", (NpgsqlDbType.Polygon, "polygon", "polygon", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlPolygon]", (NpgsqlDbType.Polygon, "polygon", "polygon", false, true) }, + { "NpgsqlTypes.NpgsqlCircle", (NpgsqlDbType.Circle, "circle", "circle", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlCircle]", (NpgsqlDbType.Circle, "circle", "circle", false, true) }, + + { "System.ValueTuple`2[[System.Net.IPAddress, System.Int32]]", (NpgsqlDbType.Cidr, "cidr", "cidr", false, false) },{ "System.Nullable`1[System.ValueTuple`2[[System.Net.IPAddress, System.Int32]]]", (NpgsqlDbType.Cidr, "cidr", "cidr", false, true) }, + { "System.Net.IPAddress", (NpgsqlDbType.Inet, "inet", "inet", false, null) }, + { "System.Net.NetworkInformation.PhysicalAddress", (NpgsqlDbType.MacAddr, "macaddr", "macaddr", false, null) }, + + { "Newtonsoft.Json.Linq.JToken", (NpgsqlDbType.Jsonb, "jsonb", "jsonb", false, null) }, + { "Newtonsoft.Json.Linq.JObject", (NpgsqlDbType.Jsonb, "jsonb", "jsonb", false, null) }, + { "Newtonsoft.Json.Linq.JArray", (NpgsqlDbType.Jsonb, "jsonb", "jsonb", false, null) }, + { "System.Guid", (NpgsqlDbType.Uuid, "uuid", "uuid", false, false) },{ "System.Nullable`1[System.Guid]", (NpgsqlDbType.Uuid, "uuid", "uuid", false, true) }, + + { "NpgsqlTypes.NpgsqlRange", (NpgsqlDbType.Range | NpgsqlDbType.Integer, "int4range", "int4range", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlRange]", (NpgsqlDbType.Range | NpgsqlDbType.Integer, "int4range", "int4range", false, true) }, + { "NpgsqlTypes.NpgsqlRange", (NpgsqlDbType.Range | NpgsqlDbType.Bigint, "int8range", "int8range", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlRange]", (NpgsqlDbType.Range | NpgsqlDbType.Bigint, "int8range", "int8range", false, true) }, + { "NpgsqlTypes.NpgsqlRange", (NpgsqlDbType.Range | NpgsqlDbType.Numeric, "numrange", "numrange", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlRange]", (NpgsqlDbType.Range | NpgsqlDbType.Numeric, "numrange", "numrange", false, true) }, + { "NpgsqlTypes.NpgsqlRange", (NpgsqlDbType.Range | NpgsqlDbType.Timestamp, "tsrange", "tsrange", false, false) },{ "System.Nullable`1[NpgsqlTypes.NpgsqlRange]", (NpgsqlDbType.Range | NpgsqlDbType.Timestamp, "tsrange", "tsrange", false, true) }, + + { "Dictionary", (NpgsqlDbType.Hstore, "hstore", "hstore", false, null) }, + { "Npgsql.LegacyPostgis.PostgisGeometry", (NpgsqlDbType.Geometry, "geometry", "geometry", false, null) }, + }; + + public (int type, string dbtype, string dbtypeFull, bool? isnullable)? GetDbInfo(Type type) { + var enumType = type.IsEnum ? type : null; + if (enumType == null && type.FullName.StartsWith("System.Nullable`1[") && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First(); + if (enumType != null) { + return ((int)NpgsqlDbType.Integer, "int4", $"int4{(type.IsEnum ? " NOT NULL" : "")}", type.IsEnum ? false : true); + } + return _dicCsToDb.TryGetValue(type.FullName, out var trydc) ? new (int, string, string, bool?)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable)) : null; + } + + public string GetComparisonDDLStatements() => this.GetComparisonDDLStatements(typeof(TEntity)); + public string GetComparisonDDLStatements(params Type[] entityTypes) { + var sb = new StringBuilder(); + foreach (var entityType in entityTypes) { + if (sb.Length > 0) sb.Append("\r\n"); + var tb = _commonUtils.GetTableByEntity(entityType); + var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名 + if (tboldname?.Length == 1) tboldname = new[] { "public", tboldname[0] }; + + var isRenameTable = false; + var tbname = tb.DbName.Split(new[] { '.' }, 2); + if (tbname.Length == 1) tbname = new[] { "public", tbname[0] }; + if (_orm.Ado.ExecuteScalar(CommandType.Text, "select 1 from pg_tables a inner join pg_namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = {0}.{1}".FormatMySql(tbname)) == null) { //表不存在 + + if (tboldname != null && _orm.Ado.ExecuteScalar(CommandType.Text, "select 1 from pg_tables a inner join pg_namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = {0}.{1}".FormatMySql(tboldname)) != null) { //旧表存在 + //修改表名 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}")).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(";\r\n"); + isRenameTable = true; + + } else { + //创建表 + var seqcols = new List(); + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ("); + foreach (var tbcol in tb.Columns.Values) { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" "); + sb.Append(tbcol.Attribute.DbType.ToUpper()); + if (tbcol.Attribute.IsIdentity && tbcol.Attribute.DbType.IndexOf("serial", StringComparison.CurrentCultureIgnoreCase) == -1) seqcols.Add(tbcol); + sb.Append(","); + } + if (tb.Primarys.Any() == false) + sb.Remove(sb.Length - 1, 1); + else { + sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pkey PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append(")"); + } + sb.Append("\r\n) WITH (OIDS=FALSE);\r\n"); + continue; + } + } + //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 + var addcols = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + foreach (var tbcol in tb.Columns) addcols.Add(tbcol.Value.Attribute.Name, tbcol.Value); + var surplus = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var dbcols = new List(); + var sql = @"select +a.attname, +t.typname, +case when a.atttypmod > 0 and a.atttypmod < 32767 then a.atttypmod - 4 else a.attlen end len, +case when t.typelem = 0 then t.typname else t2.typname end, +case when a.attnotnull then 0 else 1 end as is_nullable, +e.adsrc as is_identity +from pg_class c +inner join pg_attribute a on a.attnum > 0 and a.attrelid = c.oid +inner join pg_type t on t.oid = a.atttypid +left join pg_type t2 on t2.oid = t.typelem +left join pg_description d on d.objoid = a.attrelid and d.objsubid = a.attnum +left join pg_attrdef e on e.adrelid = a.attrelid and e.adnum = a.attnum +inner join pg_namespace ns on ns.oid = c.relnamespace +inner join pg_namespace ns2 on ns2.oid = t.typnamespace +where ns.nspname = {0} and c.relname = {1}".FormatMySql(isRenameTable ? tboldname : tbname); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + foreach (var row in ds) { + string column = string.Concat(row[0]); + string sqlType = string.Concat(row[3]); + long max_length = long.Parse(string.Concat(row[2])); + bool is_nullable = string.Concat(row[4]) == "1"; + bool is_identity = string.Concat(row[5]).StartsWith(@"nextval('") && string.Concat(row[6]).EndsWith(@"_seq'::regclass)"); + + if (addcols.TryGetValue(column, out var trycol)) { + if (trycol.Attribute.DbType.ToLower().StartsWith(sqlType.ToLower()) == false || + (trycol.Attribute.DbType.IndexOf("NOT NULL") == -1) != is_nullable || + trycol.Attribute.IsIdentity != is_identity) { + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(column)).Append(" TYPE ").Append(trycol.Attribute.DbType.ToUpper()); + if (trycol.Attribute.IsIdentity) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + } + addcols.Remove(column); + } else + surplus.Add(column, true); //记录剩余字段 + } + foreach (var addcol in addcols.Values) { + if (string.IsNullOrEmpty(addcol.Attribute.OldName) == false && surplus.ContainsKey(addcol.Attribute.OldName)) { //修改列名 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" RENAME COLUMN ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.OldName)).Append(" TO ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(";\r\n"); + if (addcol.Attribute.IsIdentity) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + + } else { //添加列 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD COLUMN ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(" ").Append(addcol.Attribute.DbType.ToUpper()); + if (addcol.Attribute.IsIdentity) sb.Append(" AUTO_INCREMENT"); + sb.Append(";\r\n"); + } + } + } + return sb.Length == 0 ? null : sb.ToString(); + } + + public bool SyncStructure() => this.SyncStructure(typeof(TEntity)); + public bool SyncStructure(params Type[] entityTypes) { + var ddl = this.GetComparisonDDLStatements(entityTypes); + if (string.IsNullOrEmpty(ddl)) return true; + try { + return _orm.Ado.ExecuteNonQuery(CommandType.Text, ddl) > 0; + } catch { + return false; + } + } + + } +} \ No newline at end of file diff --git a/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs b/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs new file mode 100644 index 00000000..f232571d --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs @@ -0,0 +1,366 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; + +namespace FreeSql.PostgreSQL { + class PostgreSQLDbFirst : IDbFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public PostgreSQLDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public int GetDbType(DbColumnInfo column) => (int)GetMySqlDbType(column); + MySqlDbType GetMySqlDbType(DbColumnInfo column) { + var is_unsigned = column.DbTypeTextFull.ToLower().EndsWith(" unsigned"); + switch (column.DbTypeText.ToLower()) { + case "bit": return MySqlDbType.Bit; + + case "tinyint": return is_unsigned ? MySqlDbType.UByte : MySqlDbType.Byte; + case "smallint": return is_unsigned ? MySqlDbType.UInt16 : MySqlDbType.Int16; + case "mediumint": return is_unsigned ? MySqlDbType.UInt24 : MySqlDbType.Int24; + case "int": return is_unsigned ? MySqlDbType.UInt32 : MySqlDbType.Int32; + case "bigint": return is_unsigned ? MySqlDbType.UInt64 : MySqlDbType.Int64; + + case "real": + case "double": return MySqlDbType.Double; + case "float": return MySqlDbType.Float; + case "numeric": + case "decimal": return MySqlDbType.Decimal; + + case "year": return MySqlDbType.Year; + case "time": return MySqlDbType.Time; + case "date": return MySqlDbType.Date; + case "timestamp": return MySqlDbType.Timestamp; + case "datetime": return MySqlDbType.DateTime; + + case "tinyblob": return MySqlDbType.TinyBlob; + case "blob": return MySqlDbType.Blob; + case "mediumblob": return MySqlDbType.MediumBlob; + case "longblob": return MySqlDbType.LongBlob; + + case "binary": return MySqlDbType.Binary; + case "varbinary": return MySqlDbType.VarBinary; + + case "tinytext": return MySqlDbType.TinyText; + case "text": return MySqlDbType.Text; + case "mediumtext": return MySqlDbType.MediumText; + case "longtext": return MySqlDbType.LongText; + + case "char": return MySqlDbType.String; + case "varchar": return MySqlDbType.VarChar; + + case "set": return MySqlDbType.Set; + case "enum": return MySqlDbType.Enum; + + case "point": return MySqlDbType.Geometry; + case "linestring": return MySqlDbType.Geometry; + case "polygon": return MySqlDbType.Geometry; + case "geometry": return MySqlDbType.Geometry; + case "multipoint": return MySqlDbType.Geometry; + case "multilinestring": return MySqlDbType.Geometry; + case "multipolygon": return MySqlDbType.Geometry; + case "geometrycollection": return MySqlDbType.Geometry; + default: return MySqlDbType.String; + } + } + + static readonly Dictionary _dicDbToCs = new Dictionary() { + { (int)MySqlDbType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") }, + + { (int)MySqlDbType.Byte, ("(sbyte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetByte") }, + { (int)MySqlDbType.Int16, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") }, + { (int)MySqlDbType.Int24, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Int32, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Int64, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") }, + + { (int)MySqlDbType.UByte, ("(byte?)", "byte.Parse({0})", "{0}.ToString()", "byte?", typeof(byte), typeof(byte?), "{0}.Value", "GetByte") }, + { (int)MySqlDbType.UInt16, ("(ushort?)", "ushort.Parse({0})", "{0}.ToString()", "ushort?", typeof(ushort), typeof(ushort?), "{0}.Value", "GetInt16") }, + { (int)MySqlDbType.UInt24, ("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.UInt32, ("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.UInt64, ("(ulong?)", "ulong.Parse({0})", "{0}.ToString()", "ulong?", typeof(ulong), typeof(ulong?), "{0}.Value", "GetInt64") }, + + { (int)MySqlDbType.Double, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, + { (int)MySqlDbType.Float, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + { (int)MySqlDbType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + + { (int)MySqlDbType.Year, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)MySqlDbType.Time, ("(TimeSpan?)", "TimeSpan.FromSeconds(double.Parse({0}))", "{0}.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") }, + { (int)MySqlDbType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)MySqlDbType.Timestamp, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.TotalSeconds.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)MySqlDbType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + + { (int)MySqlDbType.TinyBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.Blob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.MediumBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.LongBlob, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)MySqlDbType.Binary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)MySqlDbType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)MySqlDbType.TinyText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.MediumText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.LongText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)MySqlDbType.String, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.VarString, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)MySqlDbType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)MySqlDbType.Set, ("(long?)", "long.Parse({0})", "{0}.ToInt64().ToString()", "Set", typeof(bool), typeof(Enum), "{0}", "GetInt64") }, + { (int)MySqlDbType.Enum, ("(long?)", "long.Parse({0})", "{0}.ToInt64().ToString()", "Enum", typeof(bool), typeof(Enum), "{0}", "GetInt64") }, + + { (int)MySqlDbType.Geometry, ("(MygisGeometry)", "MygisGeometry.Parse({0}.Replace(StringifySplit, \"|\"))", "{0}.AsText().Replace(\"|\", StringifySplit)", "MygisGeometry", typeof(MygisGeometry), typeof(MygisGeometry), "{0}", "GetString") }, + }; + + public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null; + public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null; + public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + + public List GetDatabases() { + var sql = @"select schema_name from information_schema.schemata where schema_name not in ('information_schema', 'mysql', 'performance_schema')"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList(); + } + + public List GetTablesByDatabase(params string[] database) { + var loc1 = new List(); + var loc2 = new Dictionary(); + var loc3 = new Dictionary>(); + + if (database == null || database.Any() == false) return loc1; + var databaseIn = string.Join(",", database.Select(a => "{0}".FormatMySql(a))); + var sql = string.Format(@"select +concat(a.table_schema, '.', a.table_name) 'id', +a.table_schema 'schema', +a.table_name 'table', +a.table_type 'type' +from information_schema.tables a +where a.table_schema in ({0})", databaseIn); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var loc6 = new List(); + var loc66 = new List(); + foreach (var row in ds) { + var table_id = string.Concat(row[0]); + var schema = string.Concat(row[1]); + var table = string.Concat(row[2]); + var type = string.Concat(row[3]) == "VIEW" ? DbTableType.VIEW : DbTableType.TABLE; + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + schema = ""; + } + loc2.Add(table_id, new DbTableInfo { Id = table_id, Schema = schema, Name = table, Type = type }); + loc3.Add(table_id, new Dictionary()); + switch (type) { + case DbTableType.TABLE: + case DbTableType.VIEW: + loc6.Add(table.Replace("'", "''")); + break; + case DbTableType.StoreProcedure: + loc66.Add(table.Replace("'", "''")); + break; + } + } + if (loc6.Count == 0) return loc1; + var loc8 = "'" + string.Join("','", loc6.ToArray()) + "'"; + var loc88 = "'" + string.Join("','", loc66.ToArray()) + "'"; + + sql = string.Format(@"select +concat(a.table_schema, '.', a.table_name), +a.column_name, +a.data_type, +ifnull(a.character_maximum_length, 0) 'len', +a.column_type, +case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable', +case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity', +a.column_comment 'comment' +from information_schema.columns a +where a.table_schema in ({1}) and a.table_name in ({0}) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string type = string.Concat(row[2]); + //long max_length = long.Parse(string.Concat(row[3])); + string sqlType = string.Concat(row[4]); + var m_len = Regex.Match(sqlType, @"\w+\((\d+)"); + int max_length = m_len.Success ? int.Parse(m_len.Groups[1].Value) : -1; + bool is_nullable = string.Concat(row[5]) == "1"; + bool is_identity = string.Concat(row[6]) == "1"; + string comment = string.Concat(row[7]); + if (max_length == 0) max_length = -1; + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + } + loc3[table_id].Add(column, new DbColumnInfo { + Name = column, + MaxLength = max_length, + IsIdentity = is_identity, + IsNullable = is_nullable, + IsPrimary = false, + DbTypeText = type, + DbTypeTextFull = sqlType, + Table = loc2[table_id], + Coment = comment + }); + loc3[table_id][column].DbType = this.GetDbType(loc3[table_id][column]); + loc3[table_id][column].CsType = this.GetCsTypeInfo(loc3[table_id][column]); + } + + sql = string.Format(@"select +concat(a.constraint_schema, '.', a.table_name) 'table_id', +a.column_name, +concat(a.constraint_schema, '/', a.table_name, '/', a.constraint_name) 'index_id', +1 'IsUnique', +case when constraint_name = 'PRIMARY' then 1 else 0 end 'IsPrimaryKey', +0 'IsClustered', +0 'IsDesc' +from information_schema.key_column_usage a +where a.constraint_schema in ({1}) and a.table_name in ({0}) and isnull(position_in_unique_constraint) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var indexColumns = new Dictionary>>(); + var uniqueColumns = new Dictionary>>(); + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string index_id = string.Concat(row[2]); + bool is_unique = string.Concat(row[3]) == "1"; + bool is_primary_key = string.Concat(row[4]) == "1"; + bool is_clustered = string.Concat(row[5]) == "1"; + int is_desc = int.Parse(string.Concat(row[6])); + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + } + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key; + + Dictionary> loc10 = null; + List loc11 = null; + if (!indexColumns.TryGetValue(table_id, out loc10)) + indexColumns.Add(table_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + if (is_unique) { + if (!uniqueColumns.TryGetValue(table_id, out loc10)) + uniqueColumns.Add(table_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + } + } + foreach (string table_id in indexColumns.Keys) { + foreach (var columns in indexColumns[table_id].Values) + loc2[table_id].Indexes.Add(columns); + } + foreach (string table_id in uniqueColumns.Keys) { + foreach (var columns in uniqueColumns[table_id].Values) { + columns.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc2[table_id].Uniques.Add(columns); + } + } + + sql = string.Format(@"select +concat(a.constraint_schema, '.', a.table_name) 'table_id', +a.column_name, +concat(a.constraint_schema, '/', a.constraint_name) 'FKId', +concat(a.referenced_table_schema, '.', a.referenced_table_name) 'ref_table_id', +1 'IsForeignKey', +a.referenced_column_name 'ref_column' +from information_schema.key_column_usage a +where a.constraint_schema in ({1}) and a.table_name in ({0}) and not isnull(position_in_unique_constraint) +", loc8, databaseIn); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var fkColumns = new Dictionary>(); + foreach (var row in ds) { + string table_id = string.Concat(row[0]); + string column = string.Concat(row[1]); + string fk_id = string.Concat(row[2]); + string ref_table_id = string.Concat(row[3]); + bool is_foreign_key = string.Concat(row[4]) == "1"; + string referenced_column = string.Concat(row[5]); + if (database.Length == 1) { + table_id = table_id.Substring(table_id.IndexOf('.') + 1); + ref_table_id = ref_table_id.Substring(ref_table_id.IndexOf('.') + 1); + } + if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue; + var loc9 = loc3[table_id][column]; + if (loc2.ContainsKey(ref_table_id) == false) continue; + var loc10 = loc2[ref_table_id]; + var loc11 = loc3[ref_table_id][referenced_column]; + + Dictionary loc12 = null; + DbForeignInfo loc13 = null; + if (!fkColumns.TryGetValue(table_id, out loc12)) + fkColumns.Add(table_id, loc12 = new Dictionary()); + if (!loc12.TryGetValue(fk_id, out loc13)) + loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc10 }); + loc13.Columns.Add(loc9); + loc13.ReferencedColumns.Add(loc11); + } + foreach (var table_id in fkColumns.Keys) + foreach (var fk in fkColumns[table_id].Values) + loc2[table_id].Foreigns.Add(fk); + + foreach (var table_id in loc3.Keys) { + foreach (var loc5 in loc3[table_id].Values) { + loc2[table_id].Columns.Add(loc5); + if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5); + if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5); + } + } + foreach (var loc4 in loc2.Values) { + if (loc4.Primarys.Count == 0 && loc4.Uniques.Count > 0) { + foreach (var loc5 in loc4.Uniques[0]) { + loc5.IsPrimary = true; + loc4.Primarys.Add(loc5); + } + } + loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc4.Columns.Sort((c1, c2) => { + int compare = c2.IsPrimary.CompareTo(c1.IsPrimary); + if (compare == 0) { + bool b1 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c1.Name) != null) != null; + bool b2 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c2.Name) != null) != null; + compare = b2.CompareTo(b1); + } + if (compare == 0) compare = c1.Name.CompareTo(c2.Name); + return compare; + }); + loc1.Add(loc4); + } + loc1.Sort((t1, t2) => { + var ret = t1.Schema.CompareTo(t2.Schema); + if (ret == 0) ret = t1.Name.CompareTo(t2.Name); + return ret; + }); + + loc2.Clear(); + loc3.Clear(); + return loc1; + } + } +} \ No newline at end of file diff --git a/FreeSql/PostgreSQL/PostgreSQLExpression.cs b/FreeSql/PostgreSQL/PostgreSQLExpression.cs new file mode 100644 index 00000000..0b3be41c --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLExpression.cs @@ -0,0 +1,159 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.PostgreSQL { + class PostgreSQLExpression : CommonExpression { + + public PostgreSQLExpression(CommonUtils common) : base(common) { } + + internal override string ExpressionLambdaToSqlCall(MethodCallExpression exp, List _tables, List _selectColumnMap, SelectTableInfoType tbtype, bool isQuoteName) { + if (exp.Object.Type.FullName == "System.String") { + var left = ExpressionLambdaToSql(exp.Object, _tables, _selectColumnMap, tbtype, isQuoteName); + switch (exp.Method.Name) { + case "StartsWith": + case "EndsWith": + case "Contains": + var args0Value = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (args0Value == "NULL") return $"({left}) IS NULL"; + if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"concat('%', {args0Value})")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"concat({args0Value}, '%')")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) like concat('%', {args0Value}, '%')"; + case "ToLower": return $"lower({left})"; + case "ToUpper": return $"upper({left})"; + case "Substring": return $"substr({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)} + 1, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Length": return $"char_length({left})"; + case "IndexOf": + var indexOfFindStr = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"(locate({left}, {indexOfFindStr}, ParseLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName) + 1) - 1)"; + return $"(locate({left}, {indexOfFindStr}) - 1)"; + case "PadLeft": return $"lpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PadRight": return $"rpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Trim": + case "TrimStart": + case "TrimEnd": + if (exp.Arguments.Count == 0) { + if (exp.Method.Name == "Trim") return $"trim({left})"; + if (exp.Method.Name == "TrimStart") return $"ltrim({left})"; + if (exp.Method.Name == "TrimStart") return $"rtrim({left})"; + } + foreach (var argsTrim01 in exp.Arguments) { + if (exp.Method.Name == "Trim") left = $"trim({ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(leading {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(trailing {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + } + return left; + case "Replace": return $"replace({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "CompareTo": return $"strcmp({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + } + } + + if (exp.Object.Type.FullName == "System.Math") { + switch (exp.Method.Name) { + case "Abs": return $"abs({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sign": return $"sign({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Floor": return $"floor({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Round": + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Exp": return $"exp({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log": return $"log({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log10": return $"log10({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Pow": return $"pow({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sqrt": return $"sqrt({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PI": return $"pi()"; + case "Cos": return $"cos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sin": return $"sin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Tan": return $"tan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Acos": return $"acos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Asin": return $"asin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan": return $"atan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan2": return $"atan2({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Truncate": return $"truncate({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, 0)"; + } + } + + //dayofweek = DayOfWeek + //dayofmonth = Day + //dayofyear = DayOfYear + //month = Month + //year = Year + //hour = Hour + //minute = Minute + //second = Second + /* + * date_add(date,interval expr type) + date_sub(date,interval expr type) + adddate(date,interval expr type) + subdate(date,interval expr type) + 对日期时间进行加减法运算 + (adddate()和subdate()是date_add()和date_sub()的同义词,也 + 可以用运算符+和-而不是函数 + date是一个datetime或date值,expr对date进行加减法的一个表 + 达式字符串type指明表达式expr应该如何被解释 +  [type值 含义 期望的expr格式]: +  second 秒 seconds +  minute 分钟 minutes +  hour 时间 hours +  day 天 days +  month 月 months +  year 年 years +  minute_second 分钟和秒 "minutes:seconds" +  hour_minute 小时和分钟 "hours:minutes" +  day_hour 天和小时 "days hours" +  year_month 年和月 "years-months" +  hour_second 小时, 分钟, "hours:minutes:seconds" +  day_minute 天, 小时, 分钟 "days hours:minutes" +  day_second 天, 小时, 分钟, 秒 "days + hours:minutes:seconds" + expr中允许任何标点做分隔符,如果所有是date值时结果是一个 +date值,否则结果是一个datetime值) + 如果type关键词不完整,则mysql从右端取值,day_second因为缺 +少小时分钟等于minute_second) + 如果增加month、year_month或year,天数大于结果月份的最大天 +数则使用最大天数) +mysql> select "1997-12-31 23:59:59" + interval 1 second; + +  -> 1998-01-01 00:00:00 +mysql> select interval 1 day + "1997-12-31"; +  -> 1998-01-01 +mysql> select "1998-01-01" - interval 1 second; +  -> 1997-12-31 23:59:59 +mysql> select date_add("1997-12-31 23:59:59",interval 1 +second); +  -> 1998-01-01 00:00:00 +mysql> select date_add("1997-12-31 23:59:59",interval 1 +day); +  -> 1998-01-01 23:59:59 +mysql> select date_add("1997-12-31 23:59:59",interval +"1:1" minute_second); +  -> 1998-01-01 00:01:00 +mysql> select date_sub("1998-01-01 00:00:00",interval "1 +1:1:1" day_second); +  -> 1997-12-30 22:58:59 +mysql> select date_add("1998-01-01 00:00:00", interval "-1 +10" day_hour); +  -> 1997-12-30 14:00:00 +mysql> select date_sub("1998-01-02", interval 31 day); +  -> 1997-12-02 +mysql> select extract(year from "1999-07-02"); +  -> 1999 +mysql> select extract(year_month from "1999-07-02 +01:02:03"); +  -> 199907 +mysql> select extract(day_minute from "1999-07-02 +01:02:03"); +  -> 20102 + */ + + //convert + var xxx = DateTime.Now.ToString(""); + + + throw new Exception($"MySqlExpression 未现实函数表达式 {exp} 解析"); + } + } +} diff --git a/FreeSql/PostgreSQL/PostgreSQLProvider.cs b/FreeSql/PostgreSQL/PostgreSQLProvider.cs new file mode 100644 index 00000000..ae6976bb --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLProvider.cs @@ -0,0 +1,50 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.MySql.Curd; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; + +namespace FreeSql.PostgreSQL { + + class PostgreSQLProvider : IFreeSql { + + public ISelect Select() where T1 : class => new MySqlSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public ISelect Select(object dywhere) where T1 : class => new MySqlSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IInsert Insert() where T1 : class => new MySqlInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + public IInsert Insert(T1 source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(IEnumerable source) where T1 : class => this.Insert().AppendData(source); + public IUpdate Update() where T1 : class => new MySqlUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IUpdate Update(object dywhere) where T1 : class => new MySqlUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IDelete Delete() where T1 : class => new MySqlDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IDelete Delete(object dywhere) where T1 : class => new MySqlDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public IAdo Ado { get; } + public ICache Cache { get; } + public ICodeFirst CodeFirst { get; } + public IDbFirst DbFirst { get; } + public PostgreSQLProvider(IDistributedCache cache, IConfiguration cacheStrategy, string masterConnectionString, string[] slaveConnectionString, ILogger log) { + CacheStrategy = cacheStrategy; + if (log == null) log = new LoggerFactory(new[] { new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() }).CreateLogger("FreeSql.PostgreSQL"); + + this.InternalCommonUtils = new MySqlUtils(this); + this.InternalCommonExpression = new PostgreSQLExpression(this.InternalCommonUtils); + + this.Cache = new CacheProvider(cache, log); + this.Ado = new PostgreSQLAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + + this.DbFirst = new PostgreSQLDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.InternalCommonUtils.CodeFirst = this.CodeFirst = new PostgreSQLCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + } + + internal CommonUtils InternalCommonUtils { get; } + internal CommonExpression InternalCommonExpression { get; } + internal IConfiguration CacheStrategy { get; private set; } + + public void Transaction(Action handler) => Ado.Transaction(handler); + + public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout); + } +} diff --git a/FreeSql/PostgreSQL/PostgreSQLUtils.cs b/FreeSql/PostgreSQL/PostgreSQLUtils.cs new file mode 100644 index 00000000..62fefcbf --- /dev/null +++ b/FreeSql/PostgreSQL/PostgreSQLUtils.cs @@ -0,0 +1,48 @@ +using FreeSql.Internal; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Data.Common; + +namespace FreeSql.PostgreSQL { + + class MySqlUtils : CommonUtils { + IFreeSql _orm; + public MySqlUtils(IFreeSql mysql) { + _orm = mysql; + } + + internal override DbParameter AppendParamter(List _params, string parameterName, object value) { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + MySqlParameter ret = null; + if (value == null) ret = new MySqlParameter { ParameterName = $"{parameterName}", Value = DBNull.Value }; + else { + var type = value.GetType(); + ret = new MySqlParameter { + ParameterName = parameterName, + Value = value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.MySqlDbType = (MySqlDbType)tp.Value; + } + _params?.Add(ret); + return ret; + } + + internal override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, "?", (name, type, value) => { + var cp = new MySqlParameter { + ParameterName = name, + Value = value ?? DBNull.Value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) cp.MySqlDbType = (MySqlDbType)tp.Value; + return cp; + }); + + internal override string FormatSql(string sql, params object[] args) => sql?.FormatMySql(args); + internal override string QuoteSqlName(string name) => $"`{name.Trim('`').Replace(".", "`.`")}`"; + internal override string QuoteParamterName(string name) => $"?{name}"; + internal override string IsNull(string sql, object value) => $"ifnull({sql}, {value})"; + } +} diff --git a/FreeSql/SqlServer/Curd/SqlServerDelete.cs b/FreeSql/SqlServer/Curd/SqlServerDelete.cs new file mode 100644 index 00000000..ace43780 --- /dev/null +++ b/FreeSql/SqlServer/Curd/SqlServerDelete.cs @@ -0,0 +1,34 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.SqlServer.Curd { + + class SqlServerDelete : Internal.CommonProvider.DeleteProvider where T1 : class { + public SqlServerDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteDeleted() { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(" OUTPUT "); + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append("DELETED.").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + + var validx = sql.IndexOf(" WHERE "); + if (validx == -1) throw new ArgumentException("找不到 WHERE "); + sb.Insert(0, sql.Substring(0, validx)); + sb.Append(sql.Substring(validx)); + + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/SqlServer/Curd/SqlServerInsert.cs b/FreeSql/SqlServer/Curd/SqlServerInsert.cs new file mode 100644 index 00000000..c02fde4d --- /dev/null +++ b/FreeSql/SqlServer/Curd/SqlServerInsert.cs @@ -0,0 +1,37 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace FreeSql.SqlServer.Curd { + + class SqlServerInsert : Internal.CommonProvider.InsertProvider where T1 : class { + public SqlServerInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) { + } + + public override long ExecuteIdentity() => int.TryParse(string.Concat(_orm.Ado.ExecuteScalar(CommandType.Text, string.Concat(this.ToSql(), "; SELECT SCOPE_IDENTITY();"), _params)), out var trylng) ? trylng : 0; + + public override List ExecuteInserted() { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(" OUTPUT "); + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append("INSERTED.").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + + var validx = sql.IndexOf(") VALUES"); + if (validx == -1) throw new ArgumentException("找不到 VALUES"); + sb.Insert(0, sql.Substring(0, validx)).Insert(0, ")"); + sb.Append(sql.Substring(validx + 1)); + + return _orm.Ado.Query(sb.ToString()); + } + } +} diff --git a/FreeSql/SqlServer/Curd/SqlServerSelect.cs b/FreeSql/SqlServer/Curd/SqlServerSelect.cs new file mode 100644 index 00000000..58a337a9 --- /dev/null +++ b/FreeSql/SqlServer/Curd/SqlServerSelect.cs @@ -0,0 +1,126 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.SqlServer.Curd { + + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select1Provider where T1 : class { + + internal static string ToSqlStatic(CommonUtils _commonUtils, string _select, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List _tables) { + var sb = new StringBuilder(); + sb.Append(_select); + if (_limit > 0) sb.Append("TOP ").Append(_skip + _limit).Append(" "); + sb.Append(field); + if (_skip > 0) { + if (string.IsNullOrEmpty(_orderby)) { + var pktb = _tables.Where(a => a.Table.Primarys.Any()).FirstOrDefault(); + if (pktb != null) _orderby = string.Concat(" \r\nORDER BY ", pktb.Alias, ".", _commonUtils.QuoteSqlName(pktb?.Table.Primarys.First().Attribute.Name)); + else _orderby = string.Concat(" \r\nORDER BY ", _tables.First().Alias, ".", _commonUtils.QuoteSqlName(_tables.First().Table.Columns.First().Value.Attribute.Name)); + } + sb.Append(", ROW_NUMBER() OVER(").Append(_orderby).Append(") AS __rownum__"); + } + sb.Append(" \r\nFROM "); + var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray(); + var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray(); + for (var a = 0; a < tbsfrom.Length; a++) { + sb.Append(_commonUtils.QuoteSqlName(tbsfrom[a].Table.DbName)).Append(" ").Append(tbsfrom[a].Alias); + if (tbsjoin.Length > 0) { + //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1 + for (var b = 1; b < tbsfrom.Length; b++) + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbsfrom[b].Table.DbName)).Append(" ").Append(tbsfrom[b].Alias).Append(" ON 1 = 1"); + break; + } + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + foreach (var tb in tbsjoin) { + switch (tb.Type) { + case SelectTableInfoType.LeftJoin: + sb.Append(" \r\nLEFT JOIN "); + break; + case SelectTableInfoType.InnerJoin: + sb.Append(" \r\nINNER JOIN "); + break; + case SelectTableInfoType.RightJoin: + sb.Append(" \r\nRIGHT JOIN "); + break; + } + sb.Append(_commonUtils.QuoteSqlName(tb.Table.DbName)).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On); + } + if (_join.Length > 0) sb.Append(_join); + + var sbqf = new StringBuilder(); + foreach (var tb in _tables) { + if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false) + sbqf.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")"); + } + if (_where.Length > 0) { + sb.Append(" \r\nWHERE ").Append(_where.ToString().Substring(5)); + if (sbqf.Length > 0) sb.Append(sbqf.ToString()); + } else { + if (sbqf.Length > 0) sb.Append(" \r\nWHERE ").Append(sbqf.Remove(0, 5)); + } + if (string.IsNullOrEmpty(_groupby) == false) { + sb.Append(_groupby); + if (string.IsNullOrEmpty(_having) == false) + sb.Append(" \r\nHAVING ").Append(_having.Substring(5)); + } + if (_skip <= 0) + sb.Append(_orderby); + else + sb.Insert(0, "WITH t AS ( ").Append(" ) SELECT t.* FROM t where __rownum__ > ").Append(_skip); + + return sb.ToString(); + } + + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) { this.InternalFrom(exp?.Body); var ret = new SqlServerSelect(_orm, _commonUtils, _commonExpression, null); SqlServerSelect.CopyData(this, ret); return ret; } + public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select2Provider where T1 : class where T2 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select3Provider where T1 : class where T2 : class where T3 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select4Provider where T1 : class where T2 : class where T3 : class where T4 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select5Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select6Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select7Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select8Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select9Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } + class SqlServerSelect : FreeSql.Internal.CommonProvider.Select10Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class { + public SqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => SqlServerSelect.ToSqlStatic(_commonUtils, _select, field ?? this.GetAllField().field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables); + } +} diff --git a/FreeSql/SqlServer/Curd/SqlServerUpdate.cs b/FreeSql/SqlServer/Curd/SqlServerUpdate.cs new file mode 100644 index 00000000..4d51a7ca --- /dev/null +++ b/FreeSql/SqlServer/Curd/SqlServerUpdate.cs @@ -0,0 +1,58 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.SqlServer.Curd { + + class SqlServerUpdate : Internal.CommonProvider.UpdateProvider where T1 : class { + + public SqlServerUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) { + } + + public override List ExecuteUpdated() { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(" OUTPUT "); + var colidx = 0; + foreach (var col in _table.Columns.Values) { + if (colidx > 0) sb.Append(", "); + sb.Append("INSERTED.").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + + var validx = sql.IndexOf(" WHERE "); + if (validx == -1) throw new ArgumentException("找不到 WHERE "); + sb.Insert(0, sql.Substring(0, validx)); + sb.Append(sql.Substring(validx)); + + return _orm.Ado.Query(sb.ToString()); + } + + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) { + if (_table.Primarys.Length > 1) caseWhen.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) caseWhen.Append(", "); + caseWhen.Append("cast(").Append(_commonUtils.QuoteSqlName(pk.Attribute.Name)).Append(" as varchar)"); + ++pkidx; + } + if (_table.Primarys.Length > 1) caseWhen.Append(")"); + } + + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) { + if (_table.Primarys.Length > 1) sb.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) { + if (pkidx > 0) sb.Append(", "); + sb.Append("cast(").Append(_commonUtils.FormatSql("{0}", _table.Properties.TryGetValue(pk.CsName, out var tryp2) ? tryp2.GetValue(d) : null)).Append(" as varchar)"); + ++pkidx; + } + if (_table.Primarys.Length > 1) sb.Append(")"); + } + } +} diff --git a/FreeSql/SqlServer/SqlServerAdo/SqlServerAdo.cs b/FreeSql/SqlServer/SqlServerAdo/SqlServerAdo.cs new file mode 100644 index 00000000..8adf87f4 --- /dev/null +++ b/FreeSql/SqlServer/SqlServerAdo/SqlServerAdo.cs @@ -0,0 +1,61 @@ +using FreeSql.Internal; +using Microsoft.Extensions.Logging; +using SafeObjectPool; +using System; +using System.Collections; +using System.Data.Common; +using System.Data.SqlClient; +using System.Text; +using System.Threading; + +namespace FreeSql.SqlServer { + class SqlServerAdo : FreeSql.Internal.CommonProvider.AdoProvider { + CommonUtils _util; + + public SqlServerAdo() : base(null, null) { } + public SqlServerAdo(CommonUtils util, ICache cache, ILogger log, string masterConnectionString, string[] slaveConnectionStrings) : base(cache, log) { + this._util = util; + MasterPool = new SqlServerConnectionPool("主库", masterConnectionString, null, null); + if (slaveConnectionStrings != null) { + foreach (var slaveConnectionString in slaveConnectionStrings) { + var slavePool = new SqlServerConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + } + } + } + public override object AddslashesProcessParam(object param) { + if (param == null) return "NULL"; + if (param is bool || param is bool?) + return (bool)param ? 1 : 0; + else if (param is string || param is Enum) + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (decimal.TryParse(string.Concat(param), out var trydec)) + return param; + else if (param is DateTime) { + DateTime dt = (DateTime)param; + return string.Concat("'", dt.ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "'"); + } else if (param is DateTime?) { + DateTime? dt = param as DateTime?; + return string.Concat("'", dt.Value.ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "'"); + } else if (param is IEnumerable) { + var sb = new StringBuilder(); + var ie = param as IEnumerable; + foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z)); + return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString(); + } else { + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + //if (param is string) return string.Concat('N', nparms[a]); + } + } + + protected override DbCommand CreateCommand() { + return new SqlCommand(); + } + + protected override void ReturnConnection(ObjectPool pool, Object conn, Exception ex) { + (pool as SqlServerConnectionPool).Return(conn, ex); + } + + protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} \ No newline at end of file diff --git a/FreeSql/SqlServer/SqlServerAdo/SqlServerConnectionPool.cs b/FreeSql/SqlServer/SqlServerAdo/SqlServerConnectionPool.cs new file mode 100644 index 00000000..e3d0280c --- /dev/null +++ b/FreeSql/SqlServer/SqlServerAdo/SqlServerConnectionPool.cs @@ -0,0 +1,147 @@ +using SafeObjectPool; +using System; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.SqlServer { + + class SqlServerConnectionPool : ObjectPool { + + internal Action availableHandler; + internal Action unavailableHandler; + + public SqlServerConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) { + var policy = new SqlServerConnectionPoolPolicy { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) { + if (exception != null && exception is SqlException) { + + if (obj.Value.Ping() == false) { + + base.SetUnavailable(exception); + } + } + base.Return(obj, isRecreate); + } + } + + class SqlServerConnectionPoolPolicy : IPolicy { + + internal SqlServerConnectionPool _pool; + public string Name { get; set; } = "SqlServer SqlConnection 对象池"; + public int PoolSize { get; set; } = 100; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 5; + + private string _connectionString; + public string ConnectionString { + get => _connectionString; + set { + _connectionString = value ?? ""; + Match m = Regex.Match(_connectionString, @"Max\s*pool\s*size\s*=\s*(\d+)", RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100; + PoolSize = poolsize; + + var initConns = new Object[poolsize]; + for (var a = 0; a < poolsize; a++) try { initConns[a] = _pool.Get(); } catch { } + foreach (var conn in initConns) _pool.Return(conn); + } + } + + + public bool OnCheckAvailable(Object obj) { + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + var cmd = obj.Value.CreateCommand(); + cmd.CommandText = "select 1"; + cmd.ExecuteNonQuery(); + return true; + } + + public DbConnection OnCreate() { + var conn = new SqlConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGet(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) { + + try { + obj.Value.Open(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + async public Task OnGetAsync(Object obj) { + + if (_pool.IsAvailable) { + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) { + + try { + await obj.Value.OpenAsync(); + } catch (Exception ex) { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + + public void OnGetTimeout() { + + } + + public void OnReturn(Object obj) { + if (obj.Value.State != ConnectionState.Closed) try { obj.Value.Close(); } catch { } + } + + public void OnAvailable() { + _pool.availableHandler?.Invoke(); + } + + public void OnUnavailable() { + _pool.unavailableHandler?.Invoke(); + } + } + + static class SqlServerConnectionExtensions { + + public static bool Ping(this DbConnection that) { + try { + var cmd = that.CreateCommand(); + cmd.CommandText = "select 1"; + cmd.ExecuteNonQuery(); + return true; + } catch { + if (that.State != ConnectionState.Closed) try { that.Close(); } catch { } + return false; + } + } + } +} diff --git a/FreeSql/SqlServer/SqlServerCodeFirst.cs b/FreeSql/SqlServer/SqlServerCodeFirst.cs new file mode 100644 index 00000000..49fda5d8 --- /dev/null +++ b/FreeSql/SqlServer/SqlServerCodeFirst.cs @@ -0,0 +1,164 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.SqlServer { + + class SqlServerCodeFirst : ICodeFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public SqlServerCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public bool IsAutoSyncStructure { get; set; } = true; + + static readonly Dictionary _dicCsToDb = new Dictionary() { + { "System.Boolean", (SqlDbType.Bit, "bit","bit NOT NULL", null, false) },{ "System.Nullable`1[System.Boolean]", (SqlDbType.Bit, "bit","bit", null, true) }, + + { "System.SByte", (SqlDbType.TinyInt, "tinyint", "tinyint NOT NULL", false, false) },{ "System.Nullable`1[System.SByte]", (SqlDbType.TinyInt, "tinyint", "tinyint", false, true) }, + { "System.Int16", (SqlDbType.SmallInt, "smallint","smallint NOT NULL", false, false) },{ "System.Nullable`1[System.Int16]", (SqlDbType.SmallInt, "smallint", "smallint", false, true) }, + { "System.Int32", (SqlDbType.Int, "int", "int NOT NULL", false, false) },{ "System.Nullable`1[System.Int32]", (SqlDbType.Int, "int", "int", false, true) }, + { "System.Int64", (SqlDbType.BigInt, "bigint","bigint NOT NULL", false, false) },{ "System.Nullable`1[System.Int64]", (SqlDbType.BigInt, "bigint","bigint", false, true) }, + + { "System.Byte", (SqlDbType.TinyInt, "tinyint","tinyint NOT NULL", true, false) },{ "System.Nullable`1[System.Byte]", (SqlDbType.TinyInt, "tinyint","tinyint", true, true) }, + { "System.UInt16", (SqlDbType.SmallInt, "smallint","smallint NOT NULL", true, false) },{ "System.Nullable`1[System.UInt16]", (SqlDbType.SmallInt, "smallint", "smallint", true, true) }, + { "System.UInt32", (SqlDbType.Int, "int", "int NOT NULL", true, false) },{ "System.Nullable`1[System.UInt32]", (SqlDbType.Int, "int", "int", true, true) }, + { "System.UInt64", (SqlDbType.BigInt, "bigint", "bigint NOT NULL", true, false) },{ "System.Nullable`1[System.UInt64]", (SqlDbType.BigInt, "bigint", "bigint", true, true) }, + + { "System.Double", (SqlDbType.Float, "double", "double NOT NULL", false, false) },{ "System.Nullable`1[System.Double]", (SqlDbType.Float, "double", "double", false, true) }, + { "System.Single", (SqlDbType.Real, "float","float NOT NULL", false, false) },{ "System.Nullable`1[System.Single]", (SqlDbType.Real, "float","float", false, true) }, + { "System.Decimal", (SqlDbType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false) },{ "System.Nullable`1[System.Decimal]", (SqlDbType.Decimal, "decimal", "decimal(10,2)", false, true) }, + + { "System.TimeSpan", (SqlDbType.Time, "time","time NOT NULL", false, false) },{ "System.Nullable`1[System.TimeSpan]", (SqlDbType.Time, "time", "time",false, true) }, + { "System.DateTime", (SqlDbType.DateTime, "datetime", "datetime NOT NULL", false, false) },{ "System.Nullable`1[System.DateTime]", (SqlDbType.DateTime, "datetime", "datetime", false, true) }, + { "System.DateTimeOffset", (SqlDbType.DateTimeOffset, "datetimeoffset", "datetimeoffset NOT NULL", false, false) },{ "System.Nullable`1[System.DateTime]", (SqlDbType.DateTimeOffset, "datetimeoffset", "datetimeoffset", false, true) }, + + { "System.Byte[]", (SqlDbType.VarBinary, "varbinary", "varbinary(255)", false, null) }, + { "System.String", (SqlDbType.NVarChar, "nvarchar", "nvarchar(255)", false, null) }, + + { "System.Guid", (SqlDbType.UniqueIdentifier, "uniqueidentifier", "uniqueidentifier", false, false) },{ "System.Guid", (SqlDbType.UniqueIdentifier, "uniqueidentifier", "uniqueidentifier", false, true) }, + }; + + public (int type, string dbtype, string dbtypeFull, bool? isnullable)? GetDbInfo(Type type) { + var enumType = type.IsEnum ? type : null; + if (enumType == null && type.FullName.StartsWith("System.Nullable`1[") && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First(); + if (enumType != null) { + return ((int)SqlDbType.Int, "int", "int", type.IsEnum ? false : true); + } + return _dicCsToDb.TryGetValue(type.FullName, out var trydc) ? new (int, string, string, bool?)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable)) : null; + } + + public string GetComparisonDDLStatements() => this.GetComparisonDDLStatements(typeof(TEntity)); + public string GetComparisonDDLStatements(params Type[] entityTypes) { + var sb = new StringBuilder(); + foreach (var entityType in entityTypes) { + if (sb.Length > 0) sb.Append("\r\n"); + var tb = _commonUtils.GetTableByEntity(entityType); + var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名 + if (tboldname?.Length == 1) tboldname = new[] { "dbo", tboldname[0] }; + + var isRenameTable = false; + var tbname = tb.DbName.Split(new[] { '.' }, 2); + if (tbname.Length == 1) tbname = new[] { "dbo", tbname[0] }; + if (_orm.Ado.ExecuteScalar(CommandType.Text, "select 1 from dbo.sysobjects where id = object_id(N'[{0}].[{1}]') and OBJECTPROPERTY(id, N'IsUserTable')".FormatMySql(tbname)) == null) { //表不存在 + + if (tboldname != null && _orm.Ado.ExecuteScalar(CommandType.Text, "select 1 from dbo.sysobjects where id = object_id(N'[{0}].[{1}]') and OBJECTPROPERTY(id, N'IsUserTable')".FormatMySql(tboldname)) != null) { //旧表存在 + //修改表名 + sb.Append(_commonUtils.FormatSql("EXEC sp_rename {0}, {1} GO \r\n", _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}"), _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}"))); + isRenameTable = true; + + } else { + //创建表 + sb.Append("CREATE TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ("); + foreach (var tbcol in tb.Columns.Values) { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" "); + sb.Append(tbcol.Attribute.DbType.ToUpper()); + if (tbcol.Attribute.IsIdentity && tbcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)"); + if (tbcol.Attribute.IsPrimary) sb.Append(" primary key"); + sb.Append(","); + } + sb.Remove(sb.Length - 1, 1).Append("\r\n) GO \r\n"); + continue; + } + } + //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 + var addcols = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + foreach (var tbcol in tb.Columns) addcols.Add(tbcol.Value.Attribute.Name, tbcol.Value); + var surplus = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var dbcols = new List(); + var sql = @"select +a.name 'Column' +,b.name + case + when b.name in ('Char', 'VarChar', 'NChar', 'NVarChar', 'Binary', 'VarBinary') then '(' + + case when a.max_length = -1 then 'MAX' + when b.name in ('NChar', 'NVarchar') then cast(a.max_length / 2 as varchar) + else cast(a.max_length as varchar) end + ')' + when b.name in ('Numeric', 'Decimal') then '(' + cast(a.precision as varchar) + ',' + cast(a.scale as varchar) + ')' + else '' end as 'SqlType' +,a.is_nullable 'IsNullable' +,a.is_identity 'IsIdentity' +from sys.columns a +inner join sys.types b on b.user_type_id = a.user_type_id +left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id +left join sys.tables d on d.object_id = a.object_id +left join sys.schemas e on e.schema_id = d.schema_id +where a.object_id in (object_id(N'[{0}].[{1}]'))".FormatMySql(isRenameTable ? tboldname : tbname); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + foreach (var row in ds) { + string column = string.Concat(row[0]); + string sqlType = string.Concat(row[1]).ToLower(); + bool is_nullable = string.Concat(row[2]) == "1"; + bool is_identity = string.Concat(row[3]) == "1"; + + if (addcols.TryGetValue(column, out var trycol)) { + if (Regex.Replace(trycol.Attribute.DbType, @"\([^\)]+\)", m => Regex.Replace(m.Groups[0].Value, @"\s", "")).StartsWith(sqlType, StringComparison.CurrentCultureIgnoreCase) == false || + (trycol.Attribute.DbType.IndexOf("NOT NULL", StringComparison.CurrentCultureIgnoreCase) == -1) != is_nullable || + trycol.Attribute.IsIdentity != is_identity) { + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(column)).Append(" ").Append(trycol.Attribute.DbType.ToUpper()); + if (trycol.Attribute.IsIdentity && trycol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)"); + sb.Append(" GO \r\n"); + } + addcols.Remove(column); + } else + surplus.Add(column, true); //记录剩余字段 + } + foreach (var addcol in addcols.Values) { + if (string.IsNullOrEmpty(addcol.Attribute.OldName) == false && surplus.ContainsKey(addcol.Attribute.OldName)) { //修改列名 + sb.Append(_commonUtils.FormatSql("EXEC sp_rename {0}, {1}, 'COLUMN' GO \r\n", _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{addcol.Attribute.OldName}"), _commonUtils.QuoteSqlName(addcol.Attribute.Name))); + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(" ").Append(addcol.Attribute.DbType.ToUpper()); + if (addcol.Attribute.IsIdentity && addcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)"); + sb.Append(" GO \r\n"); + + } else { //添加列 + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD ").Append(_commonUtils.QuoteSqlName(addcol.Attribute.Name)).Append(" ").Append(addcol.Attribute.DbType.ToUpper()); + if (addcol.Attribute.IsIdentity && addcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)"); + sb.Append(" GO \r\n"); + } + } + } + return sb.Length == 0 ? null : sb.ToString(); + } + + public bool SyncStructure() => this.SyncStructure(typeof(TEntity)); + public bool SyncStructure(params Type[] entityTypes) { + var ddl = this.GetComparisonDDLStatements(entityTypes); + if (string.IsNullOrEmpty(ddl)) return true; + try { + return _orm.Ado.ExecuteNonQuery(CommandType.Text, ddl) > 0; + } catch { + return false; + } + } + + } +} \ No newline at end of file diff --git a/FreeSql/SqlServer/SqlServerDbFirst.cs b/FreeSql/SqlServer/SqlServerDbFirst.cs new file mode 100644 index 00000000..e57c2037 --- /dev/null +++ b/FreeSql/SqlServer/SqlServerDbFirst.cs @@ -0,0 +1,383 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; + +namespace FreeSql.SqlServer { + class SqlServerDbFirst : IDbFirst { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public SqlServerDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public int GetDbType(DbColumnInfo column) => (int)GetSqlDbType(column); + SqlDbType GetSqlDbType(DbColumnInfo column) { + switch (column.DbTypeText.ToLower()) { + case "bit": return SqlDbType.Bit; + case "tinyint": return SqlDbType.TinyInt; + case "smallint": return SqlDbType.SmallInt; + case "int": return SqlDbType.Int; + case "bigint": return SqlDbType.BigInt; + case "numeric": + case "decimal": return SqlDbType.Decimal; + case "smallmoney": return SqlDbType.SmallMoney; + case "money": return SqlDbType.Money; + case "float": return SqlDbType.Float; + case "real": return SqlDbType.Real; + case "date": return SqlDbType.Date; + case "datetime": + case "datetime2": return SqlDbType.DateTime; + case "datetimeoffset": return SqlDbType.DateTimeOffset; + case "smalldatetime": return SqlDbType.SmallDateTime; + case "time": return SqlDbType.Time; + case "char": return SqlDbType.Char; + case "varchar": return SqlDbType.VarChar; + case "text": return SqlDbType.Text; + case "nchar": return SqlDbType.NChar; + case "nvarchar": return SqlDbType.NVarChar; + case "ntext": return SqlDbType.NText; + case "binary": return SqlDbType.Binary; + case "varbinary": return SqlDbType.VarBinary; + case "image": return SqlDbType.Image; + case "timestamp": return SqlDbType.Timestamp; + case "uniqueidentifier": return SqlDbType.UniqueIdentifier; + default: return SqlDbType.Variant; + } + } + + static readonly Dictionary _dicDbToCs = new Dictionary() { + { (int)SqlDbType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") }, + + { (int)SqlDbType.TinyInt, ("(byte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetByte") }, + { (int)SqlDbType.SmallInt, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") }, + { (int)SqlDbType.Int, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)SqlDbType.BigInt, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") }, + + { (int)SqlDbType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + { (int)SqlDbType.SmallMoney, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + { (int)SqlDbType.Money, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + { (int)SqlDbType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + { (int)SqlDbType.Float, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, + { (int)SqlDbType.Real, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + + { (int)SqlDbType.Time, ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") }, + { (int)SqlDbType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)SqlDbType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)SqlDbType.DateTime2, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)SqlDbType.SmallDateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)SqlDbType.DateTimeOffset, ("(DateTimeOffset?)", "new DateTimeOffset(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTimeOffset), typeof(DateTimeOffset?), "{0}.Value", "GetDateTimeOffset") }, + + { (int)SqlDbType.Binary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)SqlDbType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)SqlDbType.Image, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + { (int)SqlDbType.Timestamp, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)SqlDbType.Char, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)SqlDbType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)SqlDbType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)SqlDbType.NChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)SqlDbType.NVarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)SqlDbType.NText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)SqlDbType.UniqueIdentifier, ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "MygisGeometry", typeof(Guid), typeof(Guid?), "{0}.Value", "GetGuid") }, + }; + + public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null; + public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null; + public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + + public List GetDatabases() { + var sql = @"select name from sys.databases where name not in ('master','tempdb','model','msdb')"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList(); + } + + public List GetTablesByDatabase(params string[] database) { + List loc1 = null; + Dictionary loc2 = new Dictionary(); + Dictionary> loc3 = new Dictionary>(); + + var sql = @" +select + a.Object_id +,b.name 'Owner' +,a.name 'Name' +,'TABLE' type +from sys.tables a +inner join sys.schemas b on b.schema_id = a.schema_id +where not(b.name = 'dbo' and a.name = 'sysdiagrams') +union all +select + a.Object_id +,b.name 'Owner' +,a.name 'Name' +,'VIEW' type +from sys.views a +inner join sys.schemas b on b.schema_id = a.schema_id +union all +select + a.Object_id +,b.name 'Owner' +,a.name 'Name' +,'StoreProcedure' type +from sys.procedures a +inner join sys.schemas b on b.schema_id = a.schema_id +where a.type = 'P' and charindex('$NPSP', a.name) = 0 and charindex('diagram', a.name) = 0 +order by type desc, b.name, a.name +"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var loc6 = new List(); + var loc66 = new List(); + foreach (object[] row in ds) { + int object_id = int.Parse(string.Concat(row[0])); + var owner = string.Concat(row[1]); + var table = string.Concat(row[2]); + Enum.TryParse(string.Concat(row[3]), out var type); + loc2.Add(object_id, new DbTableInfo { Id = object_id.ToString(), Schema = owner, Name = table, Type = type }); + loc3.Add(object_id, new Dictionary()); + switch (type) { + case DbTableType.VIEW: + case DbTableType.TABLE: + loc6.Add(object_id); + break; + case DbTableType.StoreProcedure: + loc66.Add(object_id); + break; + } + } + if (loc6.Count == 0) return loc1; + var loc8 = string.Join(",", loc6.Select(a => string.Concat(a))); + var loc88 = string.Join(",", loc66.Select(a => string.Concat(a))); + + var tsql_place = @" +select +isnull(e.name,'') + '.' + isnull(d.name,'') +,a.Object_id +,a.name 'Column' +,b.name 'Type' +,case + when b.name in ('Text', 'NText', 'Image') then -1 + when b.name in ('NChar', 'NVarchar') then a.max_length / 2 + else a.max_length end 'Length' +,b.name + case + when b.name in ('Char', 'VarChar', 'NChar', 'NVarChar', 'Binary', 'VarBinary') then '(' + + case when a.max_length = -1 then 'MAX' + when b.name in ('NChar', 'NVarchar') then cast(a.max_length / 2 as varchar) + else cast(a.max_length as varchar) end + ')' + when b.name in ('Numeric', 'Decimal') then '(' + cast(a.precision as varchar) + ',' + cast(a.scale as varchar) + ')' + else '' end as 'SqlType' +,c.value +{0} a +inner join sys.types b on b.user_type_id = a.user_type_id +left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id +left join sys.tables d on d.object_id = a.object_id +left join sys.schemas e on e.schema_id = d.schema_id +where a.object_id in ({1}) +"; + sql = string.Format(tsql_place, @" +,a.is_nullable 'IsNullable' +,a.is_identity 'IsIdentity' +from sys.columns", loc8); + if (loc88.Length > 0) { + sql += "union all" + + string.Format(tsql_place.Replace( + "left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id", + "left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.parameter_id"), @" +,cast(0 as bit) 'IsNullable' +,a.is_output 'IsIdentity' +from sys.parameters", loc88); + } + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + foreach (object[] row in ds) { + var table_id = string.Concat(row[0]); + var object_id = int.Parse(string.Concat(row[1])); + var column = string.Concat(row[2]); + var type = string.Concat(row[3]); + var max_length = int.Parse(string.Concat(row[4])); + var sqlType = string.Concat(row[5]); + var comment = string.Concat(row[6]); + var is_nullable = bool.Parse(string.Concat(row[7])); + var is_identity = bool.Parse(string.Concat(row[8])); + if (max_length == 0) max_length = -1; + + loc3[object_id].Add(column, new DbColumnInfo { + Name = column, + MaxLength = max_length, + IsIdentity = is_identity, + IsNullable = is_nullable, + IsPrimary = false, + DbTypeText = type, + DbTypeTextFull = sqlType, + Table = loc2[object_id], + Coment = comment + }); + loc3[object_id][column].DbType = this.GetDbType(loc3[object_id][column]); + loc3[object_id][column].CsType = this.GetCsTypeInfo(loc3[object_id][column]); + } + + sql = string.Format(@" +select + a.object_id 'Object_id' +,c.name 'Column' +,b.index_id 'Index_id' +,b.is_unique 'IsUnique' +,b.is_primary_key 'IsPrimaryKey' +,cast(case when b.type_desc = 'CLUSTERED' then 1 else 0 end as bit) 'IsClustered' +,case when a.is_descending_key = 1 then 2 when a.is_descending_key = 0 then 1 else 0 end 'IsDesc' +from sys.index_columns a +inner join sys.indexes b on b.object_id = a.object_id and b.index_id = a.index_id +left join sys.columns c on c.object_id = a.object_id and c.column_id = a.column_id +where a.object_id in ({0}) +", loc8); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var indexColumns = new Dictionary>>(); + var uniqueColumns = new Dictionary>>(); + foreach (object[] row in ds) { + int object_id = int.Parse(string.Concat(row[0])); + string column = string.Concat(row[1]); + int index_id = int.Parse(string.Concat(row[2])); + bool is_unique = bool.Parse(string.Concat(row[3])); + bool is_primary_key = bool.Parse(string.Concat(row[4])); + bool is_clustered = bool.Parse(string.Concat(row[5])); + int is_desc = int.Parse(string.Concat(row[6])); + + if (loc3.ContainsKey(object_id) == false || loc3[object_id].ContainsKey(column) == false) continue; + DbColumnInfo loc9 = loc3[object_id][column]; + if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key; + + Dictionary> loc10 = null; + List loc11 = null; + if (!indexColumns.TryGetValue(object_id, out loc10)) + indexColumns.Add(object_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + if (is_unique) { + if (!uniqueColumns.TryGetValue(object_id, out loc10)) + uniqueColumns.Add(object_id, loc10 = new Dictionary>()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new List()); + loc11.Add(loc9); + } + } + foreach (var object_id in indexColumns.Keys) { + foreach (List columns in indexColumns[object_id].Values) + loc2[object_id].Indexes.Add(columns); + } + foreach (var object_id in uniqueColumns.Keys) { + foreach (var columns in uniqueColumns[object_id].Values) { + columns.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc2[object_id].Uniques.Add(columns); + } + } + + sql = string.Format(@" +select + b.object_id 'Object_id' +,c.name 'Column' +,a.constraint_object_id 'FKId' +,referenced_object_id +,cast(1 as bit) 'IsForeignKey' +,d.name 'Referenced_Column' +,null 'Referenced_Sln' +,null 'Referenced_Table' +from sys.foreign_key_columns a +inner join sys.tables b on b.object_id = a.parent_object_id +inner join sys.columns c on c.object_id = a.parent_object_id and c.column_id = a.parent_column_id +inner join sys.columns d on d.object_id = a.referenced_object_id and d.column_id = a.referenced_column_id +where b.object_id in ({0}) +", loc8); + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var fkColumns = new Dictionary>(); + foreach (object[] row in ds) { + int object_id, fk_id, referenced_object_id; + int.TryParse(string.Concat(row[0]), out object_id); + var column = string.Concat(row[1]); + int.TryParse(string.Concat(row[2]), out fk_id); + int.TryParse(string.Concat(row[3]), out referenced_object_id); + var is_foreign_key = bool.Parse(string.Concat(row[4])); + var referenced_column = string.Concat(row[5]); + var referenced_db = string.Concat(row[6]); + var referenced_table = string.Concat(row[7]); + DbColumnInfo loc9 = loc3[object_id][column]; + DbTableInfo loc10 = null; + DbColumnInfo loc11 = null; + bool isThisSln = referenced_object_id != 0; + + if (isThisSln) { + loc10 = loc2[referenced_object_id]; + loc11 = loc3[referenced_object_id][referenced_column]; + } else { + + } + Dictionary loc12 = null; + DbForeignInfo loc13 = null; + if (!fkColumns.TryGetValue(object_id, out loc12)) + fkColumns.Add(object_id, loc12 = new Dictionary()); + if (!loc12.TryGetValue(fk_id, out loc13)) + loc12.Add(fk_id, new DbForeignInfo { Table = loc2[object_id], ReferencedTable = loc10 }); + loc13.Columns.Add(loc9); + loc13.ReferencedColumns.Add(loc11); + } + foreach (var table_id in fkColumns.Keys) + foreach (var fk in fkColumns[table_id].Values) + loc2[table_id].Foreigns.Add(fk); + + foreach (var table_id in loc3.Keys) { + foreach (var loc5 in loc3[table_id].Values) { + loc2[table_id].Columns.Add(loc5); + if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5); + if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5); + } + } + foreach (var loc4 in loc2.Values) { + if (loc4.Primarys.Count == 0 && loc4.Uniques.Count > 0) { + foreach (var loc5 in loc4.Uniques[0]) { + loc5.IsPrimary = true; + loc4.Primarys.Add(loc5); + } + } + loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc4.Columns.Sort((c1, c2) => { + int compare = c2.IsPrimary.CompareTo(c1.IsPrimary); + if (compare == 0) { + bool b1 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c1.Name) != null) != null; + bool b2 = loc4.Foreigns.Find(fk => fk.Columns.Find(c3 => c3.Name == c2.Name) != null) != null; + compare = b2.CompareTo(b1); + } + if (compare == 0) compare = c1.Name.CompareTo(c2.Name); + return compare; + }); + loc1.Add(loc4); + } + loc1.Sort((t1, t2) => { + var ret = t1.Schema.CompareTo(t2.Schema); + if (ret == 0) ret = t1.Name.CompareTo(t2.Name); + return ret; + }); + + loc2.Clear(); + loc3.Clear(); + return loc1; + } + } +} \ No newline at end of file diff --git a/FreeSql/SqlServer/SqlServerExpression.cs b/FreeSql/SqlServer/SqlServerExpression.cs new file mode 100644 index 00000000..709d472a --- /dev/null +++ b/FreeSql/SqlServer/SqlServerExpression.cs @@ -0,0 +1,82 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace FreeSql.SqlServer { + class SqlServerExpression : CommonExpression { + + public SqlServerExpression(CommonUtils common) : base(common) { } + + internal override string ExpressionLambdaToSqlCall(MethodCallExpression exp, List _tables, List _selectColumnMap, SelectTableInfoType tbtype, bool isQuoteName) { + if (exp.Object.Type.FullName == "System.String") { + var left = ExpressionLambdaToSql(exp.Object, _tables, _selectColumnMap, tbtype, isQuoteName); + switch (exp.Method.Name) { + case "StartsWith": + case "EndsWith": + case "Contains": + var args0Value = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (args0Value == "NULL") return $"({left}) IS NULL"; + if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"concat('%', {args0Value})")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"concat({args0Value}, '%')")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) like concat('%', {args0Value}, '%')"; + case "ToLower": return $"lower({left})"; + case "ToUpper": return $"upper({left})"; + case "Substring": return $"substr({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)} + 1, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Length": return $"char_length({left})"; + case "IndexOf": + var indexOfFindStr = ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName); + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"(locate({left}, {indexOfFindStr}, ParseLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName) + 1) - 1)"; + return $"(locate({left}, {indexOfFindStr}) - 1)"; + case "PadLeft": return $"lpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PadRight": return $"rpad({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Trim": + case "TrimStart": + case "TrimEnd": + if (exp.Arguments.Count == 0) { + if (exp.Method.Name == "Trim") return $"trim({left})"; + if (exp.Method.Name == "TrimStart") return $"ltrim({left})"; + if (exp.Method.Name == "TrimStart") return $"rtrim({left})"; + } + foreach (var argsTrim01 in exp.Arguments) { + if (exp.Method.Name == "Trim") left = $"trim({ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(leading {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + if (exp.Method.Name == "TrimStart") left = $"trim(trailing {ExpressionLambdaToSql(argsTrim01, _tables, _selectColumnMap, tbtype, isQuoteName)} from {left})"; + } + return left; + case "Replace": return $"replace({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "CompareTo": return $"strcmp({left}, {ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + } + } + + if (exp.Object.Type.FullName == "System.Math") { + switch (exp.Method.Name) { + case "Abs": return $"abs({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sign": return $"sign({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Floor": return $"floor({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Round": + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + return $"round({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Exp": return $"exp({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log": return $"log({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Log10": return $"log10({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Pow": return $"pow({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sqrt": return $"sqrt({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "PI": return $"pi()"; + case "Cos": return $"cos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Sin": return $"sin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Tan": return $"tan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Acos": return $"acos({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Asin": return $"asin({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan": return $"atan({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Atan2": return $"atan2({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, {ExpressionLambdaToSql(exp.Arguments[1], _tables, _selectColumnMap, tbtype, isQuoteName)})"; + case "Truncate": return $"truncate({ExpressionLambdaToSql(exp.Arguments[0], _tables, _selectColumnMap, tbtype, isQuoteName)}, 0)"; + } + } + + throw new Exception($"SqlServerExpression 未现实函数表达式 {exp} 解析"); + } + } +} diff --git a/FreeSql/SqlServer/SqlServerProvider.cs b/FreeSql/SqlServer/SqlServerProvider.cs new file mode 100644 index 00000000..807c116e --- /dev/null +++ b/FreeSql/SqlServer/SqlServerProvider.cs @@ -0,0 +1,50 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.SqlServer.Curd; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; + +namespace FreeSql.SqlServer { + + class SqlServerProvider : IFreeSql { + + public ISelect Select() where T1 : class => new SqlServerSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public ISelect Select(object dywhere) where T1 : class => new SqlServerSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IInsert Insert() where T1 : class => new SqlServerInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + public IInsert Insert(T1 source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(IEnumerable source) where T1 : class => this.Insert().AppendData(source); + public IUpdate Update() where T1 : class => new SqlServerUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IUpdate Update(object dywhere) where T1 : class => new SqlServerUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IDelete Delete() where T1 : class => new SqlServerDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IDelete Delete(object dywhere) where T1 : class => new SqlServerDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public IAdo Ado { get; } + public ICache Cache { get; } + public ICodeFirst CodeFirst { get; } + public IDbFirst DbFirst { get; } + public SqlServerProvider(IDistributedCache cache, IConfiguration cacheStrategy, string masterConnectionString, string[] slaveConnectionString, ILogger log) { + CacheStrategy = cacheStrategy; + if (log == null) log = new LoggerFactory(new[] { new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() }).CreateLogger("FreeSql.SqlServer"); + + this.InternalCommonUtils = new SqlServerUtils(this); + this.InternalCommonExpression = new SqlServerExpression(this.InternalCommonUtils); + + this.Cache = new CacheProvider(cache, log); + this.Ado = new SqlServerAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + + this.DbFirst = new SqlServerDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.InternalCommonUtils.CodeFirst = this.CodeFirst = new SqlServerCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + } + + internal CommonUtils InternalCommonUtils { get; } + internal CommonExpression InternalCommonExpression { get; } + internal IConfiguration CacheStrategy { get; private set; } + + public void Transaction(Action handler) => Ado.Transaction(handler); + + public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout); + } +} diff --git a/FreeSql/SqlServer/SqlServerUtils.cs b/FreeSql/SqlServer/SqlServerUtils.cs new file mode 100644 index 00000000..5671eedd --- /dev/null +++ b/FreeSql/SqlServer/SqlServerUtils.cs @@ -0,0 +1,49 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; + +namespace FreeSql.SqlServer { + + class SqlServerUtils : CommonUtils { + IFreeSql _orm; + public SqlServerUtils(IFreeSql mysql) { + _orm = mysql; + } + + internal override DbParameter AppendParamter(List _params, string parameterName, object value) { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + SqlParameter ret = null; + if (value == null) ret = new SqlParameter { ParameterName = $"{parameterName}", Value = DBNull.Value }; + else { + var type = value.GetType(); + ret = new SqlParameter { + ParameterName = parameterName, + Value = value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.SqlDbType = (SqlDbType)tp.Value; + } + _params?.Add(ret); + return ret; + } + + internal override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, "@", (name, type, value) => { + var cp = new SqlParameter { + ParameterName = name, + Value = value ?? DBNull.Value + }; + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) cp.SqlDbType = (SqlDbType)tp.Value; + return cp; + }); + + internal override string FormatSql(string sql, params object[] args) => sql?.FormatSqlServer(args); + internal override string QuoteSqlName(string name) => $"[{name.TrimStart('[').TrimEnd(']').Replace(".", "].[")}]"; + internal override string QuoteParamterName(string name) => $"@{name}"; + internal override string IsNull(string sql, object value) => $"isnull({sql}, {value})"; + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ab602974 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Templates/MySql/include/enumtype.tpl b/Templates/MySql/include/enumtype.tpl new file mode 100644 index 00000000..81467c5c --- /dev/null +++ b/Templates/MySql/include/enumtype.tpl @@ -0,0 +1,71 @@ +{% +var dbf = dbfirst as FreeSql.IDbFirst; +var fks = (table.Foreigns as List); + +Func UString = stra => stra.Substring(0, 1).ToUpper() + stra.Substring(1); +Func GetCsType = cola3 => { + if (cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { + return $"{UString(cola3.Table.Name)}{cola3.Name.ToUpper()}{(cola3.IsNullable ? "?" : "")}"; + } + return dbf.GetCsType(cola3); +}; +Func GetFkObjectName = fkx => { + var eqfks = fks.Where(fk22a => fk22a.ReferencedTable.Name == fkx.ReferencedTable.Name); + if (eqfks.Count() == 1) return "Obj_" + fkx.ReferencedTable.Name; + var fkretname = fkx.Columns[0].Name; + if (fkretname.EndsWith(fkx.ReferencedColumns[0].Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedColumns[0].Name.Length).TrimEnd('_'); + if (fkretname.EndsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedTable.Name.Length).TrimEnd('_'); + if (fkretname.StartsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(fkx.ReferencedTable.Name.Length).TrimStart('_'); + return "Obj_" + fkx.ReferencedTable.Name + (string.IsNullOrEmpty(fkretname) ? "" : ("_" + fkretname)); +}; + + + foreach (var col11 in table.Columns) { + if (col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { + if (col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) print("\r\n\t[Flags]"); + print($"\r\n\tpublic enum {UString(table.Name)}{col11.Name.ToUpper()}"); + if (col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) print(" : long"); + print (" {\r\n\t\t"); + + string slkdgjlksdjg = ""; + int field_idx = 0; + int unknow_idx = 0; + string exp2 = string.Concat(col11.DbTypeTextFull); + int quote_pos = -1; + while (true) { + int first_pos = quote_pos = exp2.IndexOf('\'', quote_pos + 1); + if (quote_pos == -1) break; + while (true) { + quote_pos = exp2.IndexOf('\'', quote_pos + 1); + if (quote_pos == -1) break; + int r_cout = 0; + //for (int p = 1; true; p++) { + // if (exp2[quote_pos - p] == '\\') r_cout++; + // else break; + //} + while(exp2[++quote_pos] == '\'') r_cout++; + if (r_cout % 2 == 0/* && quote_pos - first_pos > 2*/) { + string str2 = exp2.Substring(first_pos + 1, quote_pos - first_pos - 2).Replace("''", "'"); + if (Regex.IsMatch(str2, @"^[\u0391-\uFFE5a-zA-Z_\$][\u0391-\uFFE5a-zA-Z_\$\d]*$")) + slkdgjlksdjg += ", " + str2; + else + slkdgjlksdjg += string.Format(@", +/// +/// {0} +/// +[Description(""{0}"")] +Unknow{1}", str2.Replace("\"", "\\\""), ++unknow_idx); + if (col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) + slkdgjlksdjg += " = " + Math.Pow(2, field_idx++); + if (col11.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum && field_idx++ == 0) + slkdgjlksdjg += " = 1"; + break; + } + } + if (quote_pos == -1) break; + } + print(slkdgjlksdjg.Substring(2).TrimStart('\r', '\n', '\t')); + print("\r\n\t}"); + } + } +%} \ No newline at end of file diff --git a/Templates/MySql/rich-entity-navigation-object/Const.cs b/Templates/MySql/rich-entity-navigation-object/Const.cs new file mode 100644 index 00000000..b959c3dc --- /dev/null +++ b/Templates/MySql/rich-entity-navigation-object/Const.cs @@ -0,0 +1,5 @@ +public static class Const { + public static IFreeSql mysql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10") + .Build(); +} diff --git a/Templates/MySql/rich-entity-navigation-object/Models/for-table.cs.freesql b/Templates/MySql/rich-entity-navigation-object/Models/for-table.cs.freesql new file mode 100644 index 00000000..e9be6c3a --- /dev/null +++ b/Templates/MySql/rich-entity-navigation-object/Models/for-table.cs.freesql @@ -0,0 +1,239 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Newtonsoft.Json; +using FreeSql; +using FreeSql.DataAnnotations; +{% +var dbf = dbfirst as FreeSql.IDbFirst; +var cols = (table.Columns as List); +var pks = (table.Primarys as List); +var fks = (table.Foreigns as List); + +Func UString = stra => stra.Substring(0, 1).ToUpper() + stra.Substring(1); +Func GetCsType = cola3 => { + if (cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { + return $"{UString(cola3.Table.Name)}{cola3.Name.ToUpper()}{(cola3.IsNullable ? "?" : "")}"; + } + return dbf.GetCsType(cola3); +}; +Func GetFkObjectName = fkx => { + var eqfks = fks.Where(fk22a => fk22a.ReferencedTable.Name == fkx.ReferencedTable.Name); + if (eqfks.Count() == 1) return "Obj_" + fkx.ReferencedTable.Name; + var fkretname = fkx.Columns[0].Name; + if (fkretname.EndsWith(fkx.ReferencedColumns[0].Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedColumns[0].Name.Length).TrimEnd('_'); + if (fkretname.EndsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedTable.Name.Length).TrimEnd('_'); + if (fkretname.StartsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(fkx.ReferencedTable.Name.Length).TrimStart('_'); + return "Obj_" + fkx.ReferencedTable.Name + (string.IsNullOrEmpty(fkretname) ? "" : ("_" + fkretname)); +}; + +%} +namespace test.Model { + + [JsonObject(MemberSerialization.OptIn), Table(Name = "{#!string.IsNullOrEmpty(table.Schema) ? table.Schema + "." : ""}{#table.Name}"{if cols.Where(cola003 => cola003.Name.ToLower() == "is_deleted" || cola003.Name.ToLower() == "isdeleted").Any()}, SelectFilter = "a.IsDeleted = 1"{/if})] + public partial class {#UString(table.Name)} {{for col,index in table.Columns}{% + var findfks = fks.Where(fkaa => fkaa.Columns.Where(fkaac1 => fkaac1.Name == col.Name).Any()); + %}{if findfks.Any() == false} + {if string.IsNullOrEmpty(col.Coment) == false}/// + /// {#col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")} + /// {/if} + [JsonProperty, Column(Name = "{#col.Name}", DbType = "{#col.DbTypeTextFull}"{if col.IsPrimary == true}, IsPrimary = true{/if}{if col.IsIdentity == true}, IsIdentity = true{/if}{if col.IsNullable == true}, IsNullable = true{/if})] + public {#GetCsType(col)} {#UString(col.Name)} { get; set; } + {else} + private {#GetCsType(col)} _{#UString(col.Name)}; + {if string.IsNullOrEmpty(col.Coment) == false}/// + /// {#col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")} + /// {/if} + [JsonProperty, Column(Name = "{#col.Name}", DbType = "{#col.DbTypeTextFull}"{if col.IsPrimary == true}, IsPrimary = true{/if}{if col.IsIdentity == true}, IsIdentity = true{/if}{if col.IsNullable == true}, IsNullable = true{/if})] + public {#GetCsType(col)} {#UString(col.Name)} { get => _{#UString(col.Name)}; set { + if (_{#UString(col.Name)} == value) return; + _{#UString(col.Name)} = value;{for fkcok2 in findfks} + {#GetFkObjectName(fkcok2)} = null;{/for} + } } + {/if}{/for}{for fk in fks} + public {#UString(fk.ReferencedTable.Name)} {#GetFkObjectName(fk)} { get; set; } + {/for} + internal static IFreeSql mysql => Const.mysql; + public static ISelect<{#UString(table.Name)}> Select => mysql.Select<{#UString(table.Name)}>(); +{if (table.Uniques.Count > 0)} + public static int ItemCacheTimeout = 180;{for uk001 in table.Uniques}{% + + string parms = string.Empty; + string parmsByWhereLambda = string.Empty; + string parmsBy = "By"; + string parmsNodeTypeUpdateCacheRemove = string.Empty; + foreach (var uk001col in uk001) { + string getcstype = GetCsType(uk001col); + parms += getcstype.Replace("?", "") + " " + UString(uk001col.Name) + ", "; + parmsByWhereLambda += "a." + UString(uk001col.Name) + " == " + UString(uk001col.Name) + " && "; + parmsBy += UString(uk001col.Name) + "And"; + parmsNodeTypeUpdateCacheRemove += "item." + UString(uk001col.Name) + ", \"_,_\", "; + } + parms = parms.Substring(0, parms.Length - 2); + parmsByWhereLambda = parmsByWhereLambda.Substring(0, parmsByWhereLambda.Length - 4); + parmsBy = parmsBy.Substring(0, parmsBy.Length - 3); + parmsNodeTypeUpdateCacheRemove = parmsNodeTypeUpdateCacheRemove.Substring(0, parmsNodeTypeUpdateCacheRemove.Length - 9); + %} + public static {#UString(table.Name)} GetItem{#uk001[0].IsPrimary ? string.Empty : parmsBy}({#parms}) => Select.Where(a => {#parmsByWhereLambda}).Caching(ItemCacheTimeout, string.Concat("test:{#table.Name}{#uk001[0].IsPrimary ? string.Empty : parmsBy}:", {#parmsNodeTypeUpdateCacheRemove.Replace("item.", "")})).ToOne();{/for} +{/if}{if (table.Uniques.Count > 0)}{for uk001 in table.Uniques}{% + + string parms = string.Empty; + string parmsByWhereLambda = string.Empty; + string parmsNewItem = string.Empty; + string parmsBy = "By"; + string parmsNoneType = string.Empty; + foreach (var uk001col in uk001) { + string getcstype = GetCsType(uk001col); + parms += getcstype.Replace("?", "") + " " + UString(uk001col.Name) + ", "; + parmsByWhereLambda += "a." + UString(uk001col.Name) + " == " + UString(uk001col.Name) + " && "; + parmsNewItem += UString(uk001col.Name) + " = " + UString(uk001col.Name) + ", "; + parmsBy += UString(uk001col.Name) + "And"; + parmsNoneType += UString(uk001col.Name) + ", "; + } + parms = parms.Substring(0, parms.Length - 2); + parmsByWhereLambda = parmsByWhereLambda.Substring(0, parmsByWhereLambda.Length - 4); + parmsNewItem = parmsNewItem.Substring(0, parmsNewItem.Length - 2); + parmsBy = parmsBy.Substring(0, parmsBy.Length - 3); + parmsNoneType = parmsNoneType.Substring(0, parmsNoneType.Length - 2); + %} + public static long Delete{#uk001[0].IsPrimary ? string.Empty : parmsBy}({#parms}) { + var affrows = mysql.Delete<{#UString(table.Name)}>().Where(a => {#parmsByWhereLambda}).ExecuteAffrows();{if table.Uniques.Count > 1} + if (ItemCacheTimeout > 0) RemoveCache(GetItem{#uk001[0].IsPrimary ? string.Empty : parmsBy}({#parmsNoneType}));{else} + if (ItemCacheTimeout > 0) RemoveCache(new {#UString(table.Name)} { {#parmsNewItem} });{/if} + return affrows; + }{/for} +{/if}{for fkcoldel in fks}{% + string parms = string.Empty; + string parmsBy = "By"; + string parmsByWhereLambda = string.Empty; + foreach( var fkcoldelcol in fkcoldel.Columns) { + string getcstype = GetCsType(fkcoldelcol); + parms += getcstype.Replace("?", "") + " " + UString(fkcoldelcol.Name) + ", "; + parmsBy += UString(fkcoldelcol.Name) + "And"; + parmsByWhereLambda += "a." + UString(fkcoldelcol.Name) + " == " + UString(fkcoldelcol.Name) + " && "; + } + parms = parms.Substring(0, parms.Length - 2); + parmsBy = parmsBy.Substring(0, parmsBy.Length - 3); + parmsByWhereLambda = parmsByWhereLambda.Substring(0, parmsByWhereLambda.Length - 4); + %} + public static long DeleteBy{#parmsBy}({#parms}) { + return mysql.Delete<{#UString(table.Name)}>().Where(a => {#parmsByWhereLambda}).ExecuteAffrows(); //删除缓存 + } +{/for}{if (table.Uniques.Count > 0)}{% string redisRemoveCode = string.Empty; %}{for uk001 in table.Uniques}{% + + string parmsBy = "By"; + string parmsNodeTypeUpdateCacheRemove = string.Empty; + foreach (var uk001col in uk001) { + parmsBy += UString(uk001col.Name) + "And"; + parmsNodeTypeUpdateCacheRemove += "item." + UString(uk001col.Name) + ", \"_,_\", "; + } + parmsBy = parmsBy.Substring(0, parmsBy.Length - 3); + parmsNodeTypeUpdateCacheRemove = parmsNodeTypeUpdateCacheRemove.Substring(0, parmsNodeTypeUpdateCacheRemove.Length - 9); + redisRemoveCode += $@" + keys[keysIdx++] = string.Concat(""test:{table.Name}{(uk001[0].IsPrimary ? string.Empty : parmsBy)}:"", {parmsNodeTypeUpdateCacheRemove});"; + %}{/for} + internal static void RemoveCache({#UString(table.Name)} item) => RemoveCache(item == null ? null : new [] { item }); + internal static void RemoveCache(IEnumerable<{#UString(table.Name)}> items) { + if (ItemCacheTimeout <= 0 || items == null || items.Any() == false) return; + var keys = new string[items.Count() * {#table.Uniques.Count}]; + var keysIdx = 0; + foreach (var item in items) {{#redisRemoveCode} + } + if (mysql.Ado.TransactionCurrentThread != null) mysql.Ado.TransactionPreRemoveCache(keys); + else mysql.Cache.Remove(keys); + } +{/if}{if (table.Columns.Count < 100)}{% + string CsParam3 = string.Empty; + string CsParamNoType3 = string.Empty; + string parms = string.Empty; + string parmsByWhereLambda = string.Empty; + string parmsNewItem = string.Empty; + string parmsBy = "By"; + string parmsNoneType = string.Empty; + var idens = 0; + var idensCol = table.Columns[0]; + foreach (var uk001col in table.Columns) { + string getcstype = GetCsType(uk001col); + parms += getcstype.Replace("?", "") + " " + UString(uk001col.Name) + ", "; + if (uk001col.IsPrimary) + parmsByWhereLambda += "a." + UString(uk001col.Name) + " == " + UString(uk001col.Name) + " && "; + parmsNewItem += UString(uk001col.Name) + " = " + UString(uk001col.Name) + ", "; + parmsBy += UString(uk001col.Name) + "And"; + parmsNoneType += UString(uk001col.Name) + ", "; + if (uk001col.IsIdentity) { + idens++; + idensCol = uk001col; + } else { + if (getcstype.StartsWith("DateTime") && (uk001col.Name.ToLower() == "create_time" || uk001col.Name.ToLower() == "update_time" || uk001col.Name.ToLower() == "createtime" || uk001col.Name.ToLower() == "updatetime") || + getcstype == "bool?" && (uk001col.Name.ToLower() == "is_deleted" || uk001col.Name.ToLower() == "isdeleted")) { + + } else { + CsParam3 += getcstype + " " + UString(uk001col.Name) + ", "; + CsParamNoType3 += string.Format("\r\n {0} = {0}, ", UString(uk001col.Name)); + } + } + } + parms = parms.Substring(0, parms.Length - 2); + if (parmsByWhereLambda.Length > 0) parmsByWhereLambda = parmsByWhereLambda.Substring(0, parmsByWhereLambda.Length - 4); + parmsNewItem = parmsNewItem.Substring(0, parmsNewItem.Length - 2); + parmsBy = parmsBy.Substring(0, parmsBy.Length - 3); + parmsNoneType = parmsNoneType.Substring(0, parmsNoneType.Length - 2); + if (CsParam3.Length > 0) CsParam3 = CsParam3.Substring(0, CsParam3.Length - 2); + if (CsParamNoType3.Length > 0) CsParamNoType3 = CsParamNoType3.Substring(0, CsParamNoType3.Length - 2); + %}{if idens > 0} + public static {#GetCsType(idensCol)} Insert({#UString(table.Name)} item) { + item.{#UString(idensCol.Name)} = ({#GetCsType(idensCol)})mysql.Insert<{#UString(table.Name)}>().AppendData(item).ExecuteIdentity(); + if (ItemCacheTimeout > 0) RemoveCache(item); + return item.{#UString(idensCol.Name)}; + }{else} + public static void Insert({#UString(table.Name)} item) { + if (ItemCacheTimeout > 0) RemoveCache(item); + mysql.Insert<{#UString(table.Name)}>().AppendData(item).ExecuteAffrows(); + } + public static long Insert(IEnumerable<{#UString(table.Name)}> items) { + if (ItemCacheTimeout > 0) RemoveCache(items); + return mysql.Insert<{#UString(table.Name)}>().AppendData(items).ExecuteAffrows(); + }{/if} + {if CsParamNoType3.Split('=').Length <= 5}{if idens > 0} + public static {#GetCsType(idensCol)} Insert({#CsParam3}) { + var item = new {#UString(table.Name)} {{#CsParamNoType3}}; + item.{#UString(idensCol.Name)} = ({#GetCsType(idensCol)})mysql.Insert<{#UString(table.Name)}>().AppendData(new {#UString(table.Name)} {{#CsParamNoType3}}).ExecuteIdentity(); + if (ItemCacheTimeout > 0) RemoveCache(item); + return item.{#UString(idensCol.Name)}; + }{else} + public static {#UString(table.Name)} Insert({#CsParam3}) { + var item = new {#UString(table.Name)} {{#CsParamNoType3}}; + mysql.Insert<{#UString(table.Name)}>().AppendData(item).ExecuteAffrows(); + if (ItemCacheTimeout > 0) RemoveCache(item); + return item; + }{/if}{/if} + public static long Update({#UString(table.Name)} item) { + if (ItemCacheTimeout > 0) RemoveCache(item); + return mysql.Update<{#UString(table.Name)}>().SetSource(item).ExecuteAffrows(); + } + public static long Update(IEnumerable<{#UString(table.Name)}> items) { + if (ItemCacheTimeout > 0) RemoveCache(items); + return mysql.Update<{#UString(table.Name)}>().SetSource(items).ExecuteAffrows(); + } + /// + /// 指定字段更新 + /// + public IUpdate<{#UString(table.Name)}> UpdateDiy => mysql.Update<{#UString(table.Name)}>().Where(a => {#parmsByWhereLambda}); + /// + /// {if pks.Count > 0}保存或添加,如果主键有值则尝试 Update,如果影响的行为 0 则尝试 Insert{else}添加{/if} + /// + public void Save() {{if pks.Count > 0} + if ({#string.Join(" && ", pks.Select(pkssa => "this." + UString(pkssa.Name) + " != default(" + GetCsType(pkssa) + ")"))}) { + var affrows = mysql.Update<{#UString(table.Name)}>().Where(a => {#parmsByWhereLambda}).ExecuteAffrows(); + if (affrows > 0) return; + }{/if} + {if pks.Count > 0}this.{#UString(idensCol.Name)} = {#GetCsType(idensCol).Replace("?", "") == "long" ? "" : ("(" + GetCsType(idensCol) + ")")}mysql.Insert<{#UString(table.Name)}>().AppendData(this).ExecuteIdentity();{else}mysql.Insert<{#UString(table.Name)}>().AppendData(this).ExecuteAffrows();{/if} + } +{/if} + } +{include ../../include/enumtype.tpl} +} \ No newline at end of file diff --git a/Templates/MySql/rich-entity-navigation-object/readme.md b/Templates/MySql/rich-entity-navigation-object/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/Templates/MySql/simple-entity-navigation-object/Models/for-table.cs.freesql b/Templates/MySql/simple-entity-navigation-object/Models/for-table.cs.freesql new file mode 100644 index 00000000..7be41bf1 --- /dev/null +++ b/Templates/MySql/simple-entity-navigation-object/Models/for-table.cs.freesql @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Newtonsoft.Json; +using FreeSql.DataAnnotations; +{% +var dbf = dbfirst as FreeSql.IDbFirst; +var cols = (table.Columns as List); +var pks = (table.Primarys as List); +var fks = (table.Foreigns as List); + +Func UString = stra => stra.Substring(0, 1).ToUpper() + stra.Substring(1); +Func GetCsType = cola3 => { + if (cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { + return $"{UString(cola3.Table.Name)}{cola3.Name.ToUpper()}{(cola3.IsNullable ? "?" : "")}"; + } + return dbf.GetCsType(cola3); +}; +Func GetFkObjectName = fkx => { + var eqfks = fks.Where(fk22a => fk22a.ReferencedTable.Name == fkx.ReferencedTable.Name); + if (eqfks.Count() == 1) return "Obj_" + fkx.ReferencedTable.Name; + var fkretname = fkx.Columns[0].Name; + if (fkretname.EndsWith(fkx.ReferencedColumns[0].Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedColumns[0].Name.Length).TrimEnd('_'); + if (fkretname.EndsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedTable.Name.Length).TrimEnd('_'); + if (fkretname.StartsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(fkx.ReferencedTable.Name.Length).TrimStart('_'); + return "Obj_" + fkx.ReferencedTable.Name + (string.IsNullOrEmpty(fkretname) ? "" : ("_" + fkretname)); +}; +%} +namespace test.Model { + + [JsonObject(MemberSerialization.OptIn), Table(Name = "{#!string.IsNullOrEmpty(table.Schema) ? table.Schema + "." : ""}{#table.Name}"{if cols.Where(cola003 => cola003.Name.ToLower() == "is_deleted" || cola003.Name.ToLower() == "isdeleted").Any()}, SelectFilter = "a.IsDeleted = 1"{/if})] + public partial class {#UString(table.Name)} {{for col,index in table.Columns}{% + var findfks = fks.Where(fkaa => fkaa.Columns.Where(fkaac1 => fkaac1.Name == col.Name).Any()); + %}{if findfks.Any() == false} + {if string.IsNullOrEmpty(col.Coment) == false}/// + /// {#col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")} + /// {/if} + [JsonProperty, Column(Name = "{#col.Name}", DbType = "{#col.DbTypeTextFull}"{if col.IsPrimary == true}, IsPrimary = true{/if}{if col.IsIdentity == true}, IsIdentity = true{/if}{if col.IsNullable == true}, IsNullable = true{/if})] + public {#GetCsType(col)} {#UString(col.Name)} { get; set; } + {else} + private {#GetCsType(col)} _{#UString(col.Name)}; + {if string.IsNullOrEmpty(col.Coment) == false}/// + /// {#col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")} + /// {/if} + [JsonProperty, Column(Name = "{#col.Name}", DbType = "{#col.DbTypeTextFull}"{if col.IsPrimary == true}, IsPrimary = true{/if}{if col.IsIdentity == true}, IsIdentity = true{/if}{if col.IsNullable == true}, IsNullable = true{/if})] + public {#GetCsType(col)} {#UString(col.Name)} { get => _{#UString(col.Name)}; set { + if (_{#UString(col.Name)} == value) return; + _{#UString(col.Name)} = value;{for fkcok2 in findfks} + {#GetFkObjectName(fkcok2)} = null;{/for} + } } + {/if}{/for}{for fk in fks} + public {#UString(fk.ReferencedTable.Name)} {#GetFkObjectName(fk)} { get; set; } + {/for} + } +{include ../../include/enumtype.tpl} +} \ No newline at end of file diff --git a/Templates/MySql/simple-entity-navigation-object/readme.md b/Templates/MySql/simple-entity-navigation-object/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/Templates/MySql/simple-entity/Models/for-table.cs.freesql b/Templates/MySql/simple-entity/Models/for-table.cs.freesql new file mode 100644 index 00000000..aa7e88fd --- /dev/null +++ b/Templates/MySql/simple-entity/Models/for-table.cs.freesql @@ -0,0 +1,33 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Newtonsoft.Json; +using FreeSql.DataAnnotations; +{% +var dbf = dbfirst as FreeSql.IDbFirst; +var cols = (table.Columns as List); + +Func UString = stra => stra.Substring(0, 1).ToUpper() + stra.Substring(1); +Func GetCsType = cola3 => { + if (cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Enum || cola3.DbType == (int)MySql.Data.MySqlClient.MySqlDbType.Set) { + return $"{UString(cola3.Table.Name)}{cola3.Name.ToUpper()}{(cola3.IsNullable ? "?" : "")}"; + } + return dbf.GetCsType(cola3); +}; +%} +namespace test.Model { + + [JsonObject(MemberSerialization.OptIn), Table(Name = "{#!string.IsNullOrEmpty(table.Schema) ? table.Schema + "." : ""}{#table.Name}"{if cols.Where(cola003 => cola003.Name.ToLower() == "is_deleted" || cola003.Name.ToLower() == "isdeleted").Any()}, SelectFilter = "a.IsDeleted = 1"{/if})] + public partial class {#UString(table.Name)} {{for col,index in table.Columns} + {if string.IsNullOrEmpty(col.Coment) == false}/// + /// {#col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n /// ")} + /// {/if} + [JsonProperty, Column(Name = "{#col.Name}", DbType = "{#col.DbTypeTextFull}"{if col.IsPrimary == true}, IsPrimary = true{/if}{if col.IsIdentity == true}, IsIdentity = true{/if}{if col.IsNullable == true}, IsNullable = true{/if})] + public {#GetCsType(col)} {#UString(col.Name)} { get; set; } + {/for} + } +{include ../../include/enumtype.tpl} +} \ No newline at end of file diff --git a/Templates/MySql/simple-entity/readme.md b/Templates/MySql/simple-entity/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..8ae33212 --- /dev/null +++ b/readme.md @@ -0,0 +1,10 @@ +# FreeSql + +* [Insert 插入数据](Docs/insert.md) +* [Update 更新数据](Docs/update.md) +* [Delete 删除数据](Docs/delete.md) +* [Select 查询数据](Docs/select.md) + +* [CodeFirst 快速开发](Docs/codefirst.md) +* [DbFirst 快速开发](Docs/dbfirst.md) +* [DbFirst 生成器](Docs/generator.md)