From 611c066481f5fe8b756a576ce65eb139453d3245 Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Wed, 26 Jun 2019 10:09:26 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=90=88=E5=B9=B6=20FreeSql.DbContext=20?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=87=B3=20FreeSql=20=E7=BB=B4=E6=8A=A4?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ValuesController.cs | 241 ++++++++++++++ .../dbcontext_01/DbContexts/SongContext.cs | 52 +++ Examples/dbcontext_01/Program.cs | 70 ++++ .../Properties/launchSettings.json | 27 ++ Examples/dbcontext_01/Startup.cs | 111 +++++++ .../dbcontext_01/appsettings.Development.json | 9 + Examples/dbcontext_01/appsettings.json | 8 + Examples/dbcontext_01/dbcontext_01.csproj | 21 ++ .../Controllers/SongController.cs | 98 ++++++ Examples/repository_01/Entitys/Song.cs | 11 + Examples/repository_01/Program.cs | 24 ++ .../Properties/launchSettings.json | 27 ++ Examples/repository_01/Startup.cs | 98 ++++++ .../appsettings.Development.json | 9 + Examples/repository_01/appsettings.json | 8 + Examples/repository_01/repository_01.csproj | 20 ++ .../FreeSql.Extensions.LazyLoading.csproj | 2 +- FreeSql.DbContext/DbContext/DbContext.cs | 154 +++++++++ FreeSql.DbContext/DbContext/DbContextAsync.cs | 119 +++++++ .../DbContext/DbContextOptions.cs | 10 + .../DbContext/DbContextOptionsBuilder.cs | 13 + FreeSql.DbContext/DbContext/DbContextSync.cs | 119 +++++++ FreeSql.DbContext/DbContext/FreeContext.cs | 10 + FreeSql.DbContext/DbSet/DbSet.cs | 281 ++++++++++++++++ FreeSql.DbContext/DbSet/DbSetAsync.cs | 268 +++++++++++++++ FreeSql.DbContext/DbSet/DbSetSync.cs | 305 ++++++++++++++++++ .../Extenssions/DependencyInjection.cs | 32 ++ .../FreeSqlDbContextExtenssions.cs | 38 +++ FreeSql.DbContext/FreeSql.DbContext.csproj | 36 +++ FreeSql.DbContext/FreeSql.DbContext.xml | 259 +++++++++++++++ .../ContextSet/RepositoryDbContext.cs | 62 ++++ .../Repository/ContextSet/RepositoryDbSet.cs | 60 ++++ .../ContextSet/RepositoryUnitOfWork.cs | 57 ++++ .../Repository/DataFilter/DataFilter.cs | 152 +++++++++ .../Repository/DataFilter/DataFilterUtil.cs | 89 +++++ .../Extenssions/DependencyInjection.cs | 39 +++ .../FreeSqlRepositoryExtenssions.cs | 65 ++++ .../Repository/Repository/BaseRepository.cs | 160 +++++++++ .../Repository/DefaultRepository.cs | 16 + .../Repository/Repository/GuidRepository.cs | 15 + .../Repository/Repository/IBaseRepository.cs | 30 ++ .../Repository/Repository/IBasicRepository.cs | 45 +++ .../Repository/IReadOnlyRepository.cs | 27 ++ FreeSql.DbContext/TempExtensions.cs | 5 + FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs | 34 ++ FreeSql.DbContext/UnitOfWork/UnitOfWork.cs | 103 ++++++ FreeSql.DbContext/readme.md | 284 ++++++++++++++++ FreeSql.Repository/FreeSql.Repository.csproj | 23 ++ FreeSql.Repository/readme.md | 132 ++++++++ .../FreeSql.Tests.PerformanceTests.csproj | 26 -- .../FreeSql.Tests.DbContext.csproj | 25 ++ .../RepositoryTests.cs | 261 +++++++++++++++ .../FreeSql.Tests.DbContext/UnitTest1.cs | 160 +++++++++ FreeSql.Tests/FreeSql.Tests.DbContext/g.cs | 86 +++++ .../FreeSql.Tests.PerformanceTests.csproj | 26 ++ .../MySqlAdoTest.cs | 0 .../FreeSql.Tests.PerformanceTests}/g.cs | 0 ...eeSql.Tests.Provider.MySqlConnector.csproj | 6 +- .../MySqlConnector/Curd/MySqlDeleteTest.cs | 0 .../MySqlConnector/Curd/MySqlInsertTest.cs | 0 .../MySqlConnector/Curd/MySqlSelectTest.cs | 0 .../MySqlConnector/Curd/MySqlUpdateTest.cs | 0 .../MapType/BoolNullableTest.cs | 0 .../MySqlConnector/MapType/BoolTest.cs | 0 .../MySqlConnector/MapType/EnumTest.cs | 0 .../MySqlConnector/MapType/ToStringTest.cs | 0 .../MySqlConnector/MySqlCodeFirstTest.cs | 0 .../MySqlConnectorAdo/MySqlAdoTest.cs | 0 .../MySqlConnectorExpression/ConvertTest.cs | 0 .../MySqlConnectorExpression/DateTimeTest.cs | 0 .../MySqlConnectorExpression/MathTest.cs | 0 .../MySqlConnectorExpression/OtherTest.cs | 0 .../MySqlConnectorExpression/StringTest.cs | 0 .../MySqlConnectorExpression/TimeSpanTest.cs | 0 .../MySqlConnector/MySqlDbFirstTest.cs | 0 .../g.cs | 0 FreeSql.Tests/{ => FreeSql.Tests}/Class1.cs | 0 .../DataAnnotations/MySqlFluentTest.cs | 0 .../DataAnnotations/SqlServerFluentTest.cs | 0 .../SqlServer/SqlServerCollection.cs | 0 .../DataContext/SqlServer/SqlServerFixture.cs | 0 .../GetDataReaderValueBlockExpressionTest.cs | 0 .../LambadaExpressionExtensionsTest.cs | 0 .../Extensions/StringExtensionsTest.cs | 0 .../{ => FreeSql.Tests}/FreeSql.Tests.csproj | 22 +- .../{ => FreeSql.Tests}/FreeSql.Tests.xml | 0 .../LinqToSql/SqliteLinqToSqlTests.cs | 0 .../MySql/Curd/MySqlDeleteTest.cs | 0 .../MySql/Curd/MySqlInsertTest.cs | 0 .../MySql/Curd/MySqlSelectTest.cs | 0 .../MySql/Curd/MySqlUpdateTest.cs | 0 .../MySql/MapType/BoolNullableTest.cs | 0 .../MySql/MapType/BoolTest.cs | 0 .../MySql/MapType/EnumTest.cs | 0 .../MySql/MapType/ToStringTest.cs | 0 .../MySql/MySqlAdo/MySqlAdoTest.cs | 0 .../MySql/MySqlCodeFirstTest.cs | 0 .../MySql/MySqlDbFirstTest.cs | 0 .../MySql/MySqlExpression/ConvertTest.cs | 0 .../MySql/MySqlExpression/DateTimeTest.cs | 0 .../MySql/MySqlExpression/MathTest.cs | 0 .../MySql/MySqlExpression/OtherTest.cs | 0 .../MySql/MySqlExpression/StringTest.cs | 0 .../MySql/MySqlExpression/TimeSpanTest.cs | 0 .../Oracle/Curd/OracleDeleteTest.cs | 0 .../Oracle/Curd/OracleInsertTest.cs | 0 .../Oracle/Curd/OracleSelectTest.cs | 0 .../Oracle/Curd/OracleUpdateTest.cs | 0 .../Oracle/MapType/BoolNullableTest.cs | 0 .../Oracle/MapType/BoolTest.cs | 0 .../Oracle/MapType/EnumTest.cs | 0 .../Oracle/MapType/ToStringTest.cs | 0 .../Oracle/OracleAdo/OracleAdoTest.cs | 0 .../Oracle/OracleCodeFirstTest.cs | 0 .../Oracle/OracleDbFirstTest.cs | 0 .../Oracle/OracleExpression/ConvertTest.cs | 0 .../Oracle/OracleExpression/DateTimeTest.cs | 0 .../Oracle/OracleExpression/MathTest.cs | 0 .../Oracle/OracleExpression/OtherTest.cs | 0 .../Oracle/OracleExpression/StringTest.cs | 0 .../Oracle/OracleExpression/TimeSpanTest.cs | 0 .../PostgreSQL/Curd/PostgreSQLDeleteTest.cs | 0 .../PostgreSQL/Curd/PostgreSQLInsertTest.cs | 0 .../PostgreSQL/Curd/PostgreSQLSelectTest.cs | 0 .../PostgreSQL/Curd/PostgreSQLUpdateTest.cs | 0 .../PostgreSQL/MapType/BoolNullableTest.cs | 0 .../PostgreSQL/MapType/BoolTest.cs | 0 .../PostgreSQL/MapType/EnumTest.cs | 0 .../PostgreSQL/MapType/JTokenTest.cs | 0 .../PostgreSQL/MapType/ToStringTest.cs | 0 .../PostgreSQLAdo/PostgreSQLAdoTest.cs | 0 .../PostgreSQL/PostgreSQLCodeFirstTest.cs | 0 .../PostgreSQL/PostgreSQLDbFirstTest.cs | 0 .../PostgreSQLExpression/ConvertTest.cs | 0 .../PostgreSQLExpression/DateTimeTest.cs | 0 .../PostgreSQLExpression/MathTest.cs | 0 .../PostgreSQLExpression/OtherTest.cs | 0 .../PostgreSQLExpression/StringTest.cs | 0 .../PostgreSQLExpression/TimeSpanTest.cs | 0 .../SqlServer/Curd/SqlServerDeleteTest.cs | 0 .../SqlServer/Curd/SqlServerInsertTest.cs | 0 .../SqlServer/Curd/SqlServerSelectTest.cs | 0 .../SqlServer/Curd/SqlServerUpdateTest.cs | 0 .../SqlServer/MapType/BoolNullableTest.cs | 0 .../SqlServer/MapType/BoolTest.cs | 0 .../SqlServer/MapType/EnumTest.cs | 0 .../SqlServer/MapType/ToStringTest.cs | 0 .../SqlServerAdo/SqlServerAdoTest.cs | 0 .../SqlServer/SqlServerCodeFirstTest.cs | 0 .../SqlServer/SqlServerDbFirstTest.cs | 0 .../SqlServerExpression/ConvertTest.cs | 0 .../SqlServerExpression/DateTimeTest.cs | 0 .../SqlServer/SqlServerExpression/MathTest.cs | 0 .../SqlServerExpression/OtherTest.cs | 0 .../SqlServerExpression/StringTest.cs | 0 .../SqlServerExpression/TimeSpanTest.cs | 0 .../Sqlite/Curd/SqliteDeleteTest.cs | 0 .../Sqlite/Curd/SqliteInsertTest.cs | 0 .../Sqlite/Curd/SqliteSelectTest.cs | 0 .../Sqlite/Curd/SqliteUpdateTest.cs | 0 .../Sqlite/MapType/BoolNullableTest.cs | 0 .../Sqlite/MapType/BoolTest.cs | 0 .../Sqlite/MapType/EnumTest.cs | 0 .../Sqlite/MapType/ToStringTest.cs | 0 .../Sqlite/SqliteAdo/SqliteAdoTest.cs | 0 .../Sqlite/SqliteCodeFirstTest.cs | 0 .../Sqlite/SqliteExpression/ConvertTest.cs | 0 .../Sqlite/SqliteExpression/DateTimeTest.cs | 0 .../Sqlite/SqliteExpression/MathTest.cs | 0 .../Sqlite/SqliteExpression/OtherTest.cs | 0 .../Sqlite/SqliteExpression/StringTest.cs | 0 .../Sqlite/SqliteExpression/TimeSpanTest.cs | 0 .../{ => FreeSql.Tests}/UnitTest1.cs | 12 +- .../{ => FreeSql.Tests}/UnitTest2.cs | 0 FreeSql.Tests/{ => FreeSql.Tests}/ftTests.xml | 0 FreeSql.Tests/{ => FreeSql.Tests}/g.cs | 0 FreeSql.sln | 154 ++++++--- FreeSql/FreeSql.csproj | 2 +- .../FreeSql.Provider.MySql.csproj | 2 +- .../FreeSql.Provider.MySqlConnector.csproj | 2 +- .../FreeSql.Provider.Oracle.csproj | 2 +- .../FreeSql.Provider.PostgreSQL.csproj | 2 +- .../FreeSql.Provider.SqlServer.csproj | 2 +- .../FreeSql.Provider.Sqlite.csproj | 2 +- readme.md | 2 +- 185 files changed, 4577 insertions(+), 95 deletions(-) create mode 100644 Examples/dbcontext_01/Controllers/ValuesController.cs create mode 100644 Examples/dbcontext_01/DbContexts/SongContext.cs create mode 100644 Examples/dbcontext_01/Program.cs create mode 100644 Examples/dbcontext_01/Properties/launchSettings.json create mode 100644 Examples/dbcontext_01/Startup.cs create mode 100644 Examples/dbcontext_01/appsettings.Development.json create mode 100644 Examples/dbcontext_01/appsettings.json create mode 100644 Examples/dbcontext_01/dbcontext_01.csproj create mode 100644 Examples/repository_01/Controllers/SongController.cs create mode 100644 Examples/repository_01/Entitys/Song.cs create mode 100644 Examples/repository_01/Program.cs create mode 100644 Examples/repository_01/Properties/launchSettings.json create mode 100644 Examples/repository_01/Startup.cs create mode 100644 Examples/repository_01/appsettings.Development.json create mode 100644 Examples/repository_01/appsettings.json create mode 100644 Examples/repository_01/repository_01.csproj create mode 100644 FreeSql.DbContext/DbContext/DbContext.cs create mode 100644 FreeSql.DbContext/DbContext/DbContextAsync.cs create mode 100644 FreeSql.DbContext/DbContext/DbContextOptions.cs create mode 100644 FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs create mode 100644 FreeSql.DbContext/DbContext/DbContextSync.cs create mode 100644 FreeSql.DbContext/DbContext/FreeContext.cs create mode 100644 FreeSql.DbContext/DbSet/DbSet.cs create mode 100644 FreeSql.DbContext/DbSet/DbSetAsync.cs create mode 100644 FreeSql.DbContext/DbSet/DbSetSync.cs create mode 100644 FreeSql.DbContext/Extenssions/DependencyInjection.cs create mode 100644 FreeSql.DbContext/Extenssions/FreeSqlDbContextExtenssions.cs create mode 100644 FreeSql.DbContext/FreeSql.DbContext.csproj create mode 100644 FreeSql.DbContext/FreeSql.DbContext.xml create mode 100644 FreeSql.DbContext/Repository/ContextSet/RepositoryDbContext.cs create mode 100644 FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs create mode 100644 FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWork.cs create mode 100644 FreeSql.DbContext/Repository/DataFilter/DataFilter.cs create mode 100644 FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs create mode 100644 FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs create mode 100644 FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs create mode 100644 FreeSql.DbContext/Repository/Repository/BaseRepository.cs create mode 100644 FreeSql.DbContext/Repository/Repository/DefaultRepository.cs create mode 100644 FreeSql.DbContext/Repository/Repository/GuidRepository.cs create mode 100644 FreeSql.DbContext/Repository/Repository/IBaseRepository.cs create mode 100644 FreeSql.DbContext/Repository/Repository/IBasicRepository.cs create mode 100644 FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs create mode 100644 FreeSql.DbContext/TempExtensions.cs create mode 100644 FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs create mode 100644 FreeSql.DbContext/UnitOfWork/UnitOfWork.cs create mode 100644 FreeSql.DbContext/readme.md create mode 100644 FreeSql.Repository/FreeSql.Repository.csproj create mode 100644 FreeSql.Repository/readme.md delete mode 100644 FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj create mode 100644 FreeSql.Tests/FreeSql.Tests.DbContext/FreeSql.Tests.DbContext.csproj create mode 100644 FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs create mode 100644 FreeSql.Tests/FreeSql.Tests.DbContext/UnitTest1.cs create mode 100644 FreeSql.Tests/FreeSql.Tests.DbContext/g.cs create mode 100644 FreeSql.Tests/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj rename {FreeSql.Tests.PerformanceTests => FreeSql.Tests/FreeSql.Tests.PerformanceTests}/MySqlAdoTest.cs (100%) rename {FreeSql.Tests.PerformanceTests => FreeSql.Tests/FreeSql.Tests.PerformanceTests}/g.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/FreeSql.Tests.Provider.MySqlConnector.csproj (59%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/Curd/MySqlDeleteTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/Curd/MySqlInsertTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/Curd/MySqlSelectTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/Curd/MySqlUpdateTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MapType/BoolNullableTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MapType/BoolTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MapType/EnumTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MapType/ToStringTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlCodeFirstTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorAdo/MySqlAdoTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/ConvertTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/DateTimeTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/MathTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/OtherTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/StringTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlConnectorExpression/TimeSpanTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/MySqlConnector/MySqlDbFirstTest.cs (100%) rename {FreeSql.Tests.Provider.MySqlConnector => FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector}/g.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Class1.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/DataAnnotations/MySqlFluentTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/DataAnnotations/SqlServerFluentTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/DataContext/SqlServer/SqlServerCollection.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/DataContext/SqlServer/SqlServerFixture.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/ExpressionTree/GetDataReaderValueBlockExpressionTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Extensions/LambadaExpressionExtensionsTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Extensions/StringExtensionsTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/FreeSql.Tests.csproj (51%) rename FreeSql.Tests/{ => FreeSql.Tests}/FreeSql.Tests.xml (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/LinqToSql/SqliteLinqToSqlTests.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/Curd/MySqlDeleteTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/Curd/MySqlInsertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/Curd/MySqlSelectTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/Curd/MySqlUpdateTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MapType/BoolNullableTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MapType/BoolTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MapType/EnumTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MapType/ToStringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlAdo/MySqlAdoTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlCodeFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlDbFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/ConvertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/DateTimeTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/MathTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/OtherTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/StringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/MySql/MySqlExpression/TimeSpanTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/Curd/OracleDeleteTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/Curd/OracleInsertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/Curd/OracleSelectTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/Curd/OracleUpdateTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/MapType/BoolNullableTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/MapType/BoolTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/MapType/EnumTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/MapType/ToStringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleAdo/OracleAdoTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleCodeFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleDbFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/ConvertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/DateTimeTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/MathTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/OtherTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/StringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Oracle/OracleExpression/TimeSpanTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/Curd/PostgreSQLDeleteTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/Curd/PostgreSQLInsertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/Curd/PostgreSQLSelectTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/Curd/PostgreSQLUpdateTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/MapType/BoolNullableTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/MapType/BoolTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/MapType/EnumTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/MapType/JTokenTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/MapType/ToStringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLCodeFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLDbFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/ConvertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/DateTimeTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/MathTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/OtherTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/StringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/PostgreSQL/PostgreSQLExpression/TimeSpanTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/Curd/SqlServerDeleteTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/Curd/SqlServerInsertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/Curd/SqlServerSelectTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/Curd/SqlServerUpdateTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/MapType/BoolNullableTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/MapType/BoolTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/MapType/EnumTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/MapType/ToStringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerAdo/SqlServerAdoTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerCodeFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerDbFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/ConvertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/DateTimeTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/MathTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/OtherTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/StringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/SqlServer/SqlServerExpression/TimeSpanTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/Curd/SqliteDeleteTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/Curd/SqliteInsertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/Curd/SqliteSelectTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/Curd/SqliteUpdateTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/MapType/BoolNullableTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/MapType/BoolTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/MapType/EnumTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/MapType/ToStringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteAdo/SqliteAdoTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteCodeFirstTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/ConvertTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/DateTimeTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/MathTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/OtherTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/StringTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/Sqlite/SqliteExpression/TimeSpanTest.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/UnitTest1.cs (98%) rename FreeSql.Tests/{ => FreeSql.Tests}/UnitTest2.cs (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/ftTests.xml (100%) rename FreeSql.Tests/{ => FreeSql.Tests}/g.cs (100%) diff --git a/Examples/dbcontext_01/Controllers/ValuesController.cs b/Examples/dbcontext_01/Controllers/ValuesController.cs new file mode 100644 index 00000000..90252e03 --- /dev/null +++ b/Examples/dbcontext_01/Controllers/ValuesController.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FreeSql; +using Microsoft.AspNetCore.Mvc; + +namespace dbcontext_01.Controllers { + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase { + + IFreeSql _orm; + SongContext _songContext; + public ValuesController(SongContext songContext, + IFreeSql orm1, IFreeSql orm2, + IFreeSql orm3 + ) { + _songContext = songContext; + _orm = orm1; + + } + + // GET api/values + [HttpGet] + async public Task Get() { + + long id = 0; + + try { + + var repos2Song = _orm.GetRepository(); + repos2Song.Where(a => a.Id > 10).ToList(); + //查询结果,进入 states + + var song = new Song { }; + repos2Song.Insert(song); + id = song.Id; + + var adds = Enumerable.Range(0, 100) + .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + .ToList(); + //创建一堆无主键值 + + repos2Song.Insert(adds); + + for (var a = 0; a < 10; a++) + adds[a].Title = "dkdkdkdk" + a; + + repos2Song.Update(adds); + //批量修改 + + repos2Song.Delete(adds.Skip(10).Take(20).ToList()); + //批量删除,10-20 元素的主键值会被清除 + + adds.Last().Url = "skldfjlksdjglkjjcccc"; + repos2Song.Update(adds.Last()); + + adds.First().Url = "skldfjlksdjglkjjcccc"; + repos2Song.Update(adds.First()); + + + var ctx = _songContext; + var tag = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + ctx.Tags.Add(tag); + + + ctx.UnitOfWork.GetOrBeginTransaction(); + + var tagAsync = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + await ctx.Tags.AddAsync(tagAsync); + + + ctx.Songs.Select.Where(a => a.Id > 10).ToList(); + //查询结果,进入 states + + song = new Song { }; + //可插入的 song + + ctx.Songs.Add(song); + id = song.Id; + //因有自增类型,立即开启事务执行SQL,返回自增值 + + adds = Enumerable.Range(0, 100) + .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + .ToList(); + //创建一堆无主键值 + + ctx.Songs.AddRange(adds); + //立即执行,将自增值赋给 adds 所有元素,因为有自增类型,如果其他类型,指定传入主键值,不会立即执行 + + for (var a = 0; a < 10; a++) + adds[a].Title = "dkdkdkdk" + a; + + ctx.Songs.UpdateRange(adds); + //批量修改,进入队列 + + ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); + //批量删除,进入队列,完成时 10-20 元素的主键值会被清除 + + //ctx.Songs.Update(adds.First()); + + adds.Last().Url = "skldfjlksdjglkjjcccc"; + ctx.Songs.Update(adds.Last()); + + adds.First().Url = "skldfjlksdjglkjjcccc"; + ctx.Songs.Update(adds.First()); + + //单条修改 urls 的值,进入队列 + + //throw new Exception("回滚"); + + //ctx.Songs.Select.First(); + //这里做一个查询,会立即打包【执行队列】,避免没有提交的数据,影响查询结果 + + ctx.SaveChanges(); + //打包【执行队列】,提交事务 + + + using (var uow = _orm.CreateUnitOfWork()) { + + var reposSong = uow.GetRepository(); + reposSong.Where(a => a.Id > 10).ToList(); + //查询结果,进入 states + + song = new Song { }; + reposSong.Insert(song); + id = song.Id; + + adds = Enumerable.Range(0, 100) + .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + .ToList(); + //创建一堆无主键值 + + reposSong.Insert(adds); + + for (var a = 0; a < 10; a++) + adds[a].Title = "dkdkdkdk" + a; + + reposSong.Update(adds); + //批量修改 + + reposSong.Delete(adds.Skip(10).Take(20).ToList()); + //批量删除,10-20 元素的主键值会被清除 + + adds.Last().Url = "skldfjlksdjglkjjcccc"; + reposSong.Update(adds.Last()); + + adds.First().Url = "skldfjlksdjglkjjcccc"; + reposSong.Update(adds.First()); + + uow.Commit(); + } + + + + //using (var ctx = new SongContext()) { + + // var song = new Song { }; + // await ctx.Songs.AddAsync(song); + // id = song.Id; + + // var adds = Enumerable.Range(0, 100) + // .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + // .ToList(); + // await ctx.Songs.AddRangeAsync(adds); + + // for (var a = 0; a < adds.Count; a++) + // adds[a].Title = "dkdkdkdk" + a; + + // ctx.Songs.UpdateRange(adds); + + // ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); + + // //ctx.Songs.Update(adds.First()); + + // adds.Last().Url = "skldfjlksdjglkjjcccc"; + // ctx.Songs.Update(adds.Last()); + + // //throw new Exception("回滚"); + + // await ctx.SaveChangesAsync(); + //} + } catch { + var item = await _orm.Select().Where(a => a.Id == id).FirstAsync(); + + throw; + } + + var item22 = await _orm.Select().Where(a => a.Id == id).FirstAsync(); + var item33 = await _orm.Select().Where(a => a.Id > id).ToListAsync(); + + return item22.Id.ToString(); + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) { + } + } +} diff --git a/Examples/dbcontext_01/DbContexts/SongContext.cs b/Examples/dbcontext_01/DbContexts/SongContext.cs new file mode 100644 index 00000000..126c5fd4 --- /dev/null +++ b/Examples/dbcontext_01/DbContexts/SongContext.cs @@ -0,0 +1,52 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; + +namespace dbcontext_01 { + + public class SongContext : DbContext { + + public DbSet Songs { get; set; } + public DbSet Tags { get; set; } + + //protected override void OnConfiguring(DbContextOptionsBuilder builder) { + // builder.UseFreeSql(dbcontext_01.Startup.Fsql); + //} + } + + + public class Song { + [Column(IsIdentity = true)] + public int Id { get; set; } + public DateTime? Create_time { get; set; } + public bool? Is_deleted { get; set; } + public string Title { get; set; } + public string Url { get; set; } + + public virtual ICollection Tags { get; set; } + + [Column(IsVersion = true)] + public long versionRow { get; set; } + } + public class Song_tag { + public int Song_id { get; set; } + public virtual Song Song { get; set; } + + public int Tag_id { get; set; } + public virtual Tag Tag { get; set; } + } + + public class Tag { + [Column(IsIdentity = true)] + public int Id { get; set; } + public int? Parent_id { get; set; } + public virtual Tag Parent { get; set; } + + public decimal? Ddd { get; set; } + public string Name { get; set; } + + public virtual ICollection Songs { get; set; } + public virtual ICollection Tags { get; set; } + } +} diff --git a/Examples/dbcontext_01/Program.cs b/Examples/dbcontext_01/Program.cs new file mode 100644 index 00000000..d1a5dafb --- /dev/null +++ b/Examples/dbcontext_01/Program.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using FreeSql; +using FreeSql.DataAnnotations; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace dbcontext_01 +{ + public class Program + { + + public class Song { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string BigNumber { get; set; } + + [Column(IsVersion = true)]//使用简单 + public long versionRow { get; set; } + } + + public class SongContext : DbContext { + + public DbSet Songs { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder builder) { + builder.UseFreeSql(fsql); + } + } + static IFreeSql fsql; + public static void Main(string[] args) { + fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + .UseNoneCommandParameter(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) + .Build(); + + + using (var ctx = new SongContext()) { + var song = new Song { BigNumber = "1000000000000000000" }; + ctx.Songs.Add(song); + + ctx.Songs.Update(song); + + song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); + ctx.Songs.Update(song); + + ctx.SaveChanges(); + + var sql = fsql.Update().SetSource(song).ToSql(); + } + + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/Examples/dbcontext_01/Properties/launchSettings.json b/Examples/dbcontext_01/Properties/launchSettings.json new file mode 100644 index 00000000..a7caa4d3 --- /dev/null +++ b/Examples/dbcontext_01/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53030/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "dbcontext_01": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:53031/" + } + } +} \ No newline at end of file diff --git a/Examples/dbcontext_01/Startup.cs b/Examples/dbcontext_01/Startup.cs new file mode 100644 index 00000000..3955cb87 --- /dev/null +++ b/Examples/dbcontext_01/Startup.cs @@ -0,0 +1,111 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Swashbuckle.AspNetCore.Swagger; +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace dbcontext_01 +{ + public class Startup + { + public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) + { + Configuration = configuration; + + Fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document2.db;Pooling=true;Max Pool Size=10") + //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") + + //.UseConnectionString(FreeSql.DataType.Oracle, "user id=user1;password=123456;data source=//127.0.0.1:1521/XE;Pooling=true;Max Pool Size=10") + //.UseSyncStructureToUpper(true) + + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + .UseNoneCommandParameter(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText), + (cmd, log) => Trace.WriteLine(log) + ) + .Build(); + Fsql.Aop.SyncStructureBefore = (s, e) => { + Console.WriteLine(e.Identifier + ": " + string.Join(", ", e.EntityTypes.Select(a => a.FullName))); + }; + Fsql.Aop.SyncStructureAfter = (s, e) => { + Console.WriteLine(e.Identifier + ": " + string.Join(", ", e.EntityTypes.Select(a => a.FullName)) + " " + e.ElapsedMilliseconds + "ms\r\n" + e.Exception?.Message + e.Sql); + }; + + Fsql.Aop.CurdBefore = (s, e) => { + Console.WriteLine(e.Identifier + ": " + e.EntityType.FullName + ", " + e.Sql); + }; + Fsql.Aop.CurdAfter = (s, e) => { + Console.WriteLine(e.Identifier + ": " + e.EntityType.FullName + " " + e.ElapsedMilliseconds + "ms, " + e.Sql); + }; + + Fsql2 = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document222.db;Pooling=true;Max Pool Size=10") + //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText), + (cmd, log) => Trace.WriteLine(log) + ) + .Build(); + } + + enum MySql { } + enum PgSql { } + + public IConfiguration Configuration { get; } + public static IFreeSql Fsql { get; private set; } + public static IFreeSql Fsql2 { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + services.AddSwaggerGen(options => { + options.SwaggerDoc("v1", new Info { + Version = "v1", + Title = "FreeSql.DbContext API" + }); + //options.IncludeXmlComments(xmlPath); + }); + + + + services.AddSingleton(Fsql); + services.AddSingleton>(Fsql2); + services.AddFreeDbContext(options => options.UseFreeSql(Fsql)); + + + var sql1 = Fsql.Update(1).Set(a => a.Id + 10).ToSql(); + var sql2 = Fsql.Update(1).Set(a => a.Title + 10).ToSql(); + var sql3 = Fsql.Update(1).Set(a => a.Create_time.Value.AddHours(1)).ToSql(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Console.OutputEncoding = Encoding.GetEncoding("GB2312"); + Console.InputEncoding = Encoding.GetEncoding("GB2312"); + + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); + app.UseDeveloperExceptionPage(); + app.UseMvc(); + + app.UseSwagger(); + app.UseSwaggerUI(c => { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "FreeSql.RESTful API V1"); + }); + } + } +} diff --git a/Examples/dbcontext_01/appsettings.Development.json b/Examples/dbcontext_01/appsettings.Development.json new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/Examples/dbcontext_01/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Examples/dbcontext_01/appsettings.json b/Examples/dbcontext_01/appsettings.json new file mode 100644 index 00000000..def9159a --- /dev/null +++ b/Examples/dbcontext_01/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Examples/dbcontext_01/dbcontext_01.csproj b/Examples/dbcontext_01/dbcontext_01.csproj new file mode 100644 index 00000000..7d31fc7b --- /dev/null +++ b/Examples/dbcontext_01/dbcontext_01.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.1 + InProcess + + + + + + + + + + + + + + + + diff --git a/Examples/repository_01/Controllers/SongController.cs b/Examples/repository_01/Controllers/SongController.cs new file mode 100644 index 00000000..d162c33c --- /dev/null +++ b/Examples/repository_01/Controllers/SongController.cs @@ -0,0 +1,98 @@ +using FreeSql; +using Microsoft.AspNetCore.Mvc; +using restful.Entitys; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace restful.Controllers { + + public class SongRepository : GuidRepository { + public SongRepository(IFreeSql fsql) : base(fsql) { + } + } + + [Route("restapi/[controller]")] + public class SongsController : Controller { + + BaseRepository _songRepository; + + public class xxxx { + public int Id { get; set; } + + public bool IsDeleted { get; set; } + } + + + + public SongsController(IFreeSql fsql, + GuidRepository repos1, + GuidRepository repos2, + + DefaultRepository repos11, + DefaultRepository repos21, + + BaseRepository repos3, BaseRepository repos4, + IBasicRepository repos31, IBasicRepository repos41, + IReadOnlyRepository repos311, IReadOnlyRepository repos411, + + SongRepository reposSong + ) { + _songRepository = repos4; + + //test code + var curd1 = fsql.GetRepository(); + var curd2 = fsql.GetRepository(); + var curd3 = fsql.GetRepository(); + var curd4 = fsql.GetGuidRepository(); + + Console.WriteLine(repos1.Select.ToSql()); + Console.WriteLine(reposSong.Select.ToSql()); + + Console.WriteLine(repos2.Select.ToSql()); + Console.WriteLine(repos21.Select.ToSql()); + + using (reposSong.DataFilter.DisableAll()) { + Console.WriteLine(reposSong.Select.ToSql()); + } + } + + [HttpGet] + public Task> GetItems([FromQuery] string key, [FromQuery] int page = 1, [FromQuery] int limit = 20) { + return _songRepository.Select.WhereIf(!string.IsNullOrEmpty(key), a => a.Title.Contains(key)).Page(page, limit).ToListAsync(); + } + + [HttpGet("{id}")] + public Task GetItem([FromRoute] int id) { + return _songRepository.FindAsync(id); + } + + public class ModelSong { + public string title { get; set; } + } + + [HttpPost, ProducesResponseType(201)] + public Task Create([FromBody] ModelSong model) { + return _songRepository.InsertAsync(new Song { Title = model.title }); + } + + [HttpPut("{id}")] + public Task Update([FromRoute] int id, [FromBody] ModelSong model) { + return _songRepository.UpdateAsync(new Song { Id = id, Title = model.title }); + } + + [HttpPatch("{id}")] + async public Task UpdateDiy([FromRoute] int id, [FromForm] string title) { + var up = _songRepository.UpdateDiy.Where(a => a.Id == id); + if (!string.IsNullOrEmpty(title)) up.Set(a => a.Title, title); + var ret = await up.ExecuteUpdatedAsync(); + return ret.FirstOrDefault(); + } + + [HttpDelete("{id}"), ProducesResponseType(204)] + public Task Delete([FromRoute] int id) { + return _songRepository.DeleteAsync(a => a.Id == id); + } + } +} diff --git a/Examples/repository_01/Entitys/Song.cs b/Examples/repository_01/Entitys/Song.cs new file mode 100644 index 00000000..8c8a8a14 --- /dev/null +++ b/Examples/repository_01/Entitys/Song.cs @@ -0,0 +1,11 @@ +using FreeSql.DataAnnotations; +using repository_01; + +namespace restful.Entitys { + public class Song { + + [Column(IsIdentity = true)] + public int Id { get; set; } + public string Title { get; set; } + } +} diff --git a/Examples/repository_01/Program.cs b/Examples/repository_01/Program.cs new file mode 100644 index 00000000..da7591b3 --- /dev/null +++ b/Examples/repository_01/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace repository_01 +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/Examples/repository_01/Properties/launchSettings.json b/Examples/repository_01/Properties/launchSettings.json new file mode 100644 index 00000000..0426ce6f --- /dev/null +++ b/Examples/repository_01/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:52751/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "repository_01": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:52752/" + } + } +} \ No newline at end of file diff --git a/Examples/repository_01/Startup.cs b/Examples/repository_01/Startup.cs new file mode 100644 index 00000000..07243127 --- /dev/null +++ b/Examples/repository_01/Startup.cs @@ -0,0 +1,98 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using restful.Entitys; +using Swashbuckle.AspNetCore.Swagger; +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace repository_01 { + + /// + /// 用户密码信息 + /// + public class Sys1UserLogOn { + [Column(IsPrimary = true, Name = "Id")] + public Guid UserLogOnId { get; set; } + public virtual Sys1User User { get; set; } + } + public class Sys1User { + [Column(IsPrimary = true, Name = "Id")] + public Guid UserId { get; set; } + public virtual Sys1UserLogOn UserLogOn { get; set; } + } + + public class Startup { + public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) { + Configuration = configuration; + + Fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) + .Build(); + + var sysu = new Sys1User { }; + Fsql.Insert().AppendData(sysu).ExecuteAffrows(); + Fsql.Insert().AppendData(new Sys1UserLogOn { UserLogOnId = sysu.UserId }).ExecuteAffrows(); + var a = Fsql.Select().ToList(); + var b = Fsql.Select().Any(); + } + + public IConfiguration Configuration { get; } + public static IFreeSql Fsql { get; private set; } + + public void ConfigureServices(IServiceCollection services) { + + //services.AddTransient(s => s.) + + services.AddMvc(); + services.AddSwaggerGen(options => { + options.SwaggerDoc("v1", new Info { + Version = "v1", + Title = "FreeSql.RESTful API" + }); + //options.IncludeXmlComments(xmlPath); + }); + + services.AddSingleton(Fsql); + + services.AddFreeRepository(filter => { + filter + //.Apply("test", a => a.Title == DateTime.Now.ToString() + System.Threading.Thread.CurrentThread.ManagedThreadId) + .Apply("softdelete", a => a.IsDeleted == false); + }, this.GetType().Assembly); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Console.OutputEncoding = Encoding.GetEncoding("GB2312"); + Console.InputEncoding = Encoding.GetEncoding("GB2312"); + + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); + app.UseDeveloperExceptionPage(); + app.UseMvc(); + + app.UseSwagger(); + app.UseSwaggerUI(c => { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "FreeSql.RESTful API V1"); + }); + } + } + + public interface ISoftDelete { + bool IsDeleted { get; set; } + } +} diff --git a/Examples/repository_01/appsettings.Development.json b/Examples/repository_01/appsettings.Development.json new file mode 100644 index 00000000..86f6b8f7 --- /dev/null +++ b/Examples/repository_01/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Warning", + "Microsoft": "Warning" + } + } +} diff --git a/Examples/repository_01/appsettings.json b/Examples/repository_01/appsettings.json new file mode 100644 index 00000000..def9159a --- /dev/null +++ b/Examples/repository_01/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Examples/repository_01/repository_01.csproj b/Examples/repository_01/repository_01.csproj new file mode 100644 index 00000000..14435386 --- /dev/null +++ b/Examples/repository_01/repository_01.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + InProcess + + + + + + + + + + + + + + + diff --git a/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj b/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj index 871e5062..4d94d32d 100644 --- a/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj +++ b/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 扩展包,可实现【延时加载】属性. diff --git a/FreeSql.DbContext/DbContext/DbContext.cs b/FreeSql.DbContext/DbContext/DbContext.cs new file mode 100644 index 00000000..90625e87 --- /dev/null +++ b/FreeSql.DbContext/DbContext/DbContext.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace FreeSql { + public abstract partial class DbContext : IDisposable { + + internal IFreeSql _orm; + internal IFreeSql _fsql => _orm ?? throw new ArgumentNullException("请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql"); + + public IFreeSql Orm => _fsql; + + protected IUnitOfWork _uowPriv; + internal IUnitOfWork _uow => _isUseUnitOfWork ? (_uowPriv ?? (_uowPriv = new UnitOfWork(_fsql))) : null; + internal bool _isUseUnitOfWork = true; //不使用工作单元事务 + + public IUnitOfWork UnitOfWork => _uow; + + DbContextOptions _options; + internal DbContextOptions Options { + get { + if (_options != null) return _options; + if (FreeSqlDbContextExtenssions._dicSetDbContextOptions.TryGetValue(Orm, out _options)) return _options; + _options = new DbContextOptions(); + return _options; + } + } + + static ConcurrentDictionary _dicGetDbSetProps = new ConcurrentDictionary(); + protected DbContext() { + + var builder = new DbContextOptionsBuilder(); + OnConfiguring(builder); + _orm = builder._fsql; + + if (_orm != null) InitPropSets(); + } + + internal void InitPropSets() { + var props = _dicGetDbSetProps.GetOrAdd(this.GetType(), tp => + tp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .Where(a => a.PropertyType.IsGenericType && + a.PropertyType == typeof(DbSet<>).MakeGenericType(a.PropertyType.GenericTypeArguments[0])).ToArray()); + + foreach (var prop in props) { + var set = this.Set(prop.PropertyType.GenericTypeArguments[0]); + + prop.SetValue(this, set); + AllSets.Add(prop.Name, set); + } + } + + protected virtual void OnConfiguring(DbContextOptionsBuilder builder) { + + } + + protected Dictionary _dicSet = new Dictionary(); + public DbSet Set() where TEntity : class => this.Set(typeof(TEntity)) as DbSet; + public virtual IDbSet Set(Type entityType) { + if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType]; + var sd = Activator.CreateInstance(typeof(DbContextDbSet<>).MakeGenericType(entityType), this) as IDbSet; + if (entityType != typeof(object)) _dicSet.Add(entityType, sd); + return sd; + } + protected Dictionary AllSets { get; } = new Dictionary(); + + #region DbSet 快速代理 + /// + /// 添加 + /// + /// + /// + public void Add(TEntity data) where TEntity : class => this.Set().Add(data); + public void AddRange(IEnumerable data) where TEntity : class => this.Set().AddRange(data); + public Task AddAsync(TEntity data) where TEntity : class => this.Set().AddAsync(data); + public Task AddRangeAsync(IEnumerable data) where TEntity : class => this.Set().AddRangeAsync(data); + + /// + /// 更新 + /// + /// + /// + public void Update(TEntity data) where TEntity : class => this.Set().Update(data); + public void UpdateRange(IEnumerable data) where TEntity : class => this.Set().UpdateRange(data); + public Task UpdateAsync(TEntity data) where TEntity : class => this.Set().UpdateAsync(data); + public Task UpdateRangeAsync(IEnumerable data) where TEntity : class => this.Set().UpdateRangeAsync(data); + + /// + /// 删除 + /// + /// + /// + public void Remove(TEntity data) where TEntity : class => this.Set().Remove(data); + public void RemoveRange(IEnumerable data) where TEntity : class => this.Set().RemoveRange(data); + + /// + /// 添加或更新 + /// + /// + /// + public void AddOrUpdate(TEntity data) where TEntity : class => this.Set().AddOrUpdate(data); + public Task AddOrUpdateAsync(TEntity data) where TEntity : class => this.Set().AddOrUpdateAsync(data); + + /// + /// 附加实体,可用于不查询就更新或删除 + /// + /// + /// + public void Attach(TEntity data) where TEntity : class => this.Set().Attach(data); + public void AttachRange(IEnumerable data) where TEntity : class => this.Set().AttachRange(data); + #endregion + + internal class ExecCommandInfo { + public ExecCommandInfoType actionType { get; set; } + public IDbSet dbSet { get; set; } + public Type stateType { get; set; } + public object state { get; set; } + } + internal enum ExecCommandInfoType { Insert, Update, Delete } + Queue _actions = new Queue(); + internal int _affrows = 0; + + internal void EnqueueAction(ExecCommandInfoType actionType, IDbSet dbSet, Type stateType, object state) { + _actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, state = state }); + } + + ~DbContext() { + this.Dispose(); + } + bool _isdisposed = false; + public void Dispose() { + if (_isdisposed) return; + try { + _actions.Clear(); + + foreach (var set in _dicSet) + try { + set.Value.Dispose(); + } catch { } + + _dicSet.Clear(); + AllSets.Clear(); + + _uow?.Rollback(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } + } + } +} diff --git a/FreeSql.DbContext/DbContext/DbContextAsync.cs b/FreeSql.DbContext/DbContext/DbContextAsync.cs new file mode 100644 index 00000000..269486f9 --- /dev/null +++ b/FreeSql.DbContext/DbContext/DbContextAsync.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + partial class DbContext { + + async public virtual Task SaveChangesAsync() { + await ExecCommandAsync(); + _uow?.Commit(); + var ret = _affrows; + _affrows = 0; + return ret; + } + + static Dictionary>>> _dicExecCommandDbContextBetchAsync = new Dictionary>>>(); + async internal Task ExecCommandAsync() { + if (isExecCommanding) return; + if (_actions.Any() == false) return; + isExecCommanding = true; + + ExecCommandInfo oldinfo = null; + var states = new List(); + + Func> dbContextBetch = methodName => { + if (_dicExecCommandDbContextBetchAsync.TryGetValue(oldinfo.stateType, out var trydic) == false) + trydic = new Dictionary>>(); + if (trydic.TryGetValue(methodName, out var tryfunc) == false) { + var arrType = oldinfo.stateType.MakeArrayType(); + var dbsetType = oldinfo.dbSet.GetType().BaseType; + var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); + + var returnTarget = Expression.Label(typeof(Task)); + var parm1DbSet = Expression.Parameter(typeof(object)); + var parm2Vals = Expression.Parameter(typeof(object[])); + var var1Vals = Expression.Variable(arrType); + tryfunc = Expression.Lambda>>(Expression.Block( + new[] { var1Vals }, + Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)), + Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), + Expression.Label(returnTarget, Expression.Default(typeof(Task))) + ), new[] { parm1DbSet, parm2Vals }).Compile(); + trydic.Add(methodName, tryfunc); + } + return tryfunc(oldinfo.dbSet, states.ToArray()); + }; + Func funcDelete = async () => { + _affrows += await dbContextBetch("DbContextBetchRemoveAsync"); + states.Clear(); + }; + Func funcInsert = async () => { + _affrows += await dbContextBetch("DbContextBetchAddAsync"); + states.Clear(); + }; + Func funcUpdate = async (isLiveUpdate) => { + var affrows = 0; + if (isLiveUpdate) affrows = await dbContextBetch("DbContextBetchUpdateNowAsync"); + else affrows = await dbContextBetch("DbContextBetchUpdateAsync"); + if (affrows == -999) { //最后一个元素已被删除 + states.RemoveAt(states.Count - 1); + return; + } + if (affrows == -998 || affrows == -997) { //没有执行更新 + var laststate = states[states.Count - 1]; + states.Clear(); + if (affrows == -997) states.Add(laststate); //保留最后一个 + } + if (affrows > 0) { + _affrows += affrows; + var islastNotUpdated = states.Count != affrows; + var laststate = states[states.Count - 1]; + states.Clear(); + if (islastNotUpdated) states.Add(laststate); //保留最后一个 + } + }; + + while (_actions.Any() || states.Any()) { + var info = _actions.Any() ? _actions.Dequeue() : null; + if (oldinfo == null) oldinfo = info; + var isLiveUpdate = false; + + if (_actions.Any() == false && states.Any() || + info != null && oldinfo.actionType != info.actionType || + info != null && oldinfo.stateType != info.stateType) { + + if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { + //最后一个,合起来发送 + states.Add(info.state); + info = null; + } + + switch (oldinfo.actionType) { + case ExecCommandInfoType.Insert: + await funcInsert(); + break; + case ExecCommandInfoType.Delete: + await funcDelete(); + break; + } + isLiveUpdate = true; + } + + if (isLiveUpdate || oldinfo.actionType == ExecCommandInfoType.Update) { + if (states.Any()) + await funcUpdate(isLiveUpdate); + } + + if (info != null) { + states.Add(info.state); + oldinfo = info; + } + } + isExecCommanding = false; + } + } +} diff --git a/FreeSql.DbContext/DbContext/DbContextOptions.cs b/FreeSql.DbContext/DbContext/DbContextOptions.cs new file mode 100644 index 00000000..47095091 --- /dev/null +++ b/FreeSql.DbContext/DbContext/DbContextOptions.cs @@ -0,0 +1,10 @@ + +namespace FreeSql { + public class DbContextOptions { + + /// + /// 是否开启一对多,联级保存功能 + /// + public bool EnableAddOrUpdateNavigateList { get; set; } = true; + } +} diff --git a/FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs b/FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs new file mode 100644 index 00000000..a003f65d --- /dev/null +++ b/FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs @@ -0,0 +1,13 @@ + + +namespace FreeSql { + public class DbContextOptionsBuilder { + + internal IFreeSql _fsql; + + public DbContextOptionsBuilder UseFreeSql(IFreeSql orm) { + _fsql = orm; + return this; + } + } +} diff --git a/FreeSql.DbContext/DbContext/DbContextSync.cs b/FreeSql.DbContext/DbContext/DbContextSync.cs new file mode 100644 index 00000000..6eec203b --- /dev/null +++ b/FreeSql.DbContext/DbContext/DbContextSync.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; + +namespace FreeSql { + partial class DbContext { + + public virtual int SaveChanges() { + ExecCommand(); + _uow?.Commit(); + var ret = _affrows; + _affrows = 0; + return ret; + } + + static Dictionary>> _dicExecCommandDbContextBetch = new Dictionary>>(); + bool isExecCommanding = false; + internal void ExecCommand() { + if (isExecCommanding) return; + if (_actions.Any() == false) return; + isExecCommanding = true; + + ExecCommandInfo oldinfo = null; + var states = new List(); + + Func dbContextBetch = methodName => { + if (_dicExecCommandDbContextBetch.TryGetValue(oldinfo.stateType, out var trydic) == false) + trydic = new Dictionary>(); + if (trydic.TryGetValue(methodName, out var tryfunc) == false) { + var arrType = oldinfo.stateType.MakeArrayType(); + var dbsetType = oldinfo.dbSet.GetType().BaseType; + var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); + + var returnTarget = Expression.Label(typeof(int)); + var parm1DbSet = Expression.Parameter(typeof(object)); + var parm2Vals = Expression.Parameter(typeof(object[])); + var var1Vals = Expression.Variable(arrType); + tryfunc = Expression.Lambda>(Expression.Block( + new[] { var1Vals }, + Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)), + Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), + Expression.Label(returnTarget, Expression.Default(typeof(int))) + ), new[] { parm1DbSet, parm2Vals }).Compile(); + trydic.Add(methodName, tryfunc); + } + return tryfunc(oldinfo.dbSet, states.ToArray()); + }; + Action funcDelete = () => { + _affrows += dbContextBetch("DbContextBetchRemove"); + states.Clear(); + }; + Action funcInsert = () => { + _affrows += dbContextBetch("DbContextBetchAdd"); + states.Clear(); + }; + Action funcUpdate = isLiveUpdate => { + var affrows = 0; + if (isLiveUpdate) affrows = dbContextBetch("DbContextBetchUpdateNow"); + else affrows = dbContextBetch("DbContextBetchUpdate"); + if (affrows == -999) { //最后一个元素已被删除 + states.RemoveAt(states.Count - 1); + return; + } + if (affrows == -998 || affrows == -997) { //没有执行更新 + var laststate = states[states.Count - 1]; + states.Clear(); + if (affrows == -997) states.Add(laststate); //保留最后一个 + } + if (affrows > 0) { + _affrows += affrows; + var islastNotUpdated = states.Count != affrows; + var laststate = states[states.Count - 1]; + states.Clear(); + if (islastNotUpdated) states.Add(laststate); //保留最后一个 + } + }; + + while (_actions.Any() || states.Any()) { + var info = _actions.Any() ? _actions.Dequeue() : null; + if (oldinfo == null) oldinfo = info; + var isLiveUpdate = false; + + if (_actions.Any() == false && states.Any() || + info != null && oldinfo.actionType != info.actionType || + info != null && oldinfo.stateType != info.stateType) { + + if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { + //最后一个,合起来发送 + states.Add(info.state); + info = null; + } + + switch (oldinfo.actionType) { + case ExecCommandInfoType.Insert: + funcInsert(); + break; + case ExecCommandInfoType.Delete: + funcDelete(); + break; + } + isLiveUpdate = true; + } + + if (isLiveUpdate || oldinfo.actionType == ExecCommandInfoType.Update) { + if (states.Any()) + funcUpdate(isLiveUpdate); + } + + if (info != null) { + states.Add(info.state); + oldinfo = info; + } + } + isExecCommanding = false; + } + } +} diff --git a/FreeSql.DbContext/DbContext/FreeContext.cs b/FreeSql.DbContext/DbContext/FreeContext.cs new file mode 100644 index 00000000..a6b0549d --- /dev/null +++ b/FreeSql.DbContext/DbContext/FreeContext.cs @@ -0,0 +1,10 @@ + + +namespace FreeSql { + public class FreeContext : DbContext { + + public FreeContext(IFreeSql orm) { + _orm = orm; + } + } +} diff --git a/FreeSql.DbContext/DbSet/DbSet.cs b/FreeSql.DbContext/DbSet/DbSet.cs new file mode 100644 index 00000000..b115f424 --- /dev/null +++ b/FreeSql.DbContext/DbSet/DbSet.cs @@ -0,0 +1,281 @@ +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace FreeSql { + + internal class DbContextDbSet : DbSet where TEntity : class { + + public DbContextDbSet(DbContext ctx) { + _ctx = ctx; + _uow = ctx._uow; + _fsql = ctx._fsql; + } + } + + public interface IDbSet : IDisposable { + Type EntityType { get; } + } + public abstract partial class DbSet : IDbSet where TEntity : class { + + internal DbContext _ctx; + internal IUnitOfWork _uow; + internal IFreeSql _fsql; + + protected virtual ISelect OrmSelect(object dywhere) { + DbContextExecCommand(); //查询前先提交,否则会出脏读 + return _fsql.Select().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction(false)).TrackToList(TrackToList).WhereDynamic(dywhere); + } + + ~DbSet() { + this.Dispose(); + } + bool _isdisposed = false; + public void Dispose() { + if (_isdisposed) return; + try { + this._dicUpdateTimes.Clear(); + this._states.Clear(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } + } + + protected virtual IInsert OrmInsert() => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()); + protected virtual IInsert OrmInsert(TEntity data) => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).AppendData(data); + protected virtual IInsert OrmInsert(IEnumerable data) => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).AppendData(data); + + protected virtual IUpdate OrmUpdate(IEnumerable entitys) => _fsql.Update().AsType(_entityType).SetSource(entitys).WithTransaction(_uow?.GetOrBeginTransaction()); + protected virtual IDelete OrmDelete(object dywhere) => _fsql.Delete().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).WhereDynamic(dywhere); + + internal void EnqueueToDbContext(DbContext.ExecCommandInfoType actionType, EntityState state) { + _ctx.EnqueueAction(actionType, this, typeof(EntityState), state); + } + internal void IncrAffrows(int affrows) { + _ctx._affrows += affrows; + } + + internal void TrackToList(object list) { + if (list == null) return; + var ls = list as IList; + if (ls == null) { + var ie = list as IEnumerable; + if (ie == null) return; + foreach (var item in ie) { + if (item == null) return; + var itemType = item.GetType(); + if (itemType == typeof(object)) return; + if (itemType.FullName.StartsWith("Submission#")) itemType = itemType.BaseType; + var dbset = _ctx.Set(itemType); + dbset?.GetType().GetMethod("TrackToList", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(dbset, new object[] { list }); + return; + } + return; + } + + foreach (var item in ls) { + var key = _fsql.GetEntityKeyString(_entityType, item, false); + if (key == null) continue; + _states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { + _fsql.MapEntityValue(_entityType, item, ov.Value); + ov.Time = DateTime.Now; + return ov; + }); + } + } + + public ISelect Select => this.OrmSelect(null); + public ISelect Where(Expression> exp) => this.OrmSelect(null).Where(exp); + public ISelect WhereIf(bool condition, Expression> exp) => this.OrmSelect(null).WhereIf(condition, exp); + + protected ConcurrentDictionary _states = new ConcurrentDictionary(); + internal ConcurrentDictionary _statesInternal => _states; + TableInfo _tablePriv; + protected TableInfo _table => _tablePriv ?? (_tablePriv = _fsql.CodeFirst.GetTableByEntity(_entityType)); + ColumnInfo[] _tableIdentitysPriv; + protected ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray()); + protected Type _entityType = typeof(TEntity); + public Type EntityType => _entityType; + + /// + /// 动态Type,在使用 DbSet<object> 后使用本方法,指定实体类型 + /// + /// + /// + public void AsType(Type entityType) { + if (entityType == typeof(object)) throw new Exception("ISelect.AsType 参数不支持指定为 object"); + if (entityType == _entityType) return; + var newtb = _fsql.CodeFirst.GetTableByEntity(entityType); + _entityType = entityType; + _tablePriv = newtb ?? throw new Exception("DbSet.AsType 参数错误,请传入正确的实体类型"); + _tableIdentitysPriv = null; + } + + public class EntityState { + public EntityState(TEntity value, string key) { + this.Value = value; + this.Key = key; + this.Time = DateTime.Now; + } + public TEntity OldValue { get; set; } + public TEntity Value { get; set; } + public string Key { get; set; } + public DateTime Time { get; set; } + } + /// + /// 附加实体,可用于不查询就更新或删除 + /// + /// + public void Attach(TEntity data) => AttachRange(new[] { data }); + public void AttachRange(IEnumerable data) { + if (data == null || data.Any() == false) return; + if (_table.Primarys.Any() == false) throw new Exception($"不可附加,实体没有主键:{_fsql.GetEntityString(_entityType, data.First())}"); + foreach (var item in data) { + var key = _fsql.GetEntityKeyString(_entityType, item, false); + if (string.IsNullOrEmpty(key)) throw new Exception($"不可附加,未设置主键的值:{_fsql.GetEntityString(_entityType, item)}"); + + _states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { + _fsql.MapEntityValue(_entityType, item, ov.Value); + ov.Time = DateTime.Now; + return ov; + }); + } + } + /// + /// 清空状态数据 + /// + public void FlushState() { + _states.Clear(); + } + + #region Utils + EntityState CreateEntityState(TEntity data) { + if (data == null) throw new ArgumentNullException(nameof(data)); + var key = _fsql.GetEntityKeyString(_entityType, data, false); + var state = new EntityState((TEntity)Activator.CreateInstance(_entityType), key); + _fsql.MapEntityValue(_entityType, data, state.Value); + return state; + } + bool? ExistsInStates(TEntity data) { + if (data == null) throw new ArgumentNullException(nameof(data)); + var key = _fsql.GetEntityKeyString(_entityType, data, false); + if (string.IsNullOrEmpty(key)) return null; + return _states.ContainsKey(key); + } + + bool CanAdd(IEnumerable data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (data.Any() == false) return false; + foreach (var s in data) if (CanAdd(s, isThrow) == false) return false; + return true; + } + bool CanAdd(TEntity data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (_table.Primarys.Any() == false) { + if (isThrow) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + var key = _fsql.GetEntityKeyString(_entityType, data, true); + if (string.IsNullOrEmpty(key)) { + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + return true; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + return true; + } + if (isThrow) throw new Exception($"不可添加,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + } else { + if (_states.ContainsKey(key)) { + if (isThrow) throw new Exception($"不可添加,已存在于状态管理:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + var idval = _fsql.GetEntityIdentityValueWithPrimary(_entityType, data); + if (idval > 0) { + if (isThrow) throw new Exception($"不可添加,自增属性有值:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + } + return true; + } + + bool CanUpdate(IEnumerable data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (data.Any() == false) return false; + foreach (var s in data) if (CanUpdate(s, isThrow) == false) return false; + return true; + } + bool CanUpdate(TEntity data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (_table.Primarys.Any() == false) { + if (isThrow) throw new Exception($"不可更新,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + var key = _fsql.GetEntityKeyString(_entityType, data, false); + if (string.IsNullOrEmpty(key)) { + if (isThrow) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + if (_states.TryGetValue(key, out var tryval) == false) { + if (isThrow) throw new Exception($"不可更新,数据未被跟踪,应该先查询 或者 Attach:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + return true; + } + + bool CanRemove(IEnumerable data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (data.Any() == false) return false; + foreach (var s in data) if (CanRemove(s, isThrow) == false) return false; + return true; + } + bool CanRemove(TEntity data, bool isThrow) { + if (data == null) { + if (isThrow) throw new ArgumentNullException(nameof(data)); + return false; + } + if (_table.Primarys.Any() == false) { + if (isThrow) throw new Exception($"不可删除,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + var key = _fsql.GetEntityKeyString(_entityType, data, false); + if (string.IsNullOrEmpty(key)) { + if (isThrow) throw new Exception($"不可删除,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); + return false; + } + //if (_states.TryGetValue(key, out var tryval) == false) { + // if (isThrow) throw new Exception($"不可删除,数据未被跟踪,应该先查询:{_fsql.GetEntityString(_entityType, data)}"); + // return false; + //} + return true; + } + #endregion + } +} diff --git a/FreeSql.DbContext/DbSet/DbSetAsync.cs b/FreeSql.DbContext/DbSet/DbSetAsync.cs new file mode 100644 index 00000000..2b5978f9 --- /dev/null +++ b/FreeSql.DbContext/DbSet/DbSetAsync.cs @@ -0,0 +1,268 @@ +using FreeSql.Extensions.EntityUtil; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FreeSql { + partial class DbSet { + + Task DbContextExecCommandAsync() { + _dicUpdateTimes.Clear(); + return _ctx.ExecCommandAsync(); + } + + async Task DbContextBetchAddAsync(EntityState[] adds) { + if (adds.Any() == false) return 0; + var affrows = await this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrowsAsync(); + return affrows; + } + + #region Add + async Task AddPrivAsync(TEntity data, bool isCheck) { + if (isCheck && CanAdd(data, true) == false) return; + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + await DbContextExecCommandAsync(); + var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + await AddOrUpdateNavigateListAsync(data); + } else { + await DbContextExecCommandAsync(); + var newval = (await this.OrmInsert(data).ExecuteInsertedAsync()).First(); + IncrAffrows(1); + _fsql.MapEntityValue(_entityType, newval, data); + Attach(newval); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + await AddOrUpdateNavigateListAsync(data); + } + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + await DbContextExecCommandAsync(); + var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + await AddOrUpdateNavigateListAsync(data); + } + return; + } + } + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + await AddOrUpdateNavigateListAsync(data); + } + public Task AddAsync(TEntity data) => AddPrivAsync(data, true); + async public Task AddRangeAsync(IEnumerable data) { + if (CanAdd(data, true) == false) return; + if (data.ElementAtOrDefault(1) == default(TEntity)) { + await AddAsync(data.First()); + return; + } + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + await DbContextExecCommandAsync(); + var rets = await this.OrmInsert(data).ExecuteInsertedAsync(); + if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); + var idx = 0; + foreach (var s in data) + _fsql.MapEntityValue(_entityType, rets[idx++], s); + IncrAffrows(rets.Count); + AttachRange(rets); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + await AddOrUpdateNavigateListAsync(item); + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + foreach (var s in data) + await AddPrivAsync(s, false); + return; + } + } else { + //进入队列,等待 SaveChanges 时执行 + foreach (var item in data) + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(item)); + AttachRange(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + await AddOrUpdateNavigateListAsync(item); + } + } + async Task AddOrUpdateNavigateListAsync(TEntity item) { + Type itemType = null; + foreach (var prop in _table.Properties) { + if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue; + if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue; + var tref = _table.GetTableRef(prop.Key, true); + if (tref == null) continue; + + switch (tref.RefType) { + case Internal.Model.TableRefType.OneToOne: + case Internal.Model.TableRefType.ManyToOne: + case Internal.Model.TableRefType.ManyToMany: + continue; + case Internal.Model.TableRefType.OneToMany: + if (itemType == null) itemType = item.GetType(); + if (_table.TypeLazy != null && itemType == _table.TypeLazy) { + var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary()).GetOrAdd(prop.Key, propName => + _table.TypeLazy.GetField($"__lazy__{propName}", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)); + if (lazyField != null) { + var lazyFieldValue = (bool)lazyField.GetValue(item); + if (lazyFieldValue == false) continue; + } + } + var propVal = prop.Value.GetValue(item); + var propValEach = propVal as IEnumerable; + if (propValEach == null) continue; + object dbset = null; + System.Reflection.MethodInfo dbsetAddOrUpdate = null; + foreach (var propValItem in propValEach) { + if (dbset == null) { + dbset = _ctx.Set(tref.RefEntityType); + dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdateAsync", new Type[] { tref.RefEntityType }); + } + for (var colidx = 0; colidx < tref.Columns.Count; colidx++) { + tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName] + .SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item)); + } + Task task = dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem }) as Task; + await task; + } + break; + } + } + } + #endregion + + #region UpdateAsync + Task DbContextBetchUpdateAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, false); + Task DbContextBetchUpdateNowAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, true); + async Task DbContextBetchUpdatePrivAsync(EntityState[] ups, bool isLiveUpdate) { + if (ups.Any() == false) return 0; + var uplst1 = ups[ups.Length - 1]; + var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null; + + if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; + var lstval2 = default(EntityState); + if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"特别错误:更新失败,数据未被跟踪:{_fsql.GetEntityString(_entityType, uplst2.Value)}"); + + var cuig1 = _fsql.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true); + var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null; + + List data = null; + string[] cuig = null; + if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { + //最后一个不保存 + data = ups.ToList(); + data.RemoveAt(ups.Length - 1); + cuig = cuig2; + } else if (isLiveUpdate) { + //立即保存 + data = ups.ToList(); + cuig = cuig1; + } + + if (data?.Count > 0) { + + if (cuig.Length == _table.Columns.Count) + return ups.Length == data.Count ? -998 : -997; + + var updateSource = data.Select(a => a.Value).ToArray(); + var update = this.OrmUpdate(null).SetSource(updateSource).IgnoreColumns(cuig); + + var affrows = await update.ExecuteAffrowsAsync(); + + foreach (var newval in data) { + if (_states.TryGetValue(newval.Key, out var tryold)) + _fsql.MapEntityValue(_entityType, newval.Value, tryold.Value); + if (newval.OldValue != null) + _fsql.MapEntityValue(_entityType, newval.Value, newval.OldValue); + } + return affrows; + } + + //等待下次对比再保存 + return 0; + } + async public Task UpdateAsync(TEntity data) { + var exists = ExistsInStates(data); + if (exists == null) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); + if (exists == false) { + var olddata = await OrmSelect(data).FirstAsync(); + if (olddata == null) throw new Exception($"不可更新,数据库不存在该记录:{_fsql.GetEntityString(_entityType, data)}"); + } + + await UpdateRangePrivAsync(new[] { data }, true); + } + public Task UpdateRangeAsync(IEnumerable data) => UpdateRangePrivAsync(data, true); + async Task UpdateRangePrivAsync(IEnumerable data, bool isCheck) { + if (CanUpdate(data, true) == false) return; + foreach (var item in data) { + if (_dicUpdateTimes.ContainsKey(item)) + await DbContextExecCommandAsync(); + _dicUpdateTimes.Add(item, 1); + + var state = CreateEntityState(item); + state.OldValue = item; + EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); + } + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + await AddOrUpdateNavigateListAsync(item); + } + #endregion + + #region RemoveAsync + async Task DbContextBetchRemoveAsync(EntityState[] dels) { + if (dels.Any() == false) return 0; + var affrows = await this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrowsAsync(); + return Math.Max(dels.Length, affrows); + } + #endregion + + #region AddOrUpdateAsync + async public Task AddOrUpdateAsync(TEntity data) { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (_table.Primarys.Any() == false) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); + + var flagExists = ExistsInStates(data); + if (flagExists == false) { + var olddata = await OrmSelect(data).FirstAsync(); + if (olddata == null) flagExists = false; + } + + if (flagExists == true && CanUpdate(data, false)) { + await DbContextExecCommandAsync(); + var affrows = _ctx._affrows; + await UpdateRangePrivAsync(new[] { data }, false); + await DbContextExecCommandAsync(); + affrows = _ctx._affrows - affrows; + if (affrows > 0) return; + } + if (CanAdd(data, false)) { + _fsql.ClearEntityPrimaryValueWithIdentity(_entityType, data); + await AddPrivAsync(data, false); + } + } + #endregion + } +} diff --git a/FreeSql.DbContext/DbSet/DbSetSync.cs b/FreeSql.DbContext/DbSet/DbSetSync.cs new file mode 100644 index 00000000..c41c1d77 --- /dev/null +++ b/FreeSql.DbContext/DbSet/DbSetSync.cs @@ -0,0 +1,305 @@ +using FreeSql.Extensions.EntityUtil; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; + +namespace FreeSql { + partial class DbSet { + + void DbContextExecCommand() { + _dicUpdateTimes.Clear(); + _ctx.ExecCommand(); + } + + int DbContextBetchAdd(EntityState[] adds) { + if (adds.Any() == false) return 0; + var affrows = this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrows(); + return affrows; + } + + #region Add + void AddPriv(TEntity data, bool isCheck) { + if (isCheck && CanAdd(data, true) == false) return; + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + if (_tableIdentitys.Length == 1) { + DbContextExecCommand(); + var idtval = this.OrmInsert(data).ExecuteIdentity(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + AddOrUpdateNavigateList(data); + } else { + DbContextExecCommand(); + var newval = this.OrmInsert(data).ExecuteInserted().First(); + IncrAffrows(1); + _fsql.MapEntityValue(_entityType, newval, data); + Attach(newval); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + AddOrUpdateNavigateList(data); + } + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + if (_tableIdentitys.Length == 1) { + DbContextExecCommand(); + var idtval = this.OrmInsert(data).ExecuteIdentity(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + AddOrUpdateNavigateList(data); + } + return; + } + } + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); + Attach(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + AddOrUpdateNavigateList(data); + } + /// + /// 添加 + /// + /// + public void Add(TEntity data) => AddPriv(data, true); + public void AddRange(IEnumerable data) { + if (CanAdd(data, true) == false) return; + if (data.ElementAtOrDefault(1) == default(TEntity)) { + Add(data.First()); + return; + } + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + DbContextExecCommand(); + var rets = this.OrmInsert(data).ExecuteInserted(); + if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); + var idx = 0; + foreach (var s in data) + _fsql.MapEntityValue(_entityType, rets[idx++], s); + IncrAffrows(rets.Count); + AttachRange(rets); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + AddOrUpdateNavigateList(item); + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + foreach (var s in data) + AddPriv(s, false); + return; + } + } else { + //进入队列,等待 SaveChanges 时执行 + foreach (var item in data) + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(item)); + AttachRange(data); + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + AddOrUpdateNavigateList(item); + } + } + static ConcurrentDictionary> _dicLazyIsSetField = new ConcurrentDictionary>(); + void AddOrUpdateNavigateList(TEntity item) { + Type itemType = null; + foreach (var prop in _table.Properties) { + if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue; + if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue; + + object propVal = null; + + if (itemType == null) itemType = item.GetType(); + if (_table.TypeLazy != null && itemType == _table.TypeLazy) { + var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary()).GetOrAdd(prop.Key, propName => + _table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance)); + if (lazyField != null) { + var lazyFieldValue = (bool)lazyField.GetValue(item); + if (lazyFieldValue == false) continue; + } + propVal = prop.Value.GetValue(item); + } else { + propVal = prop.Value.GetValue(item); + if (propVal == null) continue; + } + + var tref = _table.GetTableRef(prop.Key, true); + if (tref == null) continue; + + switch(tref.RefType) { + case Internal.Model.TableRefType.OneToOne: + case Internal.Model.TableRefType.ManyToOne: + case Internal.Model.TableRefType.ManyToMany: + continue; + case Internal.Model.TableRefType.OneToMany: + var propValEach = propVal as IEnumerable; + if (propValEach == null) continue; + object dbset = null; + MethodInfo dbsetAddOrUpdate = null; + foreach (var propValItem in propValEach) { + if (dbset == null) { + dbset = _ctx.Set(tref.RefEntityType); + dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdate", new Type[] { tref.RefEntityType }); + } + for (var colidx = 0; colidx < tref.Columns.Count; colidx++) { + tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName] + .SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item)); + } + dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem }); + } + break; + } + } + } + #endregion + + #region Update + int DbContextBetchUpdate(EntityState[] ups) => DbContextBetchUpdatePriv(ups, false); + int DbContextBetchUpdateNow(EntityState[] ups) => DbContextBetchUpdatePriv(ups, true); + int DbContextBetchUpdatePriv(EntityState[] ups, bool isLiveUpdate) { + if (ups.Any() == false) return 0; + var uplst1 = ups[ups.Length - 1]; + var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null; + + if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; + var lstval2 = default(EntityState); + if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"特别错误:更新失败,数据未被跟踪:{_fsql.GetEntityString(_entityType, uplst2.Value)}"); + + var cuig1 = _fsql.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true); + var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null; + + List data = null; + string[] cuig = null; + if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { + //最后一个不保存 + data = ups.ToList(); + data.RemoveAt(ups.Length - 1); + cuig = cuig2; + } else if (isLiveUpdate) { + //立即保存 + data = ups.ToList(); + cuig = cuig1; + } + + if (data?.Count > 0) { + + if (cuig.Length == _table.Columns.Count) + return ups.Length == data.Count ? -998 : -997; + + var updateSource = data.Select(a => a.Value).ToArray(); + var update = this.OrmUpdate(null).SetSource(updateSource).IgnoreColumns(cuig); + + var affrows = update.ExecuteAffrows(); + + foreach (var newval in data) { + if (_states.TryGetValue(newval.Key, out var tryold)) + _fsql.MapEntityValue(_entityType, newval.Value, tryold.Value); + if (newval.OldValue != null) + _fsql.MapEntityValue(_entityType, newval.Value, newval.OldValue); + } + return affrows; + } + + //等待下次对比再保存 + return 0; + } + + Dictionary _dicUpdateTimes = new Dictionary(); + /// + /// 更新 + /// + /// + public void Update(TEntity data) { + var exists = ExistsInStates(data); + if (exists == null) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); + if (exists == false) { + var olddata = OrmSelect(data).First(); + if (olddata == null) throw new Exception($"不可更新,数据库不存在该记录:{_fsql.GetEntityString(_entityType, data)}"); + } + + UpdateRangePriv(new[] { data }, true); + } + public void UpdateRange(IEnumerable data) => UpdateRangePriv(data, true); + void UpdateRangePriv(IEnumerable data, bool isCheck) { + if (CanUpdate(data, true) == false) return; + foreach (var item in data) { + if (_dicUpdateTimes.ContainsKey(item)) + DbContextExecCommand(); + _dicUpdateTimes.Add(item, 1); + + var state = CreateEntityState(item); + state.OldValue = item; + EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); + } + if (_ctx.Options.EnableAddOrUpdateNavigateList) + foreach (var item in data) + AddOrUpdateNavigateList(item); + } + #endregion + + #region Remove + int DbContextBetchRemove(EntityState[] dels) { + if (dels.Any() == false) return 0; + var affrows = this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrows(); + return Math.Max(dels.Length, affrows); + } + + /// + /// 删除 + /// + /// + public void Remove(TEntity data) => RemoveRange(new[] { data }); + public void RemoveRange(IEnumerable data) { + if (CanRemove(data, true) == false) return; + foreach (var item in data) { + var state = CreateEntityState(item); + _states.TryRemove(state.Key, out var trystate); + _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, item); + + EnqueueToDbContext(DbContext.ExecCommandInfoType.Delete, state); + } + } + #endregion + + #region AddOrUpdate + /// + /// 添加或更新 + /// + /// + public void AddOrUpdate(TEntity data) { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (_table.Primarys.Any() == false) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); + + var flagExists = ExistsInStates(data); + if (flagExists == false) { + var olddata = OrmSelect(data).First(); + if (olddata == null) flagExists = false; + } + + if (flagExists == true && CanUpdate(data, false)) { + DbContextExecCommand(); + var affrows = _ctx._affrows; + UpdateRangePriv(new[] { data }, false); + DbContextExecCommand(); + affrows = _ctx._affrows - affrows; + if (affrows > 0) return; + } + if (CanAdd(data, false)) { + _fsql.ClearEntityPrimaryValueWithIdentity(_entityType, data); + AddPriv(data, false); + } + } + #endregion + } +} diff --git a/FreeSql.DbContext/Extenssions/DependencyInjection.cs b/FreeSql.DbContext/Extenssions/DependencyInjection.cs new file mode 100644 index 00000000..a91635a5 --- /dev/null +++ b/FreeSql.DbContext/Extenssions/DependencyInjection.cs @@ -0,0 +1,32 @@ +#if ns20 + +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace FreeSql { + public static class DbContextDependencyInjection { + + public static IServiceCollection AddFreeDbContext(this IServiceCollection services, Action options) where TDbContext : DbContext { + + services.AddScoped(sp => { + var ctx = Activator.CreateInstance(); + + if (ctx._orm == null) { + var builder = new DbContextOptionsBuilder(); + options(builder); + ctx._orm = builder._fsql; + + if (ctx._orm == null) + throw new Exception("请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql"); + + ctx.InitPropSets(); + } + + return ctx; + }); + + return services; + } + } +} +#endif \ No newline at end of file diff --git a/FreeSql.DbContext/Extenssions/FreeSqlDbContextExtenssions.cs b/FreeSql.DbContext/Extenssions/FreeSqlDbContextExtenssions.cs new file mode 100644 index 00000000..7fae5c66 --- /dev/null +++ b/FreeSql.DbContext/Extenssions/FreeSqlDbContextExtenssions.cs @@ -0,0 +1,38 @@ +using FreeSql; +using System; +using System.Collections.Concurrent; + +public static class FreeSqlDbContextExtenssions { + + /// + /// 创建普通数据上下文档对象 + /// + /// + /// + public static DbContext CreateDbContext(this IFreeSql that) { + return new FreeContext(that); + } + + /// + /// 不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能 + /// + /// + /// + /// + public static ISelect NoTracking(this ISelect select) where T : class { + return select.TrackToList(null); + } + + /// + /// 设置 DbContext 选项设置 + /// + /// + /// + public static void SetDbContextOptions(this IFreeSql that, Action options) { + if (options == null) return; + var cfg = _dicSetDbContextOptions.GetOrAdd(that, t => new DbContextOptions()); + options(cfg); + _dicSetDbContextOptions.AddOrUpdate(that, cfg, (t, o) => cfg); + } + internal static ConcurrentDictionary _dicSetDbContextOptions = new ConcurrentDictionary(); +} \ No newline at end of file diff --git a/FreeSql.DbContext/FreeSql.DbContext.csproj b/FreeSql.DbContext/FreeSql.DbContext.csproj new file mode 100644 index 00000000..3371ed37 --- /dev/null +++ b/FreeSql.DbContext/FreeSql.DbContext.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0;net45 + 0.7.1 + true + YeXiangQin + FreeSql is the most convenient ORM in dotnet. It supports Mysql, Postgresql, SqlServer, Oracle and Sqlite. + https://github.com/2881099/FreeSql.DbContext + FreeSql ORM DbContext + git + MIT + $(AssemblyName) + $(AssemblyName) + true + true + + + + FreeSql.DbContext.xml + 3 + + + + ns20;netstandard20 + + + + + + + + + + + diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml new file mode 100644 index 00000000..bdb7a92e --- /dev/null +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -0,0 +1,259 @@ + + + + FreeSql.DbContext + + + + + 添加 + + + + + + + 更新 + + + + + + + 删除 + + + + + + + 添加或更新 + + + + + + + 附加实体,可用于不查询就更新或删除 + + + + + + + 是否开启一对多,联级保存功能 + + + + + 动态Type,在使用 DbSet<object> 后使用本方法,指定实体类型 + + + + + + + 附加实体,可用于不查询就更新或删除 + + + + + + 清空状态数据 + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 添加或更新 + + + + + + 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点 + + + + 数据过滤 + 验证 + + + + + 在工作单元内创建联合主键的仓储类,工作单元下的仓储操作具有事务特点 + + + 数据过滤 + 验证 + + + + + 在工作单元内创建仓库类,工作单元下的仓储操作具有事务特点 + + + 数据过滤 + 验证 + 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + + + + + 开启过滤器,若使用 using 则使用完后,恢复为原有状态 + + 过滤器名称 + + + + + 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态 + + + + + + 禁用过滤器,若使用 using 则使用完后,恢复为原有状态 + + + + + + + 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态 + + + + + + 动态Type,在使用 Repository<object> 后使用本方法,指定实体类型 + + + + + + + 清空状态数据 + + + + + 附加实体,可用于不查询就更新或删除 + + + + + + 是否启用工作单元 + + + + + 禁用工作单元 + + + 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 + + + + + 开启工作单元 + + + + + 是否启用工作单元 + + + + + 禁用工作单元 + + + 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 + + + + + 创建普通数据上下文档对象 + + + + + + + 不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能 + + + + + + + + 设置 DbContext 选项设置 + + + + + + + 返回默认仓库类 + + + + + 数据过滤 + 验证 + + + + + 返回默认仓库类,适用联合主键的仓储类 + + + + 数据过滤 + 验证 + + + + + 返回仓库类 + + + + 数据过滤 + 验证 + 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + + + + + 合并两个仓储的设置(过滤+分表),以便查询 + + + + + + + + + + 创建基于仓储功能的工作单元,务必使用 using 包含使用 + + + + + + diff --git a/FreeSql.DbContext/Repository/ContextSet/RepositoryDbContext.cs b/FreeSql.DbContext/Repository/ContextSet/RepositoryDbContext.cs new file mode 100644 index 00000000..ac3dbf7a --- /dev/null +++ b/FreeSql.DbContext/Repository/ContextSet/RepositoryDbContext.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Threading.Tasks; + +namespace FreeSql { + internal class RepositoryDbContext : DbContext { + + protected IBaseRepository _repos; + public RepositoryDbContext(IFreeSql orm, IBaseRepository repos) : base() { + _orm = orm; + _repos = repos; + _isUseUnitOfWork = false; + _uowPriv = _repos.UnitOfWork; + } + + + static ConcurrentDictionary _dicGetRepositoryDbField = new ConcurrentDictionary(); + static FieldInfo GetRepositoryDbField(Type type) => _dicGetRepositoryDbField.GetOrAdd(type, tp => typeof(BaseRepository<,>).MakeGenericType(tp, typeof(int)).GetField("_dbPriv", BindingFlags.Instance | BindingFlags.NonPublic)); + public override IDbSet Set(Type entityType) { + if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType]; + + var tb = _orm.CodeFirst.GetTableByEntity(entityType); + if (tb == null) return null; + + object repos = _repos; + if (entityType != _repos.EntityType) { + repos = Activator.CreateInstance(typeof(DefaultRepository<,>).MakeGenericType(entityType, typeof(int)), _repos.Orm); + (repos as IBaseRepository).UnitOfWork = _repos.UnitOfWork; + GetRepositoryDbField(entityType).SetValue(repos, this); + + typeof(RepositoryDbContext).GetMethod("SetRepositoryDataFilter").MakeGenericMethod(_repos.EntityType) + .Invoke(null, new object[] { repos, _repos }); + } + + var sd = Activator.CreateInstance(typeof(RepositoryDbSet<>).MakeGenericType(entityType), repos) as IDbSet; + if (entityType != typeof(object)) _dicSet.Add(entityType, sd); + return sd; + } + + public static void SetRepositoryDataFilter(object repos, BaseRepository baseRepo) where TEntity : class { + var filter = baseRepo.DataFilter as DataFilter; + DataFilterUtil.SetRepositoryDataFilter(repos, fl => { + foreach (var f in filter._filters) + fl.Apply(f.Key, f.Value.Expression); + }); + } + + public override int SaveChanges() { + ExecCommand(); + var ret = _affrows; + _affrows = 0; + return ret; + } + async public override Task SaveChangesAsync() { + await ExecCommandAsync(); + var ret = _affrows; + _affrows = 0; + return ret; + } + } +} diff --git a/FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs b/FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs new file mode 100644 index 00000000..b50225ed --- /dev/null +++ b/FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs @@ -0,0 +1,60 @@ +using FreeSql.Extensions.EntityUtil; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FreeSql { + internal class RepositoryDbSet : DbSet where TEntity : class { + + protected BaseRepository _repos; + public RepositoryDbSet(BaseRepository repos) { + _ctx = repos._db; + _fsql = repos.Orm; + _uow = repos.UnitOfWork; + _repos = repos; + } + + protected override ISelect OrmSelect(object dywhere) { + var select = base.OrmSelect(dywhere); + + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) select.Where(filter.Value.Expression); + return select.AsTable(_repos.AsTableSelectInternal); + } + internal ISelect OrmSelectInternal(object dywhere) => OrmSelect(dywhere); + protected override IUpdate OrmUpdate(IEnumerable entitys) { + var update = base.OrmUpdate(entitys); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) { + if (entitys != null) + foreach (var entity in entitys) + if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) + throw new Exception($"FreeSql.Repository Update 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},更新的数据不符合 {_fsql.GetEntityString(_entityType, entity)}"); + update.Where(filter.Value.Expression); + } + return update.AsTable(_repos.AsTableInternal); + } + internal IUpdate OrmUpdateInternal(IEnumerable entitys) => OrmUpdate(entitys); + protected override IDelete OrmDelete(object dywhere) { + var delete = base.OrmDelete(dywhere); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) delete.Where(filter.Value.Expression); + return delete.AsTable(_repos.AsTableInternal); + } + internal IDelete OrmDeleteInternal(object dywhere) => OrmDelete(dywhere); + protected override IInsert OrmInsert(TEntity entity) => OrmInsert(new[] { entity }); + protected override IInsert OrmInsert(IEnumerable entitys) { + var insert = base.OrmInsert(entitys); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) { + if (entitys != null) + foreach (var entity in entitys) + if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) + throw new Exception($"FreeSql.Repository Insert 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},插入的数据不符合 {_fsql.GetEntityString(_entityType, entity)}"); + } + return insert.AsTable(_repos.AsTableInternal); + } + internal IInsert OrmInsertInternal(TEntity entity) => OrmInsert(entity); + internal IInsert OrmInsertInternal(IEnumerable entitys) => OrmInsert(entitys); + } +} diff --git a/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWork.cs b/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWork.cs new file mode 100644 index 00000000..d36c4887 --- /dev/null +++ b/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWork.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq.Expressions; + +namespace FreeSql { + + public interface IRepositoryUnitOfWork : IUnitOfWork { + /// + /// 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点 + /// + /// + /// + /// 数据过滤 + 验证 + /// + DefaultRepository GetRepository(Expression> filter = null) where TEntity : class; + + /// + /// 在工作单元内创建联合主键的仓储类,工作单元下的仓储操作具有事务特点 + /// + /// + /// 数据过滤 + 验证 + /// + BaseRepository GetRepository(Expression> filter = null) where TEntity : class; + + /// + /// 在工作单元内创建仓库类,工作单元下的仓储操作具有事务特点 + /// + /// + /// 数据过滤 + 验证 + /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + /// + GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class; + } + + class RepositoryUnitOfWork : UnitOfWork, IRepositoryUnitOfWork { + + public RepositoryUnitOfWork(IFreeSql fsql) : base(fsql) { + } + + public GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class { + var repos = new GuidRepository(_fsql, filter, asTable); + repos.UnitOfWork = this; + return repos; + } + + public DefaultRepository GetRepository(Expression> filter = null) where TEntity : class { + var repos = new DefaultRepository(_fsql, filter); + repos.UnitOfWork = this; + return repos; + } + + public BaseRepository GetRepository(Expression> filter = null) where TEntity : class { + var repos = new DefaultRepository(_fsql, filter); + repos.UnitOfWork = this; + return repos; + } + } +} diff --git a/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs b/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs new file mode 100644 index 00000000..d65a3b0a --- /dev/null +++ b/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Linq; + +namespace FreeSql { + public interface IDataFilter : IDisposable where TEntity : class { + + IDataFilter Apply(string filterName, Expression> filterAndValidateExp); + + /// + /// 开启过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// 过滤器名称 + /// + IDisposable Enable(params string[] filterName); + /// + /// 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + IDisposable EnableAll(); + + /// + /// 禁用过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + /// + IDisposable Disable(params string[] filterName); + /// + /// 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + IDisposable DisableAll(); + + bool IsEnabled(string filterName); + } + + internal class DataFilter : IDataFilter where TEntity : class { + + internal class FilterItem { + public Expression> Expression { get; set; } + Func _expressionDelegate; + public Func ExpressionDelegate => _expressionDelegate ?? (_expressionDelegate = Expression?.Compile()); + public bool IsEnabled { get; set; } + } + + internal ConcurrentDictionary _filters = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + public IDataFilter Apply(string filterName, Expression> filterAndValidateExp) { + + if (filterName == null) + throw new ArgumentNullException(nameof(filterName)); + if (filterAndValidateExp == null) return this; + + var filterItem = new FilterItem { Expression = filterAndValidateExp, IsEnabled = true }; + _filters.AddOrUpdate(filterName, filterItem, (k, v) => filterItem); + return this; + } + + public IDisposable Disable(params string[] filterName) { + if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); + + List restore = new List(); + foreach (var name in filterName) { + if (_filters.TryGetValue(name, out var tryfi)) { + if (tryfi.IsEnabled) { + restore.Add(name); + tryfi.IsEnabled = false; + } + } + } + return new UsingAny(() => this.Enable(restore.ToArray())); + } + public IDisposable DisableAll() { + List restore = new List(); + foreach (var val in _filters) { + if (val.Value.IsEnabled) { + restore.Add(val.Key); + val.Value.IsEnabled = false; + } + } + return new UsingAny(() => this.Enable(restore.ToArray())); + } + class UsingAny : IDisposable { + Action _ondis; + public UsingAny(Action ondis) { + _ondis = ondis; + } + public void Dispose() { + _ondis?.Invoke(); + } + } + + public IDisposable Enable(params string[] filterName) { + if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); + + List restore = new List(); + foreach (var name in filterName) { + if (_filters.TryGetValue(name, out var tryfi)) { + if (tryfi.IsEnabled == false) { + restore.Add(name); + tryfi.IsEnabled = true; + } + } + } + return new UsingAny(() => this.Disable(restore.ToArray())); + } + public IDisposable EnableAll() { + List restore = new List(); + foreach (var val in _filters) { + if (val.Value.IsEnabled == false) { + restore.Add(val.Key); + val.Value.IsEnabled = true; + } + } + return new UsingAny(() => this.Disable(restore.ToArray())); + } + + public bool IsEnabled(string filterName) { + if (filterName == null) return false; + return _filters.TryGetValue(filterName, out var tryfi) ? tryfi.IsEnabled : false; + } + + ~DataFilter() { + this.Dispose(); + } + public void Dispose() { + _filters.Clear(); + } + } + + public class FluentDataFilter : IDisposable { + + internal List<(Type type, string name, LambdaExpression exp)> _filters = new List<(Type type, string name, LambdaExpression exp)>(); + + public FluentDataFilter Apply(string filterName, Expression> filterAndValidateExp) where TEntity : class { + if (filterName == null) + throw new ArgumentNullException(nameof(filterName)); + if (filterAndValidateExp == null) return this; + + _filters.Add((typeof(TEntity), filterName, filterAndValidateExp)); + return this; + } + + ~FluentDataFilter() { + this.Dispose(); + } + public void Dispose() { + _filters.Clear(); + } + } +} diff --git a/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs b/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs new file mode 100644 index 00000000..b94905b4 --- /dev/null +++ b/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace FreeSql { + + internal class DataFilterUtil { + + internal static Action _globalDataFilter; + + static ConcurrentDictionary _dicSetRepositoryDataFilterApplyDataFilterFunc = new ConcurrentDictionary(); + static ConcurrentDictionary> _dicSetRepositoryDataFilterConvertFilterNotExists = new ConcurrentDictionary>(); + internal static void SetRepositoryDataFilter(object repos, Action scopedDataFilter) { + if (scopedDataFilter != null) { + SetRepositoryDataFilter(repos, null); + } + if (scopedDataFilter == null) { + scopedDataFilter = _globalDataFilter; + } + if (scopedDataFilter == null) return; + using (var globalFilter = new FluentDataFilter()) { + scopedDataFilter(globalFilter); + + var type = repos.GetType(); + Type entityType = (repos as IBaseRepository).EntityType; + if (entityType == null) throw new Exception("FreeSql.Repository 设置过滤器失败,原因是对象不属于 IRepository"); + + var notExists = _dicSetRepositoryDataFilterConvertFilterNotExists.GetOrAdd(type, t => new ConcurrentDictionary()); + var newFilter = new Dictionary(); + foreach (var gf in globalFilter._filters) { + if (notExists.ContainsKey(gf.name)) continue; + + LambdaExpression newExp = null; + var filterParameter1 = Expression.Parameter(entityType, gf.exp.Parameters[0].Name); + try { + newExp = Expression.Lambda( + typeof(Func<,>).MakeGenericType(entityType, typeof(bool)), + new ReplaceVisitor().Modify(gf.exp.Body, filterParameter1), + filterParameter1 + ); + } catch { + notExists.TryAdd(gf.name, true); //防止第二次错误 + continue; + } + newFilter.Add(gf.name, newExp); + } + if (newFilter.Any() == false) return; + + var del = _dicSetRepositoryDataFilterApplyDataFilterFunc.GetOrAdd(type, t => { + var reposParameter = Expression.Parameter(type); + var nameParameter = Expression.Parameter(typeof(string)); + var expressionParameter = Expression.Parameter( + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(entityType, typeof(bool))) + ); + return Expression.Lambda( + Expression.Block( + Expression.Call(reposParameter, type.GetMethod("ApplyDataFilter", BindingFlags.Instance | BindingFlags.NonPublic), nameParameter, expressionParameter) + ), + new[] { + reposParameter, nameParameter, expressionParameter + } + ).Compile(); + }); + foreach (var nf in newFilter) { + del.DynamicInvoke(repos, nf.Key, nf.Value); + } + newFilter.Clear(); + } + } + } + + class ReplaceVisitor : ExpressionVisitor { + private ParameterExpression parameter; + + public Expression Modify(Expression expression, ParameterExpression parameter) { + this.parameter = parameter; + return Visit(expression); + } + + protected override Expression VisitMember(MemberExpression node) { + if (node.Expression?.NodeType == ExpressionType.Parameter) + return Expression.Property(parameter, node.Member.Name); + return base.VisitMember(node); + } + } +} diff --git a/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs b/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs new file mode 100644 index 00000000..9265774d --- /dev/null +++ b/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs @@ -0,0 +1,39 @@ +#if ns20 + +using System; +using System.Reflection; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; + +namespace FreeSql { + public static class FreeSqlRepositoryDependencyInjection { + + public static IServiceCollection AddFreeRepository(this IServiceCollection services, Action globalDataFilter = null, params Assembly[] assemblies) { + + DataFilterUtil._globalDataFilter = globalDataFilter; + + services.AddScoped(typeof(IReadOnlyRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(IBasicRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(BaseRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(GuidRepository<>)); + + services.AddScoped(typeof(IReadOnlyRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(IBasicRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(BaseRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(DefaultRepository<,>)); + + if (assemblies?.Any() == true) { + foreach(var asse in assemblies) { + foreach (var repos in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(IBaseRepository).IsAssignableFrom(a))) { + + services.AddScoped(repos); + } + } + } + + return services; + } + } +} + +#endif \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs b/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs new file mode 100644 index 00000000..5c4fedd3 --- /dev/null +++ b/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs @@ -0,0 +1,65 @@ +using FreeSql; +using System; +using System.Linq.Expressions; +using System.Linq; + +public static class FreeSqlRepositoryExtenssions { + + /// + /// 返回默认仓库类 + /// + /// + /// + /// + /// 数据过滤 + 验证 + /// + public static DefaultRepository GetRepository(this IFreeSql that, Expression> filter = null) where TEntity : class { + return new DefaultRepository(that, filter); + } + + /// + /// 返回默认仓库类,适用联合主键的仓储类 + /// + /// + /// + /// 数据过滤 + 验证 + /// + public static BaseRepository GetRepository(this IFreeSql that, Expression> filter = null) where TEntity : class { + return new DefaultRepository(that, filter); + } + + /// + /// 返回仓库类 + /// + /// + /// + /// 数据过滤 + 验证 + /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + /// + public static GuidRepository GetGuidRepository(this IFreeSql that, Expression> filter = null, Func asTable = null) where TEntity : class { + return new GuidRepository(that, filter, asTable); + } + + /// + /// 合并两个仓储的设置(过滤+分表),以便查询 + /// + /// + /// + /// + /// + /// + public static ISelect FromRepository(this ISelect that, BaseRepository repos) where TEntity : class where T2 : class { + var filters = (repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) that.Where(filter.Value.Expression); + return that.AsTable(repos.AsTableSelectInternal); + } + + /// + /// 创建基于仓储功能的工作单元,务必使用 using 包含使用 + /// + /// + /// + public static IRepositoryUnitOfWork CreateUnitOfWork(this IFreeSql that) { + return new RepositoryUnitOfWork(that); + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs new file mode 100644 index 00000000..5ecbd52d --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + public abstract class BaseRepository : IBaseRepository + where TEntity : class { + + internal RepositoryDbContext _dbPriv; + internal RepositoryDbContext _db => _dbPriv ?? (_dbPriv = new RepositoryDbContext(Orm, this)); + internal RepositoryDbSet _dbsetPriv; + internal RepositoryDbSet _dbset => _dbsetPriv ?? (_dbsetPriv = _db.Set() as RepositoryDbSet); + public IDataFilter DataFilter { get; } = new DataFilter(); + Func _asTableVal; + protected Func AsTable { + get => _asTableVal; + set { + _asTableVal = value; + AsTableSelect = value == null ? null : new Func((a, b) => a == EntityType ? value(b) : null); + } + } + internal Func AsTableInternal => AsTable; + protected Func AsTableSelect { get; private set; } + internal Func AsTableSelectInternal => AsTableSelect; + + protected void ApplyDataFilter(string name, Expression> exp) => DataFilter.Apply(name, exp); + + protected BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) { + Orm = fsql; + DataFilterUtil.SetRepositoryDataFilter(this, null); + DataFilter.Apply("", filter); + AsTable = asTable; + } + + ~BaseRepository() { + this.Dispose(); + } + bool _isdisposed = false; + public void Dispose() { + if (_isdisposed) return; + try { + _dbsetPriv?.Dispose(); + _dbPriv?.Dispose(); + this.DataFilter.Dispose(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } + } + public Type EntityType => _dbsetPriv?.EntityType ?? typeof(TEntity); + public void AsType(Type entityType) => _dbset.AsType(entityType); + + public IFreeSql Orm { get; private set; } + public IUnitOfWork UnitOfWork { get; set; } + public IUpdate UpdateDiy => _dbset.OrmUpdateInternal(null); + + public ISelect Select => _dbset.OrmSelectInternal(null); + public ISelect Where(Expression> exp) => _dbset.OrmSelectInternal(null).Where(exp); + public ISelect WhereIf(bool condition, Expression> exp) => _dbset.OrmSelectInternal(null).WhereIf(condition, exp); + + public int Delete(Expression> predicate) => _dbset.OrmDeleteInternal(null).Where(predicate).ExecuteAffrows(); + public Task DeleteAsync(Expression> predicate) => _dbset.OrmDeleteInternal(null).Where(predicate).ExecuteAffrowsAsync(); + + public int Delete(TEntity entity) { + _dbset.Remove(entity); + return _db.SaveChanges(); + } + public Task DeleteAsync(TEntity entity) { + _dbset.Remove(entity); + return _db.SaveChangesAsync(); + } + public int Delete(IEnumerable entitys) { + _dbset.RemoveRange(entitys); + return _db.SaveChanges(); + } + public Task DeleteAsync(IEnumerable entitys) { + _dbset.RemoveRange(entitys); + return _db.SaveChangesAsync(); + } + + public virtual TEntity Insert(TEntity entity) { + _dbset.Add(entity); + _db.SaveChanges(); + return entity; + } + async public virtual Task InsertAsync(TEntity entity) { + await _dbset.AddAsync(entity); + _db.SaveChanges(); + return entity; + } + public virtual List Insert(IEnumerable entitys) { + _dbset.AddRange(entitys); + _db.SaveChanges(); + return entitys.ToList(); + } + async public virtual Task> InsertAsync(IEnumerable entitys) { + await _dbset.AddRangeAsync(entitys); + await _db.SaveChangesAsync(); + return entitys.ToList(); + } + + public int Update(TEntity entity) { + _dbset.Update(entity); + return _db.SaveChanges(); + } + public Task UpdateAsync(TEntity entity) { + _dbset.Update(entity); + return _db.SaveChangesAsync(); + } + public int Update(IEnumerable entitys) { + _dbset.UpdateRange(entitys); + return _db.SaveChanges(); + } + public Task UpdateAsync(IEnumerable entitys) { + _dbset.UpdateRange(entitys); + return _db.SaveChangesAsync(); + } + + public void Attach(TEntity data) => _db.Attach(data); + public void Attach(IEnumerable data) => _db.AttachRange(data); + public void FlushState() => _dbset.FlushState(); + + public TEntity InsertOrUpdate(TEntity entity) { + _dbset.AddOrUpdate(entity); + _db.SaveChanges(); + return entity; + } + async public Task InsertOrUpdateAsync(TEntity entity) { + await _dbset.AddOrUpdateAsync(entity); + _db.SaveChanges(); + return entity; + } + } + + public abstract class BaseRepository : BaseRepository, IBaseRepository + where TEntity : class { + + public BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) : base(fsql, filter, asTable) { + } + + public int Delete(TKey id) { + var stateKey = string.Concat(id); + _dbset._statesInternal.TryRemove(stateKey, out var trystate); + return _dbset.OrmDeleteInternal(id).ExecuteAffrows(); + } + public Task DeleteAsync(TKey id) { + var stateKey = string.Concat(id); + _dbset._statesInternal.TryRemove(stateKey, out var trystate); + return _dbset.OrmDeleteInternal(id).ExecuteAffrowsAsync(); + } + + public TEntity Find(TKey id) => _dbset.OrmSelectInternal(id).ToOne(); + public Task FindAsync(TKey id) => _dbset.OrmSelectInternal(id).ToOneAsync(); + + public TEntity Get(TKey id) => Find(id); + public Task GetAsync(TKey id) => FindAsync(id); + } +} diff --git a/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs new file mode 100644 index 00000000..8a711e2f --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs @@ -0,0 +1,16 @@ +using System; +using System.Linq.Expressions; + +namespace FreeSql { + public class DefaultRepository : + BaseRepository + where TEntity : class { + + public DefaultRepository(IFreeSql fsql) : base(fsql, null, null) { + + } + + public DefaultRepository(IFreeSql fsql, Expression> filter) : base(fsql, filter, null) { + } + } +} diff --git a/FreeSql.DbContext/Repository/Repository/GuidRepository.cs b/FreeSql.DbContext/Repository/Repository/GuidRepository.cs new file mode 100644 index 00000000..f7eba195 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/GuidRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq.Expressions; + +namespace FreeSql { + public class GuidRepository : + BaseRepository + where TEntity : class { + + public GuidRepository(IFreeSql fsql) : this(fsql, null, null) { + + } + public GuidRepository(IFreeSql fsql, Expression> filter, Func asTable) : base(fsql, filter, asTable) { + } + } +} diff --git a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs new file mode 100644 index 00000000..f1919f29 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + + public interface IBaseRepository : IDisposable { + Type EntityType { get; } + IUnitOfWork UnitOfWork { get; set; } + IFreeSql Orm { get; } + + /// + /// 动态Type,在使用 Repository<object> 后使用本方法,指定实体类型 + /// + /// + /// + void AsType(Type entityType); + } + + public interface IBaseRepository : IReadOnlyRepository, IBasicRepository + where TEntity : class { + int Delete(Expression> predicate); + + Task DeleteAsync(Expression> predicate); + } + + public interface IBaseRepository : IBaseRepository, IReadOnlyRepository, IBasicRepository + where TEntity : class { + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs b/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs new file mode 100644 index 00000000..33d79ec6 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FreeSql { + public interface IBasicRepository : IReadOnlyRepository + where TEntity : class { + TEntity Insert(TEntity entity); + List Insert(IEnumerable entitys); + Task InsertAsync(TEntity entity); + Task> InsertAsync(IEnumerable entitys); + + /// + /// 清空状态数据 + /// + void FlushState(); + /// + /// 附加实体,可用于不查询就更新或删除 + /// + /// + void Attach(TEntity entity); + void Attach(IEnumerable entity); + int Update(TEntity entity); + int Update(IEnumerable entitys); + Task UpdateAsync(TEntity entity); + Task UpdateAsync(IEnumerable entitys); + + TEntity InsertOrUpdate(TEntity entity); + Task InsertOrUpdateAsync(TEntity entity); + + IUpdate UpdateDiy { get; } + + int Delete(TEntity entity); + int Delete(IEnumerable entitys); + Task DeleteAsync(TEntity entity); + Task DeleteAsync(IEnumerable entitys); + } + + public interface IBasicRepository : IBasicRepository, IReadOnlyRepository + where TEntity : class { + int Delete(TKey id); + + Task DeleteAsync(TKey id); + } +} + diff --git a/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs b/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs new file mode 100644 index 00000000..8b2f47ee --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + public interface IReadOnlyRepository : IBaseRepository + where TEntity : class { + + IDataFilter DataFilter { get; } + + ISelect Select { get; } + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + } + + public interface IReadOnlyRepository : IReadOnlyRepository + where TEntity : class { + TEntity Get(TKey id); + + Task GetAsync(TKey id); + + TEntity Find(TKey id); + + Task FindAsync(TKey id); + } +} diff --git a/FreeSql.DbContext/TempExtensions.cs b/FreeSql.DbContext/TempExtensions.cs new file mode 100644 index 00000000..fbb7bd53 --- /dev/null +++ b/FreeSql.DbContext/TempExtensions.cs @@ -0,0 +1,5 @@ + +namespace FreeSql.Extensions.EntityUtil { + public static class TempExtensions { + } +} diff --git a/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs b/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 00000000..8ded92ff --- /dev/null +++ b/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,34 @@ +using System; +using System.Data; +using System.Data.Common; + +namespace FreeSql { + public interface IUnitOfWork : IDisposable { + + DbTransaction GetOrBeginTransaction(bool isCreate = true); + + IsolationLevel? IsolationLevel { get; set; } + + /// + /// 是否启用工作单元 + /// + bool Enable { get; } + + void Commit(); + + void Rollback(); + + /// + /// 禁用工作单元 + /// + /// + /// 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 + /// + void Close(); + + /// + /// 开启工作单元 + /// + void Open(); + } +} diff --git a/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs b/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs new file mode 100644 index 00000000..5cbaf016 --- /dev/null +++ b/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,103 @@ +using SafeObjectPool; +using System; +using System.Data; +using System.Data.Common; + +namespace FreeSql { + class UnitOfWork : IUnitOfWork { + + protected IFreeSql _fsql; + protected Object _conn; + protected DbTransaction _tran; + + public UnitOfWork(IFreeSql fsql) { + _fsql = fsql; + } + + void ReturnObject() { + _fsql.Ado.MasterPool.Return(_conn); + _tran = null; + _conn = null; + } + + + /// + /// 是否启用工作单元 + /// + public bool Enable { get; private set; } = true; + + /// + /// 禁用工作单元 + /// + /// + /// 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 + /// + public void Close() + { + if (_tran != null) + { + throw new Exception("已开启事务,不能禁用工作单元"); + } + + Enable = false; + } + + public void Open() + { + Enable = true; + } + + public IsolationLevel? IsolationLevel { get; set; } + + public DbTransaction GetOrBeginTransaction(bool isCreate = true) { + + if (_tran != null) return _tran; + if (isCreate == false) return null; + if (!Enable) return null; + if (_conn != null) _fsql.Ado.MasterPool.Return(_conn); + + _conn = _fsql.Ado.MasterPool.Get(); + try { + _tran = IsolationLevel == null ? + _conn.Value.BeginTransaction() : + _conn.Value.BeginTransaction(IsolationLevel.Value); + } catch { + ReturnObject(); + throw; + } + return _tran; + } + + public void Commit() { + if (_tran != null) { + try { + _tran.Commit(); + } finally { + ReturnObject(); + } + } + } + public void Rollback() { + if (_tran != null) { + try { + _tran.Rollback(); + } finally { + ReturnObject(); + } + } + } + ~UnitOfWork() { + this.Dispose(); + } + bool _isdisposed = false; + public void Dispose() { + if (_isdisposed) return; + try { + this.Rollback(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } + } + } +} diff --git a/FreeSql.DbContext/readme.md b/FreeSql.DbContext/readme.md new file mode 100644 index 00000000..155847e5 --- /dev/null +++ b/FreeSql.DbContext/readme.md @@ -0,0 +1,284 @@ +这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 DbContext & DbSet、Repository & UnitOfWork 实现面向对象的特性(QQ群:4336577)。 + +> dotnet add package FreeSql.DbContext + +## 更新日志 + +### v0.6.5 + +- 修复 Repository 联级保存的 bug; +- 添加工作单元开启方法; +- 适配 .net framework 4.5、netstandard 2.0; + +### v0.6.1 + +- 拆分 FreeSql 小包引用,各数据库单独包、延时加载包; +- FreeSql.Extensions.LazyLoading +- FreeSql.Provider.MySql +- FreeSql.Provider.PostgreSQL +- FreeSql.Provider.SqlServer +- FreeSql.Provider.Sqlite +- FreeSql.Provider.Oracle +- 移除 IFreeSql.Cache,以及 ISelect.Caching 方法; +- 移除 IFreeSql.Log,包括内部原有的日志输出,改为 Trace.WriteLine; +- IAdo.Query\ 读取返回变为 List\\>; +- 定义 IFreeSql 和以前一样,移除了 UseCache、UseLogger 方法; + +## DbContext & DbSet + +```csharp +using (var ctx = new SongContext()) { + var song = new Song { BigNumber = "1000000000000000000" }; + ctx.Songs.Add(song); + + song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); + ctx.Songs.Update(song); + + var tag = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + ctx.Tags.Add(tag); + + ctx.SaveChanges(); +} +``` + +## Repository & UnitOfWork + +仓储与工作单元一起使用,工作单元具有事务特点。 + +```csharp +using (var unitOfWork = fsql.CreateUnitOfWork()) { + var songRepository = unitOfWork.GetRepository(); + var tagRepository = unitOfWork.GetRepository(); + + var song = new Song { BigNumber = "1000000000000000000" }; + songRepository.Insert(song); + + songRepository.Update(song); + + song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); + songRepository.Update(song); + + var tag = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + tagRepository.Insert(tag); + + ctx.Commit(); +} +``` + +## Repository + +简单使用仓储,有状态跟踪,它不包含事务的特点。 + +```csharp +var songRepository = fsql.GetRepository(); +var song = new Song { BigNumber = "1000000000000000000" }; +songRepository.Insert(song); +``` + +## IFreeSql 核心定义 + +```csharp +var fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseNoneCommandParameter(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) + .Build(); + +public class Song { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string BigNumber { get; set; } + + [Column(IsVersion = true)] //乐观锁 + public long versionRow { get; set; } +} +public class Tag { + [Column(IsIdentity = true)] + public int Id { get; set; } + + public int? Parent_id { get; set; } + public virtual Tag Parent { get; set; } + + public string Name { get; set; } + + public virtual ICollection Tags { get; set; } +} + +public class SongContext : DbContext { + public DbSet Songs { get; set; } + public DbSet Tags { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder builder) { + builder.UseFreeSql(fsql); + } +} +``` + +# 过滤器与验证 + +假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储: + +```csharp +var userRepository = fsql.GetGuidRepository(); +var topicRepository = fsql.GetGuidRepository(); +``` + +在开发过程中,总是担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 + +```csharp +var userRepository = fsql.GetGuidRepository(a => a.Id == 1); +var topicRepository = fsql.GetGuidRepository(a => a.UserId == 1); +``` + +* 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; +* 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常; + +# 分表与分库 + +FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。 + +```csharp +var logRepository = fsql.GetGuidRepository(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}"); +``` + +上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 + +合并两个仓储,实现分表下的联表查询: + +```csharp +fsql.GetGuidRepository().Select.FromRepository(logRepository) + .LeftJoin(b => b.UserId == a.Id) + .ToList(); +``` + +注意事项: + +* 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; +* 不可在分表分库的实体类型中使用《延时加载》; + +# 历史版本 + +### v0.5.23 + +- 增加 DbSet/Repository FlushState 手工清除状态管理数据; + +### v0.5.21 + +- 修复 AddOrUpdate/InsertOrUpdate 当主键无值时,仍然查询了一次数据库; +- 增加 查询数据时 TrackToList 对导航集合的状态跟踪; +- 完善 AddOrUpdateNavigateList 联级保存,忽略标记 IsIgnore 的集合属性; +- 完成 IFreeSql.Include、IncludeMany 功能; + +### v0.5.12 + +- 增加 工作单元开关,可在任意 Insert/Update/Delete 之前调用,以关闭工作单元使其失效;[PR #1](https://github.com/2881099/FreeSql.DbContext/pull/1) + +### v0.5.9 + +- 增加 linq to sql 的查询语法,以及单元测试,[wiki](https://github.com/2881099/FreeSql/wiki/LinqToSql); +- 修复 EnableAddOrUpdateNavigateList 设置对异步方法无效的 bug; + +### v0.5.8 + +- 增加 IFreeSql.SetDbContextOptions 设置 DbContext 的功能:开启或禁用连级一对多导航集合属性保存的功能,EnableAddOrUpdateNavigateList(默认开启); +- 增加 IUnitOfWork.IsolationLevel 设置事务级别; + +### v0.5.7 + +- 修复 UnitOfWork.GetRepository() 事务 bug,原因:仓储的每步操作都提交了事务; + +### v0.5.5 + +- 修复 MapEntityValue 对 IsIgnore 未处理的 bug; + +### v0.5.4 + +- 修复 Repository 追加导航集合的保存 bug; +- 公开 IRepository.Orm 对象; + +### v0.5.3 + +- 修复 实体跟踪的 bug,当查询到的实体自增值为 0 时重现; +- 优化 状态管理字典为 ConcurrentDictionary; + +### v0.5.2 + +- 优化 SqlServer UnitOfWork 使用bug,在 FreeSql 内部解决的; +- 补充 测试与支持联合主键的自增; + +### v0.5.1 + +- 补充 开放 DbContext.UnitOfWork 对象,方便扩展并保持在同一个事务执行; +- 补充 增加 DbSet\、Repository\ 使用方法,配合 AsType(实体类型),实现弱类型操作; +- 修复 DbContext.AddOrUpdate 传入 null 时,任然会查询一次数据库的 bug; +- 优化 DbContext.AddOrUpdate 未添加实体主键的错误提醒; +- 修复 DbContext.Set\ 缓存的 bug,使用多种弱类型时发生; +- 修复 IsIgnore 过滤字段后,查询的错误; +- 修复 全局过滤器功能迁移的遗留 bug; + +### v0.4.14 + +- 优化 Add 时未设置主键的错误提醒; + +### v0.4.13 + +- 补充 Repository 增加 Attach 方法; +- 优化 Update/AddOrUpdate 实体的时候,若状态管理不存在,尝试查询一次数据库,以便跟踪对象; + +### v0.4.12 + +- 修复 非自增情况下,Add 后再 Update 该实体时,错误(需要先 Attach 或查询)的 bug; + +### v0.4.10 + +- 补充 开放 DbContext.Orm 对象; +- 修复 OnConfiguring 未配置时注入获取失败的 bug; + +### v0.4.6 + +- 修复 DbSet AddRange/UpdateRange/RemoveRange 参数为空列表时报错,现在不用判断 data.Any() == true 再执行; +- 增加 DbContext 对 DbSet 的快速代理方法(Add/Update/Remove/Attach); +- 增加 DbContext 通用类,命名为:FreeContext,也可以通过 IFreeSql 扩展方法 CreateDbContext 创建; +- 增加 ISelect NoTracking 扩展方法,查询数据时不追踪(从而提升查询性能); + +### v0.4.5 + +- 增加 DbSet Attach 方法附加实体,可用于不查询就更新或删除; + +### v0.4.2 + +- 增加 DbSet UpdateAsync/UpdateRangeAsync 方法,当一个实体被更新两次时,会先执行前面的队列; +- 增加 GetRepository 获取联合主键的适用仓储类; +- 增加 DbSet 在 Add/Update 时对导航属性(OneToMany) 的处理(AddOrUpdate); + +### v0.4.1 +- 独立 FreeSql.DbContext 项目; +- 实现 Repository + DbSet 统一的状态跟踪与工作单元; +- 增加 DbSet AddOrUpdate 方法; +- 增加 Repository InsertOrUpdate 方法; \ No newline at end of file diff --git a/FreeSql.Repository/FreeSql.Repository.csproj b/FreeSql.Repository/FreeSql.Repository.csproj new file mode 100644 index 00000000..a0b504d7 --- /dev/null +++ b/FreeSql.Repository/FreeSql.Repository.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0;net45 + 0.7.1 + YeXiangQin + FreeSql Implementation of General Repository, Support MySql/SqlServer/PostgreSQL/Oracle/Sqlite, and read/write separation、and split table. + https://github.com/2881099/FreeSql/wiki/Repository + FreeSql ORM Repository + true + git + MIT + $(AssemblyName) + $(AssemblyName) + true + true + + + + + + + diff --git a/FreeSql.Repository/readme.md b/FreeSql.Repository/readme.md new file mode 100644 index 00000000..32fee71e --- /dev/null +++ b/FreeSql.Repository/readme.md @@ -0,0 +1,132 @@ +这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 Repository & UnitOfWork 实现面向对象的特性(QQ群:4336577)。 + +> dotnet add package FreeSql.Repository + +## Repository & UnitOfWork + +仓储与工作单元一起使用,工作单元具有事务特点。 + +```csharp +using (var unitOfWork = fsql.CreateUnitOfWork()) { + var songRepository = unitOfWork.GetRepository(); + var tagRepository = unitOfWork.GetRepository(); + + var song = new Song { BigNumber = "1000000000000000000" }; + songRepository.Insert(song); + + songRepository.Update(song); + + song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); + songRepository.Update(song); + + var tag = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + tagRepository.Insert(tag); + + ctx.Commit(); +} +``` + +## Repository + +简单使用仓储,有状态跟踪,它不包含事务的特点。 + +```csharp +var songRepository = fsql.GetRepository(); +var song = new Song { BigNumber = "1000000000000000000" }; +songRepository.Insert(song); +``` + +## IFreeSql 核心定义 + +```csharp +var fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseNoneCommandParameter(true) + + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) + .Build(); + +public class Song { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string BigNumber { get; set; } + + [Column(IsVersion = true)] //乐观锁 + public long versionRow { get; set; } +} +public class Tag { + [Column(IsIdentity = true)] + public int Id { get; set; } + + public int? Parent_id { get; set; } + public virtual Tag Parent { get; set; } + + public string Name { get; set; } + + public virtual ICollection Tags { get; set; } +} + +public class SongContext : DbContext { + public DbSet Songs { get; set; } + public DbSet Tags { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder builder) { + builder.UseFreeSql(fsql); + } +} +``` + +# 过滤器与验证 + +假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储: + +```csharp +var userRepository = fsql.GetGuidRepository(); +var topicRepository = fsql.GetGuidRepository(); +``` + +在开发过程中,总是担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 + +```csharp +var userRepository = fsql.GetGuidRepository(a => a.Id == 1); +var topicRepository = fsql.GetGuidRepository(a => a.UserId == 1); +``` + +* 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; +* 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常; + +# 分表与分库 + +FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。 + +```csharp +var logRepository = fsql.GetGuidRepository(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}"); +``` + +上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 + +合并两个仓储,实现分表下的联表查询: + +```csharp +fsql.GetGuidRepository().Select.FromRepository(logRepository) + .LeftJoin(b => b.UserId == a.Id) + .ToList(); +``` + +注意事项: + +* 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; +* 不可在分表分库的实体类型中使用《延时加载》; diff --git a/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj b/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj deleted file mode 100644 index 70e6eb76..00000000 --- a/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - netcoreapp2.1 - - false - - - - - - - - - - - - - - - - - - - - diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext/FreeSql.Tests.DbContext.csproj b/FreeSql.Tests/FreeSql.Tests.DbContext/FreeSql.Tests.DbContext.csproj new file mode 100644 index 00000000..761bebcf --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.DbContext/FreeSql.Tests.DbContext.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs b/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs new file mode 100644 index 00000000..da499384 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs @@ -0,0 +1,261 @@ +using System; +using Xunit; + +namespace FreeSql.Tests { + public class RepositoryTests { + + [Fact] + public void AddUpdate() { + var repos = g.sqlite.GetGuidRepository(); + + var item = repos.Insert(new AddUpdateInfo()); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + + item = repos.Insert(new AddUpdateInfo { Id = Guid.NewGuid() }); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + + item.Title = "xxx"; + repos.Update(item); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + + Console.WriteLine(repos.UpdateDiy.Where(a => a.Id == item.Id).Set(a => a.Clicks + 1).ToSql()); + repos.UpdateDiy.Where(a => a.Id == item.Id).Set(a => a.Clicks + 1).ExecuteAffrows(); + + item = repos.Find(item.Id); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + } + + [Fact] + public void UpdateAttach() { + var repos = g.sqlite.GetGuidRepository(); + + var item = new AddUpdateInfo { Id = Guid.NewGuid() }; + repos.Attach(item); + + item.Title = "xxx"; + repos.Update(item); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + + Console.WriteLine(repos.UpdateDiy.Where(a => a.Id == item.Id).Set(a => a.Clicks + 1).ToSql()); + repos.UpdateDiy.Where(a => a.Id == item.Id).Set(a => a.Clicks + 1).ExecuteAffrows(); + + item = repos.Find(item.Id); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + } + + [Fact] + public void UpdateWhenNotExists() { + var repos = g.sqlite.GetGuidRepository(); + + var item = new AddUpdateInfo { Id = Guid.NewGuid() }; + item.Title = "xxx"; + Assert.Throws(() => repos.Update(item)); + } + + [Fact] + public void Update() { + g.sqlite.Insert(new AddUpdateInfo()).ExecuteAffrows(); + + var repos = g.sqlite.GetGuidRepository(); + + var item = new AddUpdateInfo { Id = g.sqlite.Select().First().Id }; + + item.Title = "xxx"; + repos.Update(item); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); + } + + public class AddUpdateInfo { + + public Guid Id { get; set; } + public string Title { get; set; } + + public int Clicks { get; set; } = 10; + } + + [Fact] + public void UnitOfWorkRepository() { + foreach (var fsql in new[] { g.sqlite, /*g.mysql, g.pgsql, g.oracle, g.sqlserver*/ }) { + + fsql.CodeFirst.ConfigEntity(f => { + f.Property(b => b.UserId).IsPrimary(true); + f.Property(b => b.Id).IsPrimary(true).IsIdentity(true); + f.Property(b => b.Name).IsNullable(false); + }); + + FlowModel flow = new FlowModel() { + CreateTime = DateTime.Now, + Name = "aaa", + LastModifyTime = DateTime.Now, + UserId = 1, + }; + var flowRepos = fsql.GetRepository(); + flowRepos.Insert(flow); + + // + flow = new FlowModel() { + CreateTime = DateTime.Now, + Name = "aaa", + LastModifyTime = DateTime.Now, + UserId = 1, + }; + using (var uow = fsql.CreateUnitOfWork()) { + flowRepos = uow.GetRepository(); + flowRepos.Insert(flow); + uow.Commit(); + } + } + } + + [Fact] + public void UnitOfWorkRepositoryWithDisableBeforeInsert() + { + foreach (var fsql in new[] { g.sqlite, }) + { + fsql.CodeFirst.ConfigEntity(f => { + f.Property(b => b.UserId).IsPrimary(true); + f.Property(b => b.Id).IsPrimary(true).IsIdentity(true); + f.Property(b => b.Name).IsNullable(false); + }); + + var flowRepos = fsql.GetRepository(); + + var flow = new FlowModel() + { + CreateTime = DateTime.Now, + Name = "aaa", + LastModifyTime = DateTime.Now, + UserId = 1, + }; + + //ݿѴڵݣΪ˽IJ + flowRepos.Delete(a => a.UserId == 1 &&a.Name== "aaa"); + + using (var uow = fsql.CreateUnitOfWork()) + { + //رչԪῪʼ + uow.Close(); + var uowFlowRepos = uow.GetRepository(); + uowFlowRepos.Insert(flow); + //ѹرչԪ᲻ύûӰ죬˴עȷԪǷЧرˣCommitҲӦò + //uow.Commit(); + } + + Assert.True(flowRepos.Select.Any(a => a.UserId == 1 && a.Name == "aaa")); + } + + } + + [Fact] + public void UnitOfWorkRepositoryWithDisableAfterInsert() + { + foreach (var fsql in new[] {g.sqlite,}) + { + fsql.CodeFirst.ConfigEntity(f => + { + f.Property(b => b.UserId).IsPrimary(true); + f.Property(b => b.Id).IsPrimary(true).IsIdentity(true); + f.Property(b => b.Name).IsNullable(false); + }); + + var flowRepos = fsql.GetRepository(); + + //ݿѴڵݣΪ˽IJ + flowRepos.Delete(a => a.UserId == 1 && a.Name == "aaa"); + + var flow = new FlowModel() + { + CreateTime = DateTime.Now, + Name = "aaa", + LastModifyTime = DateTime.Now, + UserId = 1, + }; + + + Assert.Throws(() => + { + using (var uow = fsql.CreateUnitOfWork()) + { + var uowFlowRepos = uow.GetRepository(); + uowFlowRepos.Insert(flow); + // Insert/Update/Delete ùرuowķᷢ쳣 + uow.Close(); + uow.Commit(); + } + + }); + } + } + + [Fact] + public void UnitOfWorkRepositoryWithoutDisable() + { + foreach (var fsql in new[] { g.sqlite, }) + { + fsql.CodeFirst.ConfigEntity(f => + { + f.Property(b => b.UserId).IsPrimary(true); + f.Property(b => b.Id).IsPrimary(true).IsIdentity(true); + f.Property(b => b.Name).IsNullable(false); + }); + + var flowRepos = fsql.GetRepository(); + if (flowRepos.Select.Any(a => a.UserId == 1 && a.Name == "aaa")) + { + flowRepos.Delete(a => a.UserId == 1); + } + + + var flow = new FlowModel() + { + CreateTime = DateTime.Now, + Name = "aaa", + LastModifyTime = DateTime.Now, + UserId = 1, + }; + + + using (var uow = fsql.CreateUnitOfWork()) + { + var uowFlowRepos = uow.GetRepository(); + uowFlowRepos.Insert(flow); + //commitύݿ + //uow.Commit(); + } + Assert.False(flowRepos.Select.Any(a => a.UserId == 1 && a.Name == "aaa")); + } + } + + + public partial class FlowModel { + public int UserId { get; set; } + public int Id { get; set; } + public int? ParentId { get; set; } + public string Name { get; set; } + public DateTime CreateTime { get; set; } + public DateTime LastModifyTime { get; set; } + public string Desc { get; set; } + } + + [Fact] + public void AsType() { + g.sqlite.Insert(new AddUpdateInfo()).ExecuteAffrows(); + + var repos = g.sqlite.GetGuidRepository(); + repos.AsType(typeof(AddUpdateInfo)); + + var item = new AddUpdateInfo(); + repos.Insert(item); + repos.Update(item); + + item.Clicks += 1; + repos.InsertOrUpdate(item); + + var item2 = repos.Find(item.Id) as AddUpdateInfo; + Assert.Equal(item.Clicks, item2.Clicks); + + repos.DataFilter.Apply("xxx", a => (a as AddUpdateInfo).Clicks == 2); + Assert.Null(repos.Find(item.Id)); + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext/UnitTest1.cs b/FreeSql.Tests/FreeSql.Tests.DbContext/UnitTest1.cs new file mode 100644 index 00000000..0bbc0c4a --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.DbContext/UnitTest1.cs @@ -0,0 +1,160 @@ +using FreeSql.DataAnnotations; +using FreeSql; +using System; +using System.Collections.Generic; +using Xunit; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace FreeSql.Tests { + public class UnitTest1 { + + class testenumWhere { + public Guid id { get; set; } + public testenumWhereType type { get; set; } + } + public enum testenumWhereType { Menu, Class, Blaaa } + + [Fact] + public void Include_ManyToMany() { + + g.sqlite.CodeFirst.SyncStructure(); + g.sqlite.CodeFirst.SyncStructure(); + g.sqlite.CodeFirst.SyncStructure(); + + using (var ctx = g.sqlite.CreateDbContext()) { + + var songs = ctx.Set().Select + .IncludeMany(a => a.Tags) + .ToList(); + + var tag1 = new Tag { + Ddd = DateTime.Now.Second, + Name = "test_manytoMany_01_й" + }; + var tag2 = new Tag { + Ddd = DateTime.Now.Second, + Name = "test_manytoMany_02_" + }; + var tag3 = new Tag { + Ddd = DateTime.Now.Second, + Name = "test_manytoMany_03_ձ" + }; + ctx.AddRange(new[] { tag1, tag2, tag3 }); + + var song1 = new Song { + Create_time = DateTime.Now, + Title = "test_manytoMany_01_й.mp3", + Url = "http://ww.baidu.com/" + }; + var song2 = new Song { + Create_time = DateTime.Now, + Title = "test_manytoMany_02_һ.mp3", + Url = "http://ww.163.com/" + }; + var song3 = new Song { + Create_time = DateTime.Now, + Title = "test_manytoMany_03_ǧһ.mp3", + Url = "http://ww.sina.com/" + }; + ctx.AddRange(new[] { song1, song2, song3 }); + + ctx.AddRange( + new[] { + new Song_tag { Song_id = song1.Id, Tag_id = tag1.Id }, + new Song_tag { Song_id = song2.Id, Tag_id = tag1.Id }, + new Song_tag { Song_id = song3.Id, Tag_id = tag1.Id }, + new Song_tag { Song_id = song1.Id, Tag_id = tag2.Id }, + new Song_tag { Song_id = song3.Id, Tag_id = tag2.Id }, + new Song_tag { Song_id = song3.Id, Tag_id = tag3.Id }, + } + ); + ctx.SaveChanges(); + } + } + + [Fact] + public void Add() { + + g.sqlite.SetDbContextOptions(opt => { + //opt.EnableAddOrUpdateNavigateList = false; + }); + + g.mysql.Insert().AppendData(new testenumWhere { type = testenumWhereType.Blaaa }).ExecuteAffrows(); + + var sql = g.mysql.Select().Where(a => a.type == testenumWhereType.Blaaa).ToSql(); + var tolist = g.mysql.Select().Where(a => a.type == testenumWhereType.Blaaa).ToList(); + + //֧ 1Զ + + using (var ctx = new FreeContext(g.sqlite)) { + + var tags = ctx.Set().Select.IncludeMany(a => a.Tags).ToList(); + + var tag = new Tag { + Name = "testaddsublist", + Tags = new[] { + new Tag { Name = "sub1" }, + new Tag { Name = "sub2" }, + new Tag { + Name = "sub3", + Tags = new[] { + new Tag { Name = "sub3_01" } + } + } + } + }; + ctx.Add(tag); + ctx.SaveChanges(); + } + } + + [Fact] + public void Update() { + //ѯ 1Զ࣬ + + using (var ctx = new FreeContext(g.sqlite)) { + + var tag = ctx.Set().Select.First(); + tag.Tags.Add(new Tag { Name = "sub3" }); + ctx.Update(tag); + ctx.SaveChanges(); + } + } + + public class Song { + [Column(IsIdentity = true)] + public int Id { get; set; } + public DateTime? Create_time { get; set; } + public bool? Is_deleted { get; set; } + public string Title { get; set; } + public string Url { get; set; } + + public virtual ICollection Tags { get; set; } + + [Column(IsVersion = true)] + public long versionRow { get; set; } + } + public class Song_tag { + public int Song_id { get; set; } + public virtual Song Song { get; set; } + + public int Tag_id { get; set; } + public virtual Tag Tag { get; set; } + } + + public class Tag { + [Column(IsIdentity = true)] + public int Id { get; set; } + public int? Parent_id { get; set; } + public virtual Tag Parent { get; set; } + + public decimal? Ddd { get; set; } + public string Name { get; set; } + + public virtual ICollection Songs { get; set; } + public virtual ICollection Tags { get; set; } + } + } +} + diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext/g.cs b/FreeSql.Tests/FreeSql.Tests.DbContext/g.cs new file mode 100644 index 00000000..4aec7356 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.DbContext/g.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + + +public class g { + + static Lazy sqlserverLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseMonitorCommand( + cmd => { + Trace.WriteLine(cmd.CommandText); + }, //监听SQL命令对象,在执行前 + (cmd, traceLog) => { + Console.WriteLine(traceLog); + }) //监听SQL命令对象,在执行后 + .UseLazyLoading(true) + .UseNoneCommandParameter(true) + .Build()); + public static IFreeSql sqlserver => sqlserverLazy.Value; + + static Lazy mysqlLazy = new Lazy(() => 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") + .UseAutoSyncStructure(true) + .UseMonitorCommand( + cmd => { + Trace.WriteLine(cmd.CommandText); + }, //监听SQL命令对象,在执行前 + (cmd, traceLog) => { + Console.WriteLine(traceLog); + }) //监听SQL命令对象,在执行后 + .UseLazyLoading(true) + .UseNoneCommandParameter(true) + .Build()); + public static IFreeSql mysql => mysqlLazy.Value; + + static Lazy pgsqlLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=10") + .UseAutoSyncStructure(true) + .UseSyncStructureToLower(true) + .UseLazyLoading(true) + .UseMonitorCommand( + cmd => { + Trace.WriteLine(cmd.CommandText); + }, //监听SQL命令对象,在执行前 + (cmd, traceLog) => { + Console.WriteLine(traceLog); + }) //监听SQL命令对象,在执行后 + .UseNoneCommandParameter(true) + .Build()); + public static IFreeSql pgsql => pgsqlLazy.Value; + + static Lazy oracleLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Oracle, "user id=user1;password=123456;data source=//127.0.0.1:1521/XE;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + .UseSyncStructureToUpper(true) + .UseNoneCommandParameter(true) + + .UseMonitorCommand( + cmd => { + Trace.WriteLine(cmd.CommandText); + }, //监听SQL命令对象,在执行前 + (cmd, traceLog) => { + Console.WriteLine(traceLog); + }) //监听SQL命令对象,在执行后 + .Build()); + public static IFreeSql oracle => oracleLazy.Value; + + static Lazy sqliteLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|/document22.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10") + .UseAutoSyncStructure(true) + .UseLazyLoading(true) + .UseMonitorCommand( + cmd => { + Trace.WriteLine(cmd.CommandText); + }, //监听SQL命令对象,在执行前 + (cmd, traceLog) => { + Console.WriteLine(traceLog); + }) //监听SQL命令对象,在执行后 + .UseNoneCommandParameter(true) + .Build()); + public static IFreeSql sqlite => sqliteLazy.Value; +} diff --git a/FreeSql.Tests/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj b/FreeSql.Tests/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj new file mode 100644 index 00000000..afdd5535 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests.PerformanceTests/FreeSql.Tests.PerformanceTests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + + + + + + + diff --git a/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs b/FreeSql.Tests/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs similarity index 100% rename from FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs diff --git a/FreeSql.Tests.PerformanceTests/g.cs b/FreeSql.Tests/FreeSql.Tests.PerformanceTests/g.cs similarity index 100% rename from FreeSql.Tests.PerformanceTests/g.cs rename to FreeSql.Tests/FreeSql.Tests.PerformanceTests/g.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj similarity index 59% rename from FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj index 7ba5e744..1085dbcd 100644 --- a/FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj +++ b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/FreeSql.Tests.Provider.MySqlConnector.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlDeleteTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlDeleteTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlInsertTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlInsertTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlInsertTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlSelectTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlSelectTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlUpdateTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/Curd/MySqlUpdateTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/BoolTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/EnumTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MapType/ToStringTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlCodeFirstTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorAdo/MySqlAdoTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorAdo/MySqlAdoTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorAdo/MySqlAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorAdo/MySqlAdoTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/ConvertTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/DateTimeTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/MathTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/OtherTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/StringTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlConnectorExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlDbFirstTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlDbFirstTest.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlDbFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/MySqlConnector/MySqlDbFirstTest.cs diff --git a/FreeSql.Tests.Provider.MySqlConnector/g.cs b/FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/g.cs similarity index 100% rename from FreeSql.Tests.Provider.MySqlConnector/g.cs rename to FreeSql.Tests/FreeSql.Tests.Provider.MySqlConnector/g.cs diff --git a/FreeSql.Tests/Class1.cs b/FreeSql.Tests/FreeSql.Tests/Class1.cs similarity index 100% rename from FreeSql.Tests/Class1.cs rename to FreeSql.Tests/FreeSql.Tests/Class1.cs diff --git a/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs similarity index 100% rename from FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs rename to FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs diff --git a/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs similarity index 100% rename from FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs rename to FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs diff --git a/FreeSql.Tests/DataContext/SqlServer/SqlServerCollection.cs b/FreeSql.Tests/FreeSql.Tests/DataContext/SqlServer/SqlServerCollection.cs similarity index 100% rename from FreeSql.Tests/DataContext/SqlServer/SqlServerCollection.cs rename to FreeSql.Tests/FreeSql.Tests/DataContext/SqlServer/SqlServerCollection.cs diff --git a/FreeSql.Tests/DataContext/SqlServer/SqlServerFixture.cs b/FreeSql.Tests/FreeSql.Tests/DataContext/SqlServer/SqlServerFixture.cs similarity index 100% rename from FreeSql.Tests/DataContext/SqlServer/SqlServerFixture.cs rename to FreeSql.Tests/FreeSql.Tests/DataContext/SqlServer/SqlServerFixture.cs diff --git a/FreeSql.Tests/ExpressionTree/GetDataReaderValueBlockExpressionTest.cs b/FreeSql.Tests/FreeSql.Tests/ExpressionTree/GetDataReaderValueBlockExpressionTest.cs similarity index 100% rename from FreeSql.Tests/ExpressionTree/GetDataReaderValueBlockExpressionTest.cs rename to FreeSql.Tests/FreeSql.Tests/ExpressionTree/GetDataReaderValueBlockExpressionTest.cs diff --git a/FreeSql.Tests/Extensions/LambadaExpressionExtensionsTest.cs b/FreeSql.Tests/FreeSql.Tests/Extensions/LambadaExpressionExtensionsTest.cs similarity index 100% rename from FreeSql.Tests/Extensions/LambadaExpressionExtensionsTest.cs rename to FreeSql.Tests/FreeSql.Tests/Extensions/LambadaExpressionExtensionsTest.cs diff --git a/FreeSql.Tests/Extensions/StringExtensionsTest.cs b/FreeSql.Tests/FreeSql.Tests/Extensions/StringExtensionsTest.cs similarity index 100% rename from FreeSql.Tests/Extensions/StringExtensionsTest.cs rename to FreeSql.Tests/FreeSql.Tests/Extensions/StringExtensionsTest.cs diff --git a/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj similarity index 51% rename from FreeSql.Tests/FreeSql.Tests.csproj rename to FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj index 27134387..902899b6 100644 --- a/FreeSql.Tests/FreeSql.Tests.csproj +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj @@ -11,23 +11,12 @@ - - - - - - - - - - - @@ -35,4 +24,15 @@ + + + + + + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests.xml b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml similarity index 100% rename from FreeSql.Tests/FreeSql.Tests.xml rename to FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml diff --git a/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs b/FreeSql.Tests/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs similarity index 100% rename from FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs rename to FreeSql.Tests/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs diff --git a/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs similarity index 100% rename from FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlDeleteTest.cs diff --git a/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs similarity index 100% rename from FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlInsertTest.cs diff --git a/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs similarity index 100% rename from FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs diff --git a/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs similarity index 100% rename from FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/Curd/MySqlUpdateTest.cs diff --git a/FreeSql.Tests/MySql/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests/MySql/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MapType/BoolTest.cs diff --git a/FreeSql.Tests/MySql/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MapType/EnumTest.cs diff --git a/FreeSql.Tests/MySql/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MapType/ToStringTest.cs diff --git a/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlAdo/MySqlAdoTest.cs diff --git a/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs diff --git a/FreeSql.Tests/MySql/MySqlDbFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlDbFirstTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlDbFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlDbFirstTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/ConvertTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/DateTimeTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/MathTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/OtherTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/StringTest.cs diff --git a/FreeSql.Tests/MySql/MySqlExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests/MySql/MySqlExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests/MySql/MySqlExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests/Oracle/Curd/OracleDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleDeleteTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/Curd/OracleDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleDeleteTest.cs diff --git a/FreeSql.Tests/Oracle/Curd/OracleInsertTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleInsertTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/Curd/OracleInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleInsertTest.cs diff --git a/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs diff --git a/FreeSql.Tests/Oracle/Curd/OracleUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleUpdateTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/Curd/OracleUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleUpdateTest.cs diff --git a/FreeSql.Tests/Oracle/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests/Oracle/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/MapType/BoolTest.cs diff --git a/FreeSql.Tests/Oracle/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/MapType/EnumTest.cs diff --git a/FreeSql.Tests/Oracle/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/MapType/ToStringTest.cs diff --git a/FreeSql.Tests/Oracle/OracleAdo/OracleAdoTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleAdo/OracleAdoTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleAdo/OracleAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleAdo/OracleAdoTest.cs diff --git a/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs diff --git a/FreeSql.Tests/Oracle/OracleDbFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleDbFirstTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleDbFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleDbFirstTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/ConvertTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/DateTimeTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/MathTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/OtherTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/StringTest.cs diff --git a/FreeSql.Tests/Oracle/OracleExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests/Oracle/OracleExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests/Oracle/OracleExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLDeleteTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/Curd/PostgreSQLDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLDeleteTest.cs diff --git a/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertTest.cs diff --git a/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs diff --git a/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLUpdateTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/Curd/PostgreSQLUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLUpdateTest.cs diff --git a/FreeSql.Tests/PostgreSQL/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests/PostgreSQL/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/BoolTest.cs diff --git a/FreeSql.Tests/PostgreSQL/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/EnumTest.cs diff --git a/FreeSql.Tests/PostgreSQL/MapType/JTokenTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/JTokenTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/MapType/JTokenTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/JTokenTest.cs diff --git a/FreeSql.Tests/PostgreSQL/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/MapType/ToStringTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLDbFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLDbFirstTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLDbFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLDbFirstTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/ConvertTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/DateTimeTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/MathTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/OtherTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/StringTest.cs diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests/PostgreSQL/PostgreSQLExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests/PostgreSQL/PostgreSQLExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests/SqlServer/Curd/SqlServerDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerDeleteTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/Curd/SqlServerDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerDeleteTest.cs diff --git a/FreeSql.Tests/SqlServer/Curd/SqlServerInsertTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerInsertTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/Curd/SqlServerInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerInsertTest.cs diff --git a/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs diff --git a/FreeSql.Tests/SqlServer/Curd/SqlServerUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerUpdateTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/Curd/SqlServerUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerUpdateTest.cs diff --git a/FreeSql.Tests/SqlServer/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests/SqlServer/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/BoolTest.cs diff --git a/FreeSql.Tests/SqlServer/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/EnumTest.cs diff --git a/FreeSql.Tests/SqlServer/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/MapType/ToStringTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerAdo/SqlServerAdoTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerAdo/SqlServerAdoTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerAdo/SqlServerAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerAdo/SqlServerAdoTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/ConvertTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/DateTimeTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/MathTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/OtherTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/StringTest.cs diff --git a/FreeSql.Tests/SqlServer/SqlServerExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests/SqlServer/SqlServerExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs diff --git a/FreeSql.Tests/Sqlite/Curd/SqliteInsertTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteInsertTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/Curd/SqliteInsertTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteInsertTest.cs diff --git a/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs diff --git a/FreeSql.Tests/Sqlite/Curd/SqliteUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteUpdateTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/Curd/SqliteUpdateTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteUpdateTest.cs diff --git a/FreeSql.Tests/Sqlite/MapType/BoolNullableTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/BoolNullableTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/MapType/BoolNullableTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/BoolNullableTest.cs diff --git a/FreeSql.Tests/Sqlite/MapType/BoolTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/BoolTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/MapType/BoolTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/BoolTest.cs diff --git a/FreeSql.Tests/Sqlite/MapType/EnumTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/EnumTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/MapType/EnumTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/EnumTest.cs diff --git a/FreeSql.Tests/Sqlite/MapType/ToStringTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/ToStringTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/MapType/ToStringTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/MapType/ToStringTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteAdo/SqliteAdoTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteAdo/SqliteAdoTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteAdo/SqliteAdoTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteAdo/SqliteAdoTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/ConvertTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/ConvertTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/ConvertTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/ConvertTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/DateTimeTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/DateTimeTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/DateTimeTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/DateTimeTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/MathTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/MathTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/MathTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/MathTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/OtherTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/OtherTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/OtherTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/OtherTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/StringTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/StringTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/StringTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/StringTest.cs diff --git a/FreeSql.Tests/Sqlite/SqliteExpression/TimeSpanTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/TimeSpanTest.cs similarity index 100% rename from FreeSql.Tests/Sqlite/SqliteExpression/TimeSpanTest.cs rename to FreeSql.Tests/FreeSql.Tests/Sqlite/SqliteExpression/TimeSpanTest.cs diff --git a/FreeSql.Tests/UnitTest1.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs similarity index 98% rename from FreeSql.Tests/UnitTest1.cs rename to FreeSql.Tests/FreeSql.Tests/UnitTest1.cs index 15e99247..9695c6a0 100644 --- a/FreeSql.Tests/UnitTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs @@ -275,23 +275,25 @@ namespace FreeSql.Tests { var testgrpsql = g.sqlite.Select() .From((a, b) => a.InnerJoin(aa => aa.TemplatesId == b.Id2)) - .GroupBy((a, b) => b.Title) + .GroupBy((a, b) => b.Code) .ToSql(a => new { a.Key, - sss = a.Sum(a.Value.Item1.Id) + sss = a.Sum(a.Value.Item1.Id), + sss2 = a.Sum(a.Value.Item2.Id2) }); var testgrpsql2 = g.sqlite.Select() .From((a, b) => a.InnerJoin(aa => aa.TemplatesId == b.Id2)) - .GroupBy((a, b) => b.Title) + .GroupBy((a, b) => b.Code) .ToList(a => new { a.Key, - sss = a.Sum(a.Value.Item1.Id) + sss = a.Sum(a.Value.Item1.Id), + sss2 = a.Sum(a.Value.Item2.Id2) }); - var tbid = g.sqlite.Select().First().Id; + var tbid = g.sqlite.Select().First()?.Id ?? Guid.Empty; var testarray = new[] { 1, 2, 3 }; var tbidsql = g.sqlite.Update().Where(a => a.Id == tbid) diff --git a/FreeSql.Tests/UnitTest2.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs similarity index 100% rename from FreeSql.Tests/UnitTest2.cs rename to FreeSql.Tests/FreeSql.Tests/UnitTest2.cs diff --git a/FreeSql.Tests/ftTests.xml b/FreeSql.Tests/FreeSql.Tests/ftTests.xml similarity index 100% rename from FreeSql.Tests/ftTests.xml rename to FreeSql.Tests/FreeSql.Tests/ftTests.xml diff --git a/FreeSql.Tests/g.cs b/FreeSql.Tests/FreeSql.Tests/g.cs similarity index 100% rename from FreeSql.Tests/g.cs rename to FreeSql.Tests/FreeSql.Tests/g.cs diff --git a/FreeSql.sln b/FreeSql.sln index 672214b1..00fecbec 100644 --- a/FreeSql.sln +++ b/FreeSql.sln @@ -5,15 +5,11 @@ 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 readme.md = readme.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.PerformanceTests", "FreeSql.Tests.PerformanceTests\FreeSql.Tests.PerformanceTests.csproj", "{446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{94C8A78D-AA15-47B2-A348-530CD86BFC1B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "restful", "Examples\restful\restful.csproj", "{83D10565-AF9D-4EDC-8FB8-8C962A843F97}" @@ -42,7 +38,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Extensions.LazyLoad EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Provider.MySqlConnector", "Providers\FreeSql.Provider.MySqlConnector\FreeSql.Provider.MySqlConnector.csproj", "{D2A41321-5E84-410B-B25C-3AA122D4CA27}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.Provider.MySqlConnector", "FreeSql.Tests.Provider.MySqlConnector\FreeSql.Tests.Provider.MySqlConnector.csproj", "{D113CDFB-FB97-482B-8A00-058E715B624A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbcontext_01", "Examples\dbcontext_01\dbcontext_01.csproj", "{690F89E0-A721-423F-8F5D-D262F73235EA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "repository_01", "Examples\repository_01\repository_01.csproj", "{4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests", "FreeSql.Tests\FreeSql.Tests\FreeSql.Tests.csproj", "{C98891E0-1C83-41F8-B36F-982B8DC45999}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.DbContext", "FreeSql.DbContext\FreeSql.DbContext.csproj", "{82885C27-23C8-4A6E-92CF-80FE61A041E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Repository", "FreeSql.Repository\FreeSql.Repository.csproj", "{3B6887DA-A7E3-410A-B450-587F6B4923F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.DbContext", "FreeSql.Tests\FreeSql.Tests.DbContext\FreeSql.Tests.DbContext.csproj", "{11C37354-7AF1-4010-9BB6-43AA9CBA6291}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.PerformanceTests", "FreeSql.Tests\FreeSql.Tests.PerformanceTests\FreeSql.Tests.PerformanceTests.csproj", "{DFFD1206-A70A-493A-BE88-3131B800522E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Tests.Provider.MySqlConnector", "FreeSql.Tests\FreeSql.Tests.Provider.MySqlConnector\FreeSql.Tests.Provider.MySqlConnector.csproj", "{0F45294A-34FF-4FB8-A046-20E09E3A4D5C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -66,30 +76,6 @@ Global {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 - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|x64.ActiveCfg = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|x64.Build.0 = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|x86.ActiveCfg = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Debug|x86.Build.0 = Debug|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|Any CPU.Build.0 = Release|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|x64.ActiveCfg = Release|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|x64.Build.0 = Release|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|x86.ActiveCfg = Release|Any CPU - {446D9CBE-BFE4-4FB3-ADFD-4C1C5EA1B6EE}.Release|x86.Build.0 = Release|Any CPU {83D10565-AF9D-4EDC-8FB8-8C962A843F97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83D10565-AF9D-4EDC-8FB8-8C962A843F97}.Debug|Any CPU.Build.0 = Debug|Any CPU {83D10565-AF9D-4EDC-8FB8-8C962A843F97}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -222,18 +208,102 @@ Global {D2A41321-5E84-410B-B25C-3AA122D4CA27}.Release|x64.Build.0 = Release|Any CPU {D2A41321-5E84-410B-B25C-3AA122D4CA27}.Release|x86.ActiveCfg = Release|Any CPU {D2A41321-5E84-410B-B25C-3AA122D4CA27}.Release|x86.Build.0 = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|x64.ActiveCfg = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|x64.Build.0 = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|x86.ActiveCfg = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Debug|x86.Build.0 = Debug|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|Any CPU.Build.0 = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|x64.ActiveCfg = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|x64.Build.0 = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|x86.ActiveCfg = Release|Any CPU - {D113CDFB-FB97-482B-8A00-058E715B624A}.Release|x86.Build.0 = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|x64.Build.0 = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Debug|x86.Build.0 = Debug|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|Any CPU.Build.0 = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|x64.ActiveCfg = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|x64.Build.0 = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|x86.ActiveCfg = Release|Any CPU + {690F89E0-A721-423F-8F5D-D262F73235EA}.Release|x86.Build.0 = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|x64.Build.0 = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Debug|x86.Build.0 = Debug|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|Any CPU.Build.0 = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|x64.ActiveCfg = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|x64.Build.0 = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|x86.ActiveCfg = Release|Any CPU + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513}.Release|x86.Build.0 = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|x64.ActiveCfg = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|x64.Build.0 = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|x86.ActiveCfg = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Debug|x86.Build.0 = Debug|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|Any CPU.Build.0 = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|x64.ActiveCfg = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|x64.Build.0 = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|x86.ActiveCfg = Release|Any CPU + {C98891E0-1C83-41F8-B36F-982B8DC45999}.Release|x86.Build.0 = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|x64.ActiveCfg = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|x64.Build.0 = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Debug|x86.Build.0 = Debug|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|Any CPU.Build.0 = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|x64.ActiveCfg = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|x64.Build.0 = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|x86.ActiveCfg = Release|Any CPU + {82885C27-23C8-4A6E-92CF-80FE61A041E1}.Release|x86.Build.0 = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|x64.ActiveCfg = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|x64.Build.0 = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|x86.ActiveCfg = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Debug|x86.Build.0 = Debug|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|Any CPU.Build.0 = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|x64.ActiveCfg = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|x64.Build.0 = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|x86.ActiveCfg = Release|Any CPU + {3B6887DA-A7E3-410A-B450-587F6B4923F7}.Release|x86.Build.0 = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|x64.ActiveCfg = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|x64.Build.0 = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|x86.ActiveCfg = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Debug|x86.Build.0 = Debug|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|Any CPU.Build.0 = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|x64.ActiveCfg = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|x64.Build.0 = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|x86.ActiveCfg = Release|Any CPU + {11C37354-7AF1-4010-9BB6-43AA9CBA6291}.Release|x86.Build.0 = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|x64.Build.0 = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|x86.ActiveCfg = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Debug|x86.Build.0 = Debug|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|Any CPU.Build.0 = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|x64.ActiveCfg = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|x64.Build.0 = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|x86.ActiveCfg = Release|Any CPU + {DFFD1206-A70A-493A-BE88-3131B800522E}.Release|x86.Build.0 = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|x64.Build.0 = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Debug|x86.Build.0 = Debug|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|Any CPU.Build.0 = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|x64.ActiveCfg = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|x64.Build.0 = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|x86.ActiveCfg = Release|Any CPU + {0F45294A-34FF-4FB8-A046-20E09E3A4D5C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -250,6 +320,8 @@ Global {22C0B061-F7CC-4A7F-AEC7-D4DBBE6B23B2} = {2A381C57-2697-427B-9F10-55DA11FD02E4} {1FE00D5E-EC0F-4238-93EC-DABA26DBD1A9} = {4A92E8A6-9A6D-41A1-9CDA-DE10899648AA} {D2A41321-5E84-410B-B25C-3AA122D4CA27} = {2A381C57-2697-427B-9F10-55DA11FD02E4} + {690F89E0-A721-423F-8F5D-D262F73235EA} = {94C8A78D-AA15-47B2-A348-530CD86BFC1B} + {4C0973CB-BD49-4A5B-A6FE-EE0594BDD513} = {94C8A78D-AA15-47B2-A348-530CD86BFC1B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {089687FD-5D25-40AB-BA8A-A10D1E137F98} diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj index 586d5cd8..657f4034 100644 --- a/FreeSql/FreeSql.csproj +++ b/FreeSql/FreeSql.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql is the most convenient ORM in dotnet. It supports Mysql, Postgresql, SqlServer, Oracle and Sqlite. diff --git a/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj b/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj index 2c2c7b11..dc32780a 100644 --- a/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj +++ b/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj @@ -2,7 +2,7 @@ netstandard2.0;net452 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 MySql 5.6 diff --git a/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj b/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj index f160ff74..86e4ba0f 100644 --- a/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj +++ b/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 MySql 5.6 diff --git a/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj b/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj index 4a938a3e..a2fbdd88 100644 --- a/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj +++ b/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 Oracle 11 diff --git a/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj b/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj index 6c9d2cd2..efd7c307 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj +++ b/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 PostgreSQL 9.5 diff --git a/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj b/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj index 5917a46e..0cc24814 100644 --- a/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj +++ b/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj @@ -2,7 +2,7 @@ netstandard2.0;net451 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 SqlServer 2005+,并根据版本适配分页方法:row_number 或 offset fetch next diff --git a/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj b/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj index 17a52aac..a3423442 100644 --- a/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj +++ b/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj @@ -2,7 +2,7 @@ netstandard2.0;net45 - 0.6.13 + 0.7.1 true YeXiangQin FreeSql 数据库实现,基于 Sqlite 3.0 diff --git a/readme.md b/readme.md index 2f41d03e..6ab84261 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ FreeSql 是一个功能强大的对象关系映射程序(O/RM),支持 .NETCore |--------------| ------- | ---- | | FreeSql | [![nuget](https://img.shields.io/nuget/v/FreeSql.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql) | [![stats](https://img.shields.io/nuget/dt/FreeSql.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql?groupby=Version) | | [FreeSql.Repository](https://github.com/2881099/FreeSql/wiki/Repository) | [![nuget](https://img.shields.io/nuget/v/FreeSql.Repository.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.Repository) | [![stats](https://img.shields.io/nuget/dt/FreeSql.Repository.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.Repository?groupby=Version) | -| [FreeSql.DbContext](https://github.com/2881099/FreeSql.DbContext) | [![nuget](https://img.shields.io/nuget/v/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.DbContext) | [![stats](https://img.shields.io/nuget/dt/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.DbContext?groupby=Version) | +| FreeSql.DbContext | [![nuget](https://img.shields.io/nuget/v/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.DbContext) | [![stats](https://img.shields.io/nuget/dt/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.DbContext?groupby=Version) | | [FreeSql.AdminLTE](https://github.com/2881099/FreeSql.AdminLTE) | [![nuget](https://img.shields.io/nuget/v/FreeSql.AdminLTE.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.AdminLTE) | [![stats](https://img.shields.io/nuget/dt/FreeSql.AdminLTE.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.AdminLTE?groupby=Version) | | [FreeSql.Connection.Extensions](https://github.com/2881099/FreeSql.Connection.Extensions) | [![nuget](https://img.shields.io/nuget/v/FreeSql.Connection.Extensions.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.Connection.Extensions) | [![stats](https://img.shields.io/nuget/dt/FreeSql.Connection.Extensions.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.Connection.Extensions?groupby=Version) |