├── EntityFrameworkCore.BootKit ├── Models │ ├── DbExecutionOptions.cs │ ├── DatabaseType.cs │ ├── DbRecord.cs │ ├── DbPatchModel.cs │ └── DatabaseBind.cs ├── DatabaseExtensions │ ├── HasNoKeyAttribute.cs │ ├── FactoryDatabaseExtension.cs │ └── TransactionDatabaseExtension.cs ├── Interfaces │ ├── IDbRecord.cs │ └── IRequireDbPermission.cs ├── DbConnectionSetting.cs ├── DatabaseSettings.cs ├── DbContexts │ ├── DbContext4Memory.cs │ ├── MongoDbConnection.cs │ ├── DataContext.cs │ └── DbContext4SqlServer.cs ├── DefaultDataContextLoader.cs ├── EntityFrameworkCore.BootKit.csproj ├── Utility.cs └── Database.cs ├── Directory.Build.props ├── EntityFrameworkCore.BootKit.UnitTest ├── Tables │ ├── PizzaType.cs │ ├── MongoDbCollection.cs │ └── PIzzaOrder.cs ├── EntityFrameworkCore.BootKit.UnitTest.csproj └── DatabaseTest.cs ├── docs ├── index.rst ├── Makefile ├── make.bat └── conf.py ├── EntityFrameworkCore.BootKit.Postgre ├── DbContext4Redshift.cs ├── DbContext4PostgreSql.cs ├── EntityFrameworkCore.BootKit.Postgre.csproj └── PostgreContextOptionsExtensions.cs ├── EntityFrameworkCore.BootKit.Mongo ├── EntityFrameworkCore.BootKit.Mongo.csproj ├── MongoDbContextOptionsExtensions.cs ├── DbContext4MongoDb.cs └── MongoDbQueryExtension.cs ├── LICENSE ├── EntityFrameworkCore.BootKit.MySql ├── MySqlContextOptionsExtensions.cs ├── EntityFrameworkCore.BootKit.MySql.csproj ├── DbContext4Aurora.cs └── DbContext4MySql.cs ├── EntityFrameworkCore.BootKit.Sqlite ├── EntityFrameworkCore.BootKit.Sqlite.csproj └── DbContext4Sqlite.cs ├── EntityFrameworkCore.BootKit.sln ├── README.md └── .gitignore /EntityFrameworkCore.BootKit/Models/DbExecutionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EntityFrameworkCore.BootKit.Models; 2 | 3 | public class DbExecutionOptions 4 | { 5 | public int Timeout { get; set; } = 30; 6 | } 7 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DatabaseExtensions/HasNoKeyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EntityFrameworkCore.BootKit 6 | { 7 | public class HasNoKeyAttribute : Attribute 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Interfaces/IDbRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EntityFrameworkCore.BootKit 6 | { 7 | /// 8 | /// EF BootKit will generate a concrete table in corresponding database 9 | /// 10 | public interface IDbRecord 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Models/DatabaseType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EntityFrameworkCore.BootKit 6 | { 7 | public enum DatabaseType 8 | { 9 | InMemory = 1, 10 | Sqlite = 2, 11 | SqlServer = 3, 12 | MySql = 4, 13 | Oracle = 5, 14 | MongoDb = 6, 15 | PostgreSql = 7, 16 | Redshift = 8 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DbConnectionSetting.cs: -------------------------------------------------------------------------------- 1 | namespace EntityFrameworkCore.BootKit; 2 | 3 | public class DbConnectionSetting 4 | { 5 | public DbConnectionSetting() 6 | { 7 | Slavers = []; 8 | } 9 | 10 | public string Master { get; set; } 11 | public string[] Slavers { get; set; } 12 | 13 | public int ConnectionTimeout { get; set; } = 30; 14 | 15 | public int ExecutionTimeout { get; set; } = 30; 16 | } 17 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DatabaseSettings.cs: -------------------------------------------------------------------------------- 1 | namespace EntityFrameworkCore.BootKit; 2 | 3 | public class DatabaseSettings 4 | { 5 | public string Default { get; set; } 6 | public DbConnectionSetting DefaultConnection { get; set; } 7 | public bool EnableSqlLog { get; set; } 8 | public bool EnableSensitiveDataLogging { get; set; } 9 | public bool EnableRetryOnFailure { get; set; } 10 | public bool UseCamelCase { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Models/DbRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text; 6 | 7 | namespace EntityFrameworkCore.BootKit 8 | { 9 | public abstract class DbRecord 10 | { 11 | [Key] 12 | [StringLength(36)] 13 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 14 | public string Id { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14.0 4 | 10.0.0 5 | 5.0.17 6 | 6.0.36 7 | 8.0.13 8 | 10.0.0 9 | true 10 | false 11 | 12 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.UnitTest/Tables/PizzaType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace EntityFrameworkCore.BootKit.UnitTest.Tables 7 | { 8 | public class PizzaType : DbRecord, IDbRecord 9 | { 10 | [Required] 11 | public String OrderId { get; set; } 12 | 13 | [Required] 14 | [MaxLength(64)] 15 | public String Name { get; set; } 16 | 17 | public Decimal Amount { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.UnitTest/Tables/MongoDbCollection.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson.Serialization.Attributes; 2 | using MongoDB.Bson.Serialization.IdGenerators; 3 | 4 | namespace EntityFrameworkCore.BootKit.UnitTest.Tables 5 | { 6 | [BsonIgnoreExtraElements] 7 | public class MongoDbCollection : INoSqlDbRecord 8 | { 9 | [BsonId(IdGenerator = typeof(StringObjectIdGenerator))] 10 | public string Id { get; set; } 11 | 12 | public string Name { get; set; } 13 | } 14 | 15 | public interface INoSqlDbRecord 16 | { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. EntityFrameworkCore.BootKit documentation master file, created by 2 | sphinx-quickstart on Tue Aug 21 16:39:58 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to EntityFrameworkCore.BootKit's documentation! 7 | ======================================================= 8 | 9 | .. include:: ../README.rst 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Contents: 14 | 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Interfaces/IRequireDbPermission.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EntityFrameworkCore.BootKit 6 | { 7 | /// 8 | /// Get permission before update record, it won't commit when any implementation return false. 9 | /// 10 | public interface IRequireDbPermission 11 | { 12 | /// 13 | /// Allow invoke patch method 14 | /// 15 | /// 16 | /// 17 | Boolean AllowPatch(DbPatchModel patch); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = EntityFrameworkCoreBootKit 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.UnitTest/Tables/PIzzaOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text; 6 | 7 | namespace EntityFrameworkCore.BootKit.UnitTest.Tables 8 | { 9 | public class PizzaOrder : DbRecord, IDbRecord 10 | { 11 | [MaxLength(32)] 12 | public String OrderNumber { get; set; } 13 | 14 | [MaxLength(64)] 15 | public String CustomerName { get; set; } 16 | 17 | [Required] 18 | public DateTime CreatedTime { get; set; } 19 | 20 | [ForeignKey("OrderId")] 21 | public List PizzaTypes { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Models/DbPatchModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EntityFrameworkCore.BootKit 6 | { 7 | public class DbPatchModel 8 | { 9 | public DbPatchModel() 10 | { 11 | Values = new Dictionary { }; 12 | IgnoredColumns = new List(); 13 | } 14 | 15 | public string Id { get; set; } 16 | public string Table { get; set; } 17 | public string Where { get; set; } 18 | public object[] Params { get; set; } 19 | 20 | public List IgnoredColumns { get; set; } 21 | 22 | public Dictionary Values { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Postgre/DbContext4Redshift.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EntityFrameworkCore.BootKit.DbContexts; 4 | 5 | public class DbContext4Redshift : DataContext 6 | { 7 | public DbContext4Redshift(DbContextOptions options, IServiceProvider serviceProvider) 8 | : base(options, serviceProvider) { } 9 | 10 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | SetLog(optionsBuilder); 13 | optionsBuilder.UseNpgsql(ConnectionString, 14 | x => 15 | { 16 | x.UseNetTopologySuite(); 17 | if (enableRetryOnFailure) 18 | { 19 | x.EnableRetryOnFailure(); 20 | } 21 | }); 22 | base.OnConfiguring(optionsBuilder); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DbContexts/DbContext4Memory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace EntityFrameworkCore.BootKit 7 | { 8 | /// 9 | /// https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory 10 | /// 11 | public class DbContext4Memory : DataContext 12 | { 13 | public DbContext4Memory(DbContextOptions options, IServiceProvider serviceProvider) 14 | : base(options, serviceProvider) { } 15 | 16 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 17 | { 18 | SetLog(optionsBuilder); 19 | optionsBuilder.UseInMemoryDatabase(ConnectionString); 20 | base.OnConfiguring(optionsBuilder); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=EntityFrameworkCoreBootKit 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Mongo/EntityFrameworkCore.BootKit.Mongo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0;net8.0;net6.0;netstandard2.1 5 | $(GeneratePackageOnBuild) 6 | enable 7 | enable 8 | $(BootKitVersion) 9 | $(BootKitVersion) 10 | $(BootKitVersion) 11 | https://avatars3.githubusercontent.com/u/44989469?s=200&v=4 12 | MIT 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Models/DatabaseBind.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Text; 5 | 6 | namespace EntityFrameworkCore.BootKit 7 | { 8 | public class DatabaseBind 9 | { 10 | public DbConnection MasterConnection { get; set; } 11 | public DataContext DbContextMaster { get; set; } 12 | 13 | public int SlaveId { get; set; } 14 | public bool IsRelational { get; set; } = true; 15 | public List SlaveConnections { get; set; } 16 | public DbConnection SlaveConnection => SlaveConnections[SlaveId]; 17 | public DataContext DbContextSlaver { get; set; } 18 | 19 | public Type TableInterface { get; set; } 20 | 21 | public Type DbContextType { get; set; } 22 | 23 | public List Entities { get; set; } 24 | 25 | public Boolean CreateDbIfNotExist { get; set; } 26 | public IServiceProvider ServiceProvider { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Haiping Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.UnitTest/EntityFrameworkCore.BootKit.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Mongo/MongoDbContextOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using MongoDB.Driver; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkCore.BootKit; 6 | 7 | public static class MongoDbContextOptionsExtensions 8 | { 9 | public static DbContextOptionsBuilder UseMongoDb(this DbContextOptionsBuilder optionsBuilder, string connectionString) 10 | { 11 | return optionsBuilder; 12 | } 13 | 14 | public static IMongoCollection Collection(this Database database, string name = "") where T : class 15 | { 16 | Type entityType = typeof(T); 17 | 18 | DatabaseBind binding = database.DbContextBinds.First(x => x.GetType().Equals(typeof(DatabaseBind))); 19 | var db = database.GetMaster(entityType); 20 | if (string.IsNullOrEmpty(name)) 21 | { 22 | // Default collection name 23 | name = typeof(T).Name; 24 | 25 | // Check if the class has TableAttribute 26 | var attributes = typeof(T).GetCustomAttributesData() 27 | .FirstOrDefault(x => x.AttributeType == typeof(TableAttribute)); 28 | if (attributes != null) 29 | { 30 | var arguments = attributes.ConstructorArguments; 31 | if (arguments.Count > 0) 32 | { 33 | name = attributes.ConstructorArguments[0].Value.ToString(); 34 | } 35 | } 36 | } 37 | return (db as DbContext4MongoDb).Set(name); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DbContexts/MongoDbConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Text; 6 | 7 | namespace EntityFrameworkCore.BootKit 8 | { 9 | public sealed class MongoDbConnection : DbConnection, ICloneable 10 | { 11 | public override string ConnectionString { get; set; } 12 | 13 | public override string Database => throw new NotImplementedException(); 14 | 15 | public override string DataSource => throw new NotImplementedException(); 16 | 17 | public override string ServerVersion => throw new NotImplementedException(); 18 | 19 | public override ConnectionState State => throw new NotImplementedException(); 20 | 21 | public MongoDbConnection() 22 | { 23 | 24 | } 25 | 26 | public MongoDbConnection(string connectionString) 27 | { 28 | ConnectionString = connectionString; 29 | } 30 | 31 | public object Clone() 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | 36 | protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | public override void ChangeDatabase(string databaseName) 42 | { 43 | throw new NotImplementedException(); 44 | } 45 | 46 | public override void Close() 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | protected override DbCommand CreateDbCommand() 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | public override void Open() 57 | { 58 | throw new NotImplementedException(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.MySql/MySqlContextOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using MySqlConnector; 3 | 4 | namespace EntityFrameworkCore.BootKit; 5 | 6 | public static class MySqlContextOptionsExtensions 7 | { 8 | public static Database GetDefaultPostgre(this DefaultDataContextLoader loader, string dbConfigSection) 9 | { 10 | var dc = new Database(); 11 | 12 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration"); 13 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 14 | 15 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 16 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 17 | 18 | if (db.Equals("MySql")) 19 | { 20 | dc.BindDbContext(new DatabaseBind 21 | { 22 | MasterConnection = new MySqlConnection(connectionString), 23 | CreateDbIfNotExist = true 24 | }); 25 | } 26 | 27 | return dc; 28 | } 29 | 30 | public static Database GetDefaultRedshift(this DefaultDataContextLoader loader, string dbConfigSection) 31 | { 32 | var dc = new Database(); 33 | 34 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration"); 35 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 36 | 37 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 38 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 39 | 40 | if (db.Equals("Aurora")) 41 | { 42 | dc.BindDbContext(new DatabaseBind 43 | { 44 | MasterConnection = new MySqlConnection(connectionString), 45 | CreateDbIfNotExist = true 46 | }); 47 | } 48 | 49 | return dc; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.MySql/EntityFrameworkCore.BootKit.MySql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0;net8.0;net6.0;netstandard2.1 5 | $(GeneratePackageOnBuild) 6 | enable 7 | enable 8 | $(BootKitVersion) 9 | $(BootKitVersion) 10 | $(BootKitVersion) 11 | https://avatars3.githubusercontent.com/u/44989469?s=200&v=4 12 | MIT 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Postgre/DbContext4PostgreSql.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EntityFrameworkCore.BootKit.DbContexts; 4 | 5 | public class DbContext4PostgreSql : DataContext 6 | { 7 | public DbContext4PostgreSql(DbContextOptions options, IServiceProvider serviceProvider) 8 | : base(options, serviceProvider) { } 9 | 10 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | SetLog(optionsBuilder); 13 | optionsBuilder.UseNpgsql(ConnectionString, 14 | x => 15 | { 16 | x.UseNetTopologySuite(); 17 | if (enableRetryOnFailure) 18 | { 19 | x.EnableRetryOnFailure(); 20 | } 21 | }); 22 | base.OnConfiguring(optionsBuilder); 23 | } 24 | } 25 | 26 | public class DbContext4PostgreSql2 : DataContext 27 | { 28 | public DbContext4PostgreSql2(DbContextOptions options, IServiceProvider serviceProvider) 29 | : base(options, serviceProvider) { } 30 | 31 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 32 | { 33 | SetLog(optionsBuilder); 34 | optionsBuilder.UseNpgsql(ConnectionString, 35 | x => 36 | { 37 | x.UseNetTopologySuite(); 38 | if (enableRetryOnFailure) 39 | { 40 | x.EnableRetryOnFailure(); 41 | } 42 | }); 43 | base.OnConfiguring(optionsBuilder); 44 | } 45 | } 46 | 47 | public class DbContext4PostgreSql3 : DataContext 48 | { 49 | public DbContext4PostgreSql3(DbContextOptions options, IServiceProvider serviceProvider) 50 | : base(options, serviceProvider) { } 51 | 52 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 53 | { 54 | SetLog(optionsBuilder); 55 | optionsBuilder.UseNpgsql(ConnectionString, 56 | x => 57 | { 58 | x.UseNetTopologySuite(); 59 | if (enableRetryOnFailure) 60 | { 61 | x.EnableRetryOnFailure(); 62 | } 63 | }); 64 | base.OnConfiguring(optionsBuilder); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Postgre/EntityFrameworkCore.BootKit.Postgre.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0;net8.0;net6.0;netstandard2.1 5 | $(GeneratePackageOnBuild) 6 | enable 7 | enable 8 | $(BootKitVersion) 9 | $(BootKitVersion) 10 | $(BootKitVersion) 11 | https://avatars3.githubusercontent.com/u/44989469?s=200&v=4 12 | MIT 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.MySql/DbContext4Aurora.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EntityFrameworkCore.BootKit; 4 | 5 | public class DbContext4Aurora : DataContext 6 | { 7 | public DbContext4Aurora(DbContextOptions options, IServiceProvider serviceProvider) 8 | : base(options, serviceProvider) { } 9 | 10 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | SetLog(optionsBuilder); 13 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 14 | x => 15 | { 16 | x.UseNetTopologySuite(); 17 | if (enableRetryOnFailure) 18 | { 19 | x.EnableRetryOnFailure(); 20 | } 21 | }); 22 | base.OnConfiguring(optionsBuilder); 23 | } 24 | } 25 | 26 | public class DbContext4Aurora2 : DataContext 27 | { 28 | public DbContext4Aurora2(DbContextOptions options, IServiceProvider serviceProvider) 29 | : base(options, serviceProvider) { } 30 | 31 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 32 | { 33 | SetLog(optionsBuilder); 34 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 35 | x => 36 | { 37 | x.UseNetTopologySuite(); 38 | if (enableRetryOnFailure) 39 | { 40 | x.EnableRetryOnFailure(); 41 | } 42 | }); 43 | base.OnConfiguring(optionsBuilder); 44 | } 45 | } 46 | 47 | public class DbContext4Aurora3 : DataContext 48 | { 49 | public DbContext4Aurora3(DbContextOptions options, IServiceProvider serviceProvider) 50 | : base(options, serviceProvider) { } 51 | 52 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 53 | { 54 | SetLog(optionsBuilder); 55 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 56 | x => 57 | { 58 | x.UseNetTopologySuite(); 59 | if (enableRetryOnFailure) 60 | { 61 | x.EnableRetryOnFailure(); 62 | } 63 | }); 64 | base.OnConfiguring(optionsBuilder); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Postgre/PostgreContextOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using EntityFrameworkCore.BootKit.DbContexts; 2 | using Microsoft.Extensions.Configuration; 3 | using Npgsql; 4 | 5 | namespace EntityFrameworkCore.BootKit; 6 | 7 | public static class PostgreContextOptionsExtensions 8 | { 9 | public static Database GetDefaultPostgre(this DefaultDataContextLoader loader, string dbConfigSection) 10 | { 11 | var dc = new Database(); 12 | 13 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration"); 14 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 15 | 16 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 17 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 18 | 19 | if (db.Equals("Redshift")) 20 | { 21 | dc.BindDbContext(new DatabaseBind 22 | { 23 | MasterConnection = new NpgsqlConnection("Server=*.us-east-1.redshift.amazonaws.com; Port=5439;User ID=;Password=;Database=;Server Compatibility Mode=Redshift;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"), 24 | CreateDbIfNotExist = true 25 | }); 26 | } 27 | 28 | return dc; 29 | } 30 | 31 | public static Database GetDefaultRedshift(this DefaultDataContextLoader loader, string dbConfigSection) 32 | { 33 | var dc = new Database(); 34 | 35 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration"); 36 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 37 | 38 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 39 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 40 | 41 | if (db.Equals("Redshift")) 42 | { 43 | dc.BindDbContext(new DatabaseBind 44 | { 45 | MasterConnection = new NpgsqlConnection("Server=*.us-east-1.redshift.amazonaws.com; Port=5439;User ID=;Password=;Database=;Server Compatibility Mode=Redshift;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"), 46 | CreateDbIfNotExist = true 47 | }); 48 | } 49 | 50 | return dc; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Sqlite/EntityFrameworkCore.BootKit.Sqlite.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(LangVersion) 5 | net10.0;net8.0;net6.0;netstandard2.1 6 | $(GeneratePackageOnBuild) 7 | enable 8 | enable 9 | 10 | EntityFrameworkCore Boot Kit (EFBK) is a quick start database connecter for using EntityFrameworkCore. 11 | Support variety of databases such as Sqlite, MySql, SqlServer, PostgreSql, MongoDb, Amazon Redshift, AWS Aurora and Memory database. 12 | 13 | https://github.com/Oceania2018/EntityFrameworkCore.BootKit 14 | https://github.com/Oceania2018/EntityFrameworkCore.BootKit 15 | EntityFramework, EntityFrameworkCore, Database, Redshift, Aurora, Postgre, MySql, MongoDB 16 | Haiping Chen 17 | SciSharp STACK 18 | $(BootKitVersion) 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Sqlite/DbContext4Sqlite.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EntityFrameworkCore.BootKit.Sqlite; 4 | 5 | public class DbContext4Sqlite : DataContext 6 | { 7 | public DbContext4Sqlite(DbContextOptions options, IServiceProvider serviceProvider) 8 | : base(options, serviceProvider) { } 9 | 10 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | SetLog(optionsBuilder); 13 | optionsBuilder.UseSqlite(ConnectionString, 14 | x => x.UseNetTopologySuite()); 15 | base.OnConfiguring(optionsBuilder); 16 | } 17 | } 18 | 19 | public class DbContext4Sqlite2 : DataContext 20 | { 21 | public DbContext4Sqlite2(DbContextOptions options, IServiceProvider serviceProvider) 22 | : base(options, serviceProvider) { } 23 | 24 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 25 | { 26 | SetLog(optionsBuilder); 27 | optionsBuilder.UseSqlite(ConnectionString, 28 | x => x.UseNetTopologySuite()); 29 | base.OnConfiguring(optionsBuilder); 30 | } 31 | } 32 | 33 | public class DbContext4Sqlite3 : DataContext 34 | { 35 | public DbContext4Sqlite3(DbContextOptions options, IServiceProvider serviceProvider) 36 | : base(options, serviceProvider) { } 37 | 38 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 39 | { 40 | SetLog(optionsBuilder); 41 | optionsBuilder.UseSqlite(ConnectionString, 42 | x => x.UseNetTopologySuite()); 43 | base.OnConfiguring(optionsBuilder); 44 | } 45 | } 46 | 47 | public class DbContext4Sqlite4 : DataContext 48 | { 49 | public DbContext4Sqlite4(DbContextOptions options, IServiceProvider serviceProvider) 50 | : base(options, serviceProvider) { } 51 | 52 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 53 | { 54 | SetLog(optionsBuilder); 55 | optionsBuilder.UseSqlite(ConnectionString, 56 | x => x.UseNetTopologySuite()); 57 | base.OnConfiguring(optionsBuilder); 58 | } 59 | } 60 | 61 | public class DbContext4Sqlite5 : DataContext 62 | { 63 | public DbContext4Sqlite5(DbContextOptions options, IServiceProvider serviceProvider) 64 | : base(options, serviceProvider) { } 65 | 66 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 67 | { 68 | SetLog(optionsBuilder); 69 | optionsBuilder.UseSqlite(ConnectionString, 70 | x => x.UseNetTopologySuite()); 71 | base.OnConfiguring(optionsBuilder); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DefaultDataContextLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace EntityFrameworkCore.BootKit; 8 | 9 | public class DefaultDataContextLoader 10 | { 11 | /// 12 | /// Get data contexts implemented IDbRecord 13 | /// 14 | /// 15 | public Database GetDefaultDc() 16 | { 17 | return GetDefaultDc("Database"); 18 | } 19 | 20 | public Database GetDefaultDc(string dbConfigSection) 21 | { 22 | var dc = new Database(); 23 | 24 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration"); 25 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 26 | 27 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 28 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 29 | 30 | if (db.Equals("SqlServer")) 31 | { 32 | dc.BindDbContext(new DatabaseBind 33 | { 34 | MasterConnection = new SqlConnection(connectionString), 35 | CreateDbIfNotExist = true 36 | }); 37 | } 38 | else if (db.Equals("InMemory")) 39 | { 40 | dc.BindDbContext(new DatabaseBind 41 | { 42 | CreateDbIfNotExist = true 43 | }); 44 | } 45 | 46 | return dc; 47 | } 48 | 49 | public Database GetDefaultDc2(string dbConfigSection) 50 | { 51 | var dc = new Database(); 52 | 53 | var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Assemblies"); 54 | var contentRootPath = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(); 55 | string db = config.GetSection($"{dbConfigSection}:Default").Value; 56 | string connectionString = config.GetSection($"{dbConfigSection}:ConnectionStrings")[db]; 57 | 58 | if (db.Equals("SqlServer")) 59 | { 60 | dc.BindDbContext(new DatabaseBind 61 | { 62 | MasterConnection = new SqlConnection(connectionString), 63 | CreateDbIfNotExist = true 64 | }); 65 | } 66 | else if (db.Equals("InMemory")) 67 | { 68 | dc.BindDbContext(new DatabaseBind 69 | { 70 | }); 71 | } 72 | 73 | return dc; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DbContexts/DataContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using System.Linq; 7 | 8 | namespace EntityFrameworkCore.BootKit 9 | { 10 | public class DataContext : DbContext 11 | { 12 | public string ConnectionString = ""; 13 | public List EntityTypes { get; set; } 14 | public IServiceProvider ServiceProvider { get; set; } 15 | protected bool enableRetryOnFailure => dbSettings.EnableRetryOnFailure; 16 | protected bool useCamelCase => dbSettings.UseCamelCase; 17 | 18 | 19 | private static DatabaseSettings _dbSettings; 20 | protected DatabaseSettings dbSettings 21 | { 22 | get 23 | { 24 | if (_dbSettings == null) 25 | { 26 | if (ServiceProvider == null) 27 | { 28 | throw new Exception($"ServiceProvider is not initialized."); 29 | } 30 | _dbSettings = (DatabaseSettings)ServiceProvider.GetService(typeof(DatabaseSettings)); 31 | } 32 | return _dbSettings; 33 | } 34 | } 35 | 36 | public DataContext(DbContextOptions options, IServiceProvider serviceProvider) 37 | : base(options) 38 | { 39 | ServiceProvider = serviceProvider; 40 | } 41 | 42 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 43 | { 44 | base.OnConfiguring(optionsBuilder); 45 | } 46 | 47 | protected override void OnModelCreating(ModelBuilder modelBuilder) 48 | { 49 | EntityTypes.ForEach(type => 50 | { 51 | var type1 = modelBuilder.Model.FindEntityType(type); 52 | if(type1 == null) 53 | { 54 | modelBuilder.Model.AddEntityType(type); 55 | } 56 | 57 | if (type.GetCustomAttributes(typeof(HasNoKeyAttribute)).Any()) 58 | modelBuilder.Entity(type).HasNoKey(); 59 | }); 60 | 61 | base.OnModelCreating(modelBuilder); 62 | } 63 | 64 | protected void SetLog(DbContextOptionsBuilder optionsBuilder) 65 | { 66 | if (ServiceProvider == null) 67 | return; 68 | 69 | if (dbSettings.EnableSqlLog) 70 | optionsBuilder.UseLoggerFactory((ILoggerFactory)ServiceProvider.GetService(typeof(ILoggerFactory))); 71 | if (dbSettings.EnableSensitiveDataLogging) 72 | optionsBuilder.EnableSensitiveDataLogging(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DbContexts/DbContext4SqlServer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | 4 | namespace EntityFrameworkCore.BootKit; 5 | 6 | public class DbContext4SqlServer : DataContext 7 | { 8 | public DbContext4SqlServer(DbContextOptions options, IServiceProvider serviceProvider) 9 | : base(options, serviceProvider) { } 10 | 11 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 12 | { 13 | SetLog(optionsBuilder); 14 | optionsBuilder.UseSqlServer(ConnectionString, 15 | x => 16 | { 17 | x.UseNetTopologySuite(); 18 | if (enableRetryOnFailure) 19 | { 20 | x.EnableRetryOnFailure(); 21 | } 22 | }); 23 | base.OnConfiguring(optionsBuilder); 24 | } 25 | } 26 | 27 | public class DbContext4SqlServer2 : DataContext 28 | { 29 | public DbContext4SqlServer2(DbContextOptions options, IServiceProvider serviceProvider) 30 | : base(options, serviceProvider) { } 31 | 32 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 33 | { 34 | SetLog(optionsBuilder); 35 | optionsBuilder.UseSqlServer(ConnectionString, 36 | x => 37 | { 38 | x.UseNetTopologySuite(); 39 | if (enableRetryOnFailure) 40 | { 41 | x.EnableRetryOnFailure(); 42 | } 43 | }); 44 | base.OnConfiguring(optionsBuilder); 45 | } 46 | } 47 | 48 | public class DbContext4SqlServer3 : DataContext 49 | { 50 | public DbContext4SqlServer3(DbContextOptions options, IServiceProvider serviceProvider) 51 | : base(options, serviceProvider) { } 52 | 53 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 54 | { 55 | SetLog(optionsBuilder); 56 | optionsBuilder.UseSqlServer(ConnectionString, 57 | x => 58 | { 59 | x.UseNetTopologySuite(); 60 | if (enableRetryOnFailure) 61 | { 62 | x.EnableRetryOnFailure(); 63 | } 64 | }); 65 | base.OnConfiguring(optionsBuilder); 66 | } 67 | } 68 | 69 | public class DbContext4SqlServer4 : DataContext 70 | { 71 | public DbContext4SqlServer4(DbContextOptions options, IServiceProvider serviceProvider) 72 | : base(options, serviceProvider) { } 73 | 74 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 75 | { 76 | SetLog(optionsBuilder); 77 | optionsBuilder.UseSqlServer(ConnectionString, 78 | x => 79 | { 80 | x.UseNetTopologySuite(); 81 | if (enableRetryOnFailure) 82 | { 83 | x.EnableRetryOnFailure(); 84 | } 85 | }); 86 | base.OnConfiguring(optionsBuilder); 87 | } 88 | } 89 | 90 | public class DbContext4SqlServer5 : DataContext 91 | { 92 | public DbContext4SqlServer5(DbContextOptions options, IServiceProvider serviceProvider) 93 | : base(options, serviceProvider) { } 94 | 95 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 96 | { 97 | SetLog(optionsBuilder); 98 | optionsBuilder.UseSqlServer(ConnectionString, 99 | x => 100 | { 101 | x.UseNetTopologySuite(); 102 | if (enableRetryOnFailure) 103 | { 104 | x.EnableRetryOnFailure(); 105 | } 106 | }); 107 | base.OnConfiguring(optionsBuilder); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.MySql/DbContext4MySql.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EntityFrameworkCore.BootKit; 4 | 5 | public class DbContext4MySql : DataContext 6 | { 7 | public DbContext4MySql(DbContextOptions options, IServiceProvider serviceProvider) 8 | : base(options, serviceProvider) { } 9 | 10 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | SetLog(optionsBuilder); 13 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 14 | x => 15 | { 16 | x.UseNetTopologySuite(); 17 | if (enableRetryOnFailure) 18 | { 19 | x.EnableRetryOnFailure(); 20 | } 21 | }); 22 | base.OnConfiguring(optionsBuilder); 23 | } 24 | } 25 | 26 | public class DbContext4MySql2 : DataContext 27 | { 28 | public DbContext4MySql2(DbContextOptions options, IServiceProvider serviceProvider) 29 | : base(options, serviceProvider) { } 30 | 31 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 32 | { 33 | SetLog(optionsBuilder); 34 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 35 | x => 36 | { 37 | x.UseNetTopologySuite(); 38 | if (enableRetryOnFailure) 39 | { 40 | x.EnableRetryOnFailure(); 41 | } 42 | }); 43 | base.OnConfiguring(optionsBuilder); 44 | } 45 | } 46 | 47 | public class DbContext4MySql3 : DataContext 48 | { 49 | public DbContext4MySql3(DbContextOptions options, IServiceProvider serviceProvider) 50 | : base(options, serviceProvider) { } 51 | 52 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 53 | { 54 | SetLog(optionsBuilder); 55 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 56 | x => 57 | { 58 | x.UseNetTopologySuite(); 59 | if (enableRetryOnFailure) 60 | { 61 | x.EnableRetryOnFailure(); 62 | } 63 | }); 64 | base.OnConfiguring(optionsBuilder); 65 | } 66 | } 67 | 68 | public class DbContext4MySql4 : DataContext 69 | { 70 | public DbContext4MySql4(DbContextOptions options, IServiceProvider serviceProvider) 71 | : base(options, serviceProvider) { } 72 | 73 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 74 | { 75 | SetLog(optionsBuilder); 76 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 77 | x => 78 | { 79 | x.UseNetTopologySuite(); 80 | if (enableRetryOnFailure) 81 | { 82 | x.EnableRetryOnFailure(); 83 | } 84 | }); 85 | base.OnConfiguring(optionsBuilder); 86 | } 87 | } 88 | 89 | public class DbContext4MySql5 : DataContext 90 | { 91 | public DbContext4MySql5(DbContextOptions options, IServiceProvider serviceProvider) 92 | : base(options, serviceProvider) { } 93 | 94 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 95 | { 96 | SetLog(optionsBuilder); 97 | optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString), 98 | x => 99 | { 100 | x.UseNetTopologySuite(); 101 | if (enableRetryOnFailure) 102 | { 103 | x.EnableRetryOnFailure(); 104 | } 105 | }); 106 | base.OnConfiguring(optionsBuilder); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.BootKit", "EntityFrameworkCore.BootKit\EntityFrameworkCore.BootKit.csproj", "{817B600E-BD74-4A7A-AC99-37E0BBECD899}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.BootKit.UnitTest", "EntityFrameworkCore.BootKit.UnitTest\EntityFrameworkCore.BootKit.UnitTest.csproj", "{6031B9F8-88AD-4048-ACDC-7B4116155995}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.BootKit.Mongo", "EntityFrameworkCore.BootKit.Mongo\EntityFrameworkCore.BootKit.Mongo.csproj", "{7DB89166-A667-4281-9957-8D0DB9E12783}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.BootKit.Postgre", "EntityFrameworkCore.BootKit.Postgre\EntityFrameworkCore.BootKit.Postgre.csproj", "{4104673F-29DE-4084-9C9F-2355EE025686}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.BootKit.MySql", "EntityFrameworkCore.BootKit.MySql\EntityFrameworkCore.BootKit.MySql.csproj", "{589CE124-0B1D-4E62-B075-364F7561E90F}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.BootKit.Sqlite", "EntityFrameworkCore.BootKit.Sqlite\EntityFrameworkCore.BootKit.Sqlite.csproj", "{90408991-7584-40B2-96DB-02E469B20B08}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {817B600E-BD74-4A7A-AC99-37E0BBECD899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {817B600E-BD74-4A7A-AC99-37E0BBECD899}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {817B600E-BD74-4A7A-AC99-37E0BBECD899}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {817B600E-BD74-4A7A-AC99-37E0BBECD899}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {6031B9F8-88AD-4048-ACDC-7B4116155995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {6031B9F8-88AD-4048-ACDC-7B4116155995}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {6031B9F8-88AD-4048-ACDC-7B4116155995}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {6031B9F8-88AD-4048-ACDC-7B4116155995}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {7DB89166-A667-4281-9957-8D0DB9E12783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {7DB89166-A667-4281-9957-8D0DB9E12783}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {7DB89166-A667-4281-9957-8D0DB9E12783}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {7DB89166-A667-4281-9957-8D0DB9E12783}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {4104673F-29DE-4084-9C9F-2355EE025686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {4104673F-29DE-4084-9C9F-2355EE025686}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {4104673F-29DE-4084-9C9F-2355EE025686}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {4104673F-29DE-4084-9C9F-2355EE025686}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {589CE124-0B1D-4E62-B075-364F7561E90F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {589CE124-0B1D-4E62-B075-364F7561E90F}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {589CE124-0B1D-4E62-B075-364F7561E90F}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {589CE124-0B1D-4E62-B075-364F7561E90F}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {90408991-7584-40B2-96DB-02E469B20B08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {90408991-7584-40B2-96DB-02E469B20B08}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {90408991-7584-40B2-96DB-02E469B20B08}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {90408991-7584-40B2-96DB-02E469B20B08}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {3CC917AA-6464-4371-836E-71B3CA8029B7} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DatabaseExtensions/FactoryDatabaseExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Linq.Dynamic.Core; 7 | 8 | namespace EntityFrameworkCore.BootKit 9 | { 10 | public static class FactoryDatabaseExtension 11 | { 12 | public static int Patch(this Database db, DbPatchModel patch) 13 | { 14 | var binding = db.GetBinding(typeof(TTableInterface)); 15 | 16 | var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies"); 17 | var permissions = Utility.GetInstanceWithInterface(assemblies); 18 | 19 | var result = permissions.Any(x => !x.AllowPatch(patch)); 20 | 21 | if (result) return 0; 22 | 23 | if (!String.IsNullOrEmpty(patch.Id)) 24 | { 25 | var record = db.Table(patch.Table).FirstOrDefault(x => x.Id == patch.Id); 26 | SetValues(patch, record); 27 | 28 | return 1; 29 | } 30 | else 31 | { 32 | var records = db.Table(patch.Table).Where(patch.Where, patch.Params).ToList(); 33 | records.ForEach(record => SetValues(patch, record)); 34 | 35 | return records.Count; 36 | } 37 | } 38 | 39 | private static void SetValues(DbPatchModel patch, DbRecord record) 40 | { 41 | patch.Values.Where(x => !patch.IgnoredColumns.Contains(x.Key)) 42 | .ToList() 43 | .ForEach(x => 44 | { 45 | try 46 | { 47 | record.SetValue(x.Key, x.Value); 48 | } 49 | catch (Exception ex) 50 | { 51 | Console.WriteLine($"Set value exception: {x.Key} {x.Value}"); 52 | throw ex; 53 | } 54 | }); 55 | } 56 | 57 | public static IQueryable Table(this Database db, string tableName) 58 | { 59 | return Table(db, tableName); 60 | } 61 | 62 | public static IQueryable Table(this Database db, string tableName) 63 | { 64 | var binding = db.GetBinding(tableName); 65 | 66 | var tableType = binding.Entities.First(x => x.Name.ToLower().Equals(tableName.ToLower())); 67 | 68 | if (tableType == null) return null; 69 | 70 | DbContext dc = null; 71 | 72 | if (binding.DbContextMaster != null && binding.DbContextMaster.Database.CurrentTransaction != null) 73 | { 74 | dc = db.GetMaster(tableType); 75 | } 76 | else 77 | { 78 | dc = db.GetReader(tableType); 79 | } 80 | 81 | var dbSet = (IQueryable)dc.GetType() 82 | .GetMethod("Set").MakeGenericMethod(tableType) 83 | .Invoke(dc, null); 84 | 85 | return dbSet; 86 | } 87 | 88 | public static object Add(this Database db, string table, Object entity) 89 | { 90 | var dbSet = db.Table(table); 91 | 92 | var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies"); 93 | var tableType = Utility.GetType(table, assemblies); 94 | 95 | return dbSet.InvokeFunction("Add", new Object[] { entity }); 96 | } 97 | 98 | public static object Remove(this Database db, string table, Object entity) 99 | { 100 | var dbSet = db.Table(table); 101 | 102 | var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies"); 103 | var tableType = Utility.GetType(table, assemblies); 104 | 105 | return dbSet.InvokeFunction("Remove", new Object[] { entity }); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EntityFrameworkCore.BootKit 2 | n'] 3 | [![Join the chat at https://gitter.im/publiclab/publiclab](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sci-sharp/community) [![Documentation Status](https://readthedocs.org/projects/entityframeworkcorebootkit/badge/?version=latest)](https://tensorflownet.readthedocs.io/en/latest/?badge=latest) [![NuGet](https://img.shields.io/nuget/dt/EntityFrameworkCore.BootKit.svg)](https://www.nuget.org/packages/EntityFrameworkCore.BootKit) 4 | 5 | EntityFrameworkCore Boot Kit (EFBK) is a quick start database connect library for using .NET EntityFrameworkCore. 6 | 7 | ### Features: 8 | 9 | * Inherits from EntityFrameworkCore Triggers to enable entries update notfication. 10 | * Support mulitple databases like MySql, SQL Server, Sqlite, PostgreSql, MongoDB, Amazon Redshift, AWS Aurora and InMemory. 11 | * Support dynamic linq to query and update database. 12 | * Support read/write seperated mode. Randomly choose multiple slaves. 13 | * Multiple database with distributed transaction supported, and MySQL multiple databases/tables sharding supported. 14 | * Tracking entry change history. 15 | * Built-in DbFactory with access control list (ACL) hook. 16 | 17 | ### Get started 18 | 19 | How to install 20 | 21 | ```sh 22 | PM> Install-Package EntityFrameworkCore.BootKit 23 | ``` 24 | 25 | How to use 26 | 27 | 1. Define entity 28 | 29 | 30 | ```cs 31 | public class PizzaOrder : DbRecord, IDbRecord 32 | { 33 | [MaxLength(32)] 34 | public String OrderNumber { get; set; } 35 | 36 | [MaxLength(64)] 37 | public String CustomerName { get; set; } 38 | 39 | [Required] 40 | public DateTime CreatedTime { get; set; } 41 | 42 | [ForeignKey("OrderId")] 43 | public List PizzaTypes { get; set; } 44 | } 45 | ``` 46 | 47 | 2. Init data context 48 | 49 | ```cs 50 | var db = new Database(); 51 | AppDomain.CurrentDomain.SetData("Assemblies", new string[] { "EntityFrameworkCore.BootKit.UnitTest" }); 52 | 53 | // bind as much data context as you can 54 | db.BindDbContext(new DatabaseBind 55 | { 56 | MasterConnection = new SqliteConnection($"Data Source={Directory.GetCurrentDirectory()}\\..\\..\\..\\..\\bootkit.db"), 57 | CreateDbIfNotExist = true 58 | }); 59 | 60 | db.BindDbContext(new DatabaseBind 61 | { 62 | MasterConnection = new NpgsqlConnection("Server=; Port=5439;User ID=;Password=;Database=;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"), 63 | }); 64 | ``` 65 | 66 | 3. Retrieve record 67 | 68 | ```cs 69 | var order = db.Table().Include(x => x.PizzaTypes).FirstOrDefault(); 70 | ``` 71 | 72 | 4. Retrieve record by table name 73 | 74 | ```cs 75 | var table = db.Table("PizzaOrder"); 76 | var pizzaOrder = table.First() as PizzaOrder; 77 | ``` 78 | 79 | 5. Update record in transaction 80 | 81 | ```cs 82 | int row = db.DbTran(() => 83 | { 84 | var po = db.Table().Find(PIZZA_ORDER_ID); 85 | po.CreatedTime = DateTime.UtcNow 86 | }); 87 | ``` 88 | 89 | 6. Update record in Patch function 90 | 91 | ```cs 92 | int row = db.DbTran(() => 93 | { 94 | var patch = new DbPatchModel 95 | { 96 | Table = "PizzaOrder", 97 | Id = PIZZA_ORDER_ID 98 | }; 99 | 100 | patch.Values.Add("CreatedTime", dt); 101 | db.Patch(patch); 102 | }); 103 | ``` 104 | 105 | 7. Implement IRequireDbPermission to interupt update 106 | 8. View raw sql 107 | 108 | ```cs 109 | string sql = table.ToSql(); 110 | ``` 111 | 112 | 9. Added MongoDb support 113 | 114 | ```cs 115 | db.BindDbContext(new DatabaseBind 116 | { 117 | MasterConnection = new MongoDbConnection("mongodb://user:password@localhost:27017/db"), 118 | }); 119 | var collection = db.Collection().FirstOrDefault(); 120 | 121 | // Add new record 122 | db.Collection().InsertOne(new MongoDbCollection 123 | { 124 | Id = Guid.NewGuid().ToString(), 125 | Name = "Pizza" 126 | }); 127 | 128 | // Update record 129 | db.Collection().UpdateOne(x => x.Name == "Pizza", x => x.Name, "Pizza 2"); 130 | ``` 131 | 132 | 10. Support Amazon Redshift 133 | 134 | ```cs 135 | db.BindDbContext(new DatabaseBind 136 | { 137 | string connString = "Server=*.us-east-1.redshift.amazonaws.com; Port=5439;User ID=;Password=;Database=;Server Compatibility Mode=Redshift;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"; 138 | MasterConnection = new NpgsqlConnection(connString), 139 | }); 140 | ``` 141 | 142 | ### Documentation 143 | 144 | https://entityframeworkcorebootkit.readthedocs.io 145 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/EntityFrameworkCore.BootKit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(LangVersion) 5 | 6 | 7 | 8 | net10.0;net8.0;net6.0;netstandard2.1 9 | $(GeneratePackageOnBuild) 10 | EntityFrameworkCore Boot Kit (EFBK) is a quick start database connecter for using EntityFrameworkCore. 11 | Support variety of databases such as Sqlite, MySql, SqlServer, PostgreSql, MongoDb, Amazon Redshift, AWS Aurora and Memory database. 12 | https://github.com/Oceania2018/EntityFrameworkCore.BootKit 13 | https://github.com/Oceania2018/EntityFrameworkCore.BootKit 14 | EntityFramework, EntityFrameworkCore, Database, Redshift, Aurora, Postgre, MySql, MongoDB 15 | Haiping Chen 16 | SciSharp STACK 17 | $(BootKitVersion) 18 | 1. Upgrade to EF 10.0. 19 | 20 | $(BootKitVersion) 21 | $(BootKitVersion) 22 | Apache 2.0 23 | git 24 | https://avatars3.githubusercontent.com/u/44989469?s=200&v=4 25 | MIT 26 | 27 | 28 | 29 | TRACE;DEBUG 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Mongo/DbContext4MongoDb.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using MongoDB.Bson.Serialization.Conventions; 3 | using MongoDB.Driver; 4 | using System; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace EntityFrameworkCore.BootKit; 9 | 10 | public class DbContext4MongoDb : DataContext 11 | { 12 | private static bool _isRegisteredConvention = false; 13 | 14 | public DbContext4MongoDb(DbContextOptions options, IServiceProvider serviceProvider) 15 | : base(options, serviceProvider) { } 16 | 17 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 18 | { 19 | SetLog(optionsBuilder); 20 | optionsBuilder.UseMongoDb(ConnectionString); 21 | base.OnConfiguring(optionsBuilder); 22 | } 23 | 24 | public new IMongoCollection Set(string name = "") 25 | { 26 | return GetDatabase().GetCollection(String.IsNullOrEmpty(name) ? typeof(TEntity).Name : name); 27 | } 28 | 29 | public IMongoDatabase GetDatabase() 30 | { 31 | string databaseName = ConnectionString.Split('/').Last().Split('?').First(); 32 | 33 | /*MongoClientSettings settings = new MongoClientSettings(); 34 | settings.ConnectTimeout = new TimeSpan(0, 0, 0, 30, 0); 35 | settings.ConnectionMode = ConnectionMode.Direct; 36 | 37 | string server = new Regex("@.+/").Match(ConnectionString).Value.Substring(1); 38 | string host = server.Split(':').First(); 39 | string userName = new Regex("//[^@]+").Match(ConnectionString).Value.Split(':').First().Substring(2); 40 | string password = new Regex("//[^@]+").Match(ConnectionString).Value.Split(':').Last(); 41 | 42 | MongoCredential credential = MongoCredential.CreateCredential(databaseName, userName, password); 43 | settings.Credential = credential; 44 | 45 | settings.Server = new MongoServerAddress(host);*/ 46 | 47 | MongoClient client = new MongoClient(ConnectionString); 48 | 49 | IMongoDatabase database = client.GetDatabase(databaseName); 50 | 51 | // Prevent ConventionRegistry to keep growing 52 | if (!_isRegisteredConvention) 53 | { 54 | var packs = new ConventionPack 55 | { 56 | new IgnoreExtraElementsConvention(true) 57 | }; 58 | 59 | if (useCamelCase) 60 | { 61 | packs.Add(new CamelCaseElementNameConvention()); 62 | } 63 | 64 | ConventionRegistry.Register("EntityFrameworkCore.BootKit", packs, t => true); 65 | _isRegisteredConvention = true; 66 | } 67 | 68 | return database; 69 | } 70 | } 71 | 72 | public class DbContext4MongoDb2 : DataContext 73 | { 74 | private static bool _isRegisteredIgnoreExtraElementsConvention = false; 75 | 76 | public DbContext4MongoDb2(DbContextOptions options, IServiceProvider serviceProvider) 77 | : base(options, serviceProvider) { } 78 | 79 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 80 | { 81 | SetLog(optionsBuilder); 82 | optionsBuilder.UseMongoDb(ConnectionString); 83 | base.OnConfiguring(optionsBuilder); 84 | } 85 | 86 | public new IMongoCollection Set(string name = "") 87 | { 88 | return GetDatabase().GetCollection(String.IsNullOrEmpty(name) ? typeof(TEntity).Name : name); 89 | } 90 | 91 | public IMongoDatabase GetDatabase() 92 | { 93 | string databaseName = ConnectionString.Split('/').Last(); 94 | 95 | MongoClientSettings settings = new MongoClientSettings(); 96 | settings.ConnectTimeout = new TimeSpan(0, 0, 0, 30, 0); 97 | settings.DirectConnection = true; 98 | 99 | string server = new Regex("@.+/").Match(ConnectionString).Value.Substring(1); 100 | string host = server.Split(':').First(); 101 | string userName = new Regex("//[^@]+").Match(ConnectionString).Value.Split(':').First().Substring(2); 102 | string password = new Regex("//[^@]+").Match(ConnectionString).Value.Split(':').Last(); 103 | 104 | MongoCredential credential = MongoCredential.CreateCredential(databaseName, userName, password); 105 | settings.Credential = credential; 106 | 107 | settings.Server = new MongoServerAddress(host); 108 | 109 | MongoClient client = new MongoClient(settings); 110 | 111 | IMongoDatabase database = client.GetDatabase(databaseName); 112 | 113 | // Prevent ConventionRegistry to keep growing 114 | if (!_isRegisteredIgnoreExtraElementsConvention) 115 | { 116 | var pack = new ConventionPack(); 117 | pack.Add(new IgnoreExtraElementsConvention(true)); 118 | ConventionRegistry.Register("EntityFrameworkCore.BootKit", pack, t => true); 119 | _isRegisteredIgnoreExtraElementsConvention = true; 120 | } 121 | 122 | return database; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.Mongo/MongoDbQueryExtension.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using MongoDB.Driver.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Dynamic.Core; 7 | using System.Linq.Expressions; 8 | 9 | namespace EntityFrameworkCore.BootKit; 10 | 11 | public static class MongoDbQueryExtension 12 | { 13 | public static IQueryable Queryable(this IMongoCollection source) 14 | { 15 | return source.AsQueryable(); 16 | } 17 | 18 | public static TSource FirstOrDefault(this IMongoCollection source, Expression> filter = null) 19 | { 20 | return filter == null ? source.Queryable().FirstOrDefault() : source.Queryable().FirstOrDefault(filter); 21 | } 22 | 23 | public static TSource LastOrDefault(this IMongoCollection source, Expression> filter = null) 24 | { 25 | throw new NotSupportedException("MongoDB does not support LastOrDefault() method."); 26 | // return filter == null ? source.Queryable().LastOrDefault() : source.Queryable().LastOrDefault(filter); 27 | } 28 | 29 | public static IOrderedQueryable OrderByDescending(this IMongoCollection source, Expression> keySelector) 30 | { 31 | return source.Queryable().OrderByDescending(keySelector); 32 | } 33 | 34 | public static IQueryable Where(this IMongoCollection source, Expression> filter) 35 | { 36 | return source.Queryable().Where(filter); 37 | } 38 | 39 | public static UpdateResult UpdateOne(this IMongoCollection source, 40 | Expression> filter, 41 | Expression> field, 42 | TField value) 43 | { 44 | return source.UpdateOne(filter, Builders.Update.Set(field, value)); 45 | } 46 | 47 | public static UpdateResult UpdateOne(this IMongoCollection source, 48 | Expression> filter, 49 | (Expression>, object)[] kvs) 50 | { 51 | var updateDefinitionBuilder = Builders.Update; 52 | var definitions = new List>(); 53 | foreach (var pair in kvs) 54 | { 55 | definitions.Add(updateDefinitionBuilder.Set(pair.Item1, pair.Item2)); 56 | } 57 | 58 | var updateFields = updateDefinitionBuilder.Combine(definitions); 59 | 60 | return source.UpdateOne(filter, updateFields); 61 | } 62 | 63 | public static UpdateResult UpsetOne(this IMongoCollection source, 64 | Expression> filter, 65 | Expression> field, 66 | TField value) 67 | { 68 | return source.UpdateOne(filter, Builders.Update.Set(field, value), options: new UpdateOptions 69 | { 70 | IsUpsert = true 71 | }); 72 | } 73 | 74 | public static UpdateResult UpsertOne(this IMongoCollection source, 75 | Expression> filter, 76 | (Expression>, TField1)[] kvs) 77 | { 78 | var updateDefinitionBuilder = Builders.Update; 79 | var definitions = new List>(); 80 | foreach (var pair in kvs) 81 | { 82 | definitions.Add(updateDefinitionBuilder.Set(pair.Item1, pair.Item2)); 83 | } 84 | 85 | var updateFields = updateDefinitionBuilder.Combine(definitions); 86 | 87 | return source.UpdateOne(filter, updateFields, options: new UpdateOptions 88 | { 89 | IsUpsert = true 90 | }); 91 | } 92 | 93 | public static UpdateResult UpdateMany(this IMongoCollection source, 94 | Expression> filter, 95 | Expression> field, 96 | TField value) 97 | { 98 | return source.UpdateMany(filter, Builders.Update.Set(field, value)); 99 | } 100 | 101 | public static UpdateResult UpsertMany(this IMongoCollection source, 102 | Expression> filter, 103 | Expression> field, 104 | TField value) 105 | { 106 | return source.UpdateMany(filter, Builders.Update.Set(field, value), options: new UpdateOptions 107 | { 108 | IsUpsert = true 109 | }); 110 | } 111 | 112 | public static DeleteResult DeleteOne(this IMongoCollection source, 113 | Expression> filter) 114 | { 115 | return source.DeleteOne(filter); 116 | } 117 | 118 | public static DeleteResult DeleteMany(this IMongoCollection source, 119 | Expression> filter) 120 | { 121 | return source.DeleteMany(filter); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace EntityFrameworkCore.BootKit 8 | { 9 | public static class Utility 10 | { 11 | public static Type GetType(String typeName, params string[] assemblyNames) 12 | { 13 | for (int i = 0; i < assemblyNames.Count(); i++) 14 | { 15 | List types = Assembly.Load(new AssemblyName(assemblyNames[i])) 16 | .GetTypes().Where(x => !x.IsAbstract && !x.FullName.StartsWith("<>f__AnonymousType")).ToList(); 17 | 18 | Type type = types.FirstOrDefault(x => x.Name == typeName); 19 | if (type != null) 20 | { 21 | return type; 22 | }; 23 | } 24 | 25 | return null; 26 | } 27 | 28 | public static List GetClassesWithInterface(Type type, string assemblyName) 29 | { 30 | List types = Assembly.Load(new AssemblyName(assemblyName)) 31 | .GetTypes() 32 | .Where(x => !x.IsAbstract && !x.FullName.StartsWith("<>f__AnonymousType")) 33 | .ToList(); 34 | 35 | types = types.Where(x => !x.GetTypeInfo().IsAbstract && x.GetInterfaces().Contains(type)).ToList(); 36 | 37 | return types; 38 | } 39 | 40 | public static List GetClassesWithInterface(Type type, params string[] assemblyNames) 41 | { 42 | List types = new List(); 43 | assemblyNames.ToList().ForEach(assemblyName => { 44 | types.AddRange(GetClassesWithInterface(type, assemblyName)); 45 | }); 46 | 47 | return types; 48 | } 49 | 50 | public static List GetInstanceWithInterface(params string[] assemblyNames) 51 | { 52 | List instances = new List(); 53 | 54 | var types = GetClassesWithInterface(typeof(T), assemblyNames); 55 | var objects = types.Where(x => x.GetInterfaces().Contains(typeof(T))).Select(x => (T)Activator.CreateInstance(x)).ToList(); 56 | instances.AddRange(objects); 57 | 58 | return instances; 59 | } 60 | 61 | public static object InvokeFunction(this object obj, string methodName, object[] parameters) 62 | { 63 | Type type = obj.GetType(); 64 | MethodInfo method = type.GetMethod(methodName); 65 | return method.Invoke(obj, parameters); 66 | } 67 | 68 | public static bool SetValue(this object obj, string propName, object value) 69 | { 70 | Type type = obj.GetType(); 71 | PropertyInfo property = type.GetProperties().FirstOrDefault(x => x.Name.ToLower().Equals(propName.ToLower())); 72 | if (property == null) return false; 73 | 74 | if (property.PropertyType.Equals(typeof(String))) 75 | { 76 | property.SetValue(obj, value.ToString(), null); 77 | } 78 | else if (property.PropertyType.Equals(typeof(DateTime)) && value.GetType() == typeof(string)) 79 | { 80 | property.SetValue(obj, DateTime.Parse(value.ToString()), null); 81 | } 82 | else if (property.PropertyType.Equals(typeof(Int32)) && value.GetType() == typeof(string)) 83 | { 84 | property.SetValue(obj, Int32.Parse(value.ToString()), null); 85 | } 86 | /*else if (property.PropertyType.IsEnum) 87 | { 88 | property.SetValue(obj, int.Parse(value.ToString()), null); 89 | } 90 | else if (property.PropertyType.IsGenericType && Nullable.GetUnderlyingType(property.PropertyType) != null && Nullable.GetUnderlyingType(property.PropertyType).IsEnum) 91 | { 92 | var enumType = Nullable.GetUnderlyingType(property.PropertyType); 93 | var enumValue = Enum.ToObject(enumType, value); 94 | property.SetValue(obj, enumValue, null); 95 | } 96 | else if (property.PropertyType.IsGenericType && Nullable.GetUnderlyingType(property.PropertyType) != null && Nullable.GetUnderlyingType(property.PropertyType).Equals(typeof(int))) 97 | { 98 | property.SetValue(obj, int.Parse(value.ToString()), null); 99 | }*/ 100 | else if (property.PropertyType.IsGenericType && Nullable.GetUnderlyingType(property.PropertyType) != null && Nullable.GetUnderlyingType(property.PropertyType).Equals(typeof(decimal))) 101 | { 102 | if (String.IsNullOrEmpty(value.ToString())) 103 | { 104 | property.SetValue(obj, null); 105 | } 106 | else 107 | { 108 | property.SetValue(obj, decimal.Parse(value.ToString())); 109 | } 110 | } 111 | else if (property.PropertyType.Equals(typeof(Decimal))) 112 | { 113 | property.SetValue(obj, decimal.Parse(value.ToString())); 114 | } 115 | else 116 | { 117 | property.SetValue(obj, value, null); 118 | } 119 | 120 | return true; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # EntityFrameworkCore.BootKit documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Aug 21 16:39:58 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'EntityFrameworkCore.BootKit' 50 | copyright = '2018, Haiping Chen' 51 | author = 'Haiping Chen' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '1.9.1' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '1.9.1' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ---------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'sphinx_rtd_theme' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a theme 89 | # further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | # Custom sidebar templates, must be a dictionary that maps document names 100 | # to template names. 101 | # 102 | # This is required for the alabaster theme 103 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 104 | html_sidebars = { 105 | '**': [ 106 | 'relations.html', # needs 'show_related': True theme option to display 107 | 'searchbox.html', 108 | ] 109 | } 110 | 111 | 112 | # -- Options for HTMLHelp output ------------------------------------------ 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = 'EntityFrameworkCoreBootKitdoc' 116 | 117 | 118 | # -- Options for LaTeX output --------------------------------------------- 119 | 120 | latex_elements = { 121 | # The paper size ('letterpaper' or 'a4paper'). 122 | # 123 | # 'papersize': 'letterpaper', 124 | 125 | # The font size ('10pt', '11pt' or '12pt'). 126 | # 127 | # 'pointsize': '10pt', 128 | 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | 133 | # Latex figure (float) alignment 134 | # 135 | # 'figure_align': 'htbp', 136 | } 137 | 138 | # Grouping the document tree into LaTeX files. List of tuples 139 | # (source start file, target name, title, 140 | # author, documentclass [howto, manual, or own class]). 141 | latex_documents = [ 142 | (master_doc, 'EntityFrameworkCoreBootKit.tex', 'EntityFrameworkCore.BootKit Documentation', 143 | 'Haiping Chen', 'manual'), 144 | ] 145 | 146 | 147 | # -- Options for manual page output --------------------------------------- 148 | 149 | # One entry per manual page. List of tuples 150 | # (source start file, name, description, authors, manual section). 151 | man_pages = [ 152 | (master_doc, 'entityframeworkcorebootkit', 'EntityFrameworkCore.BootKit Documentation', 153 | [author], 1) 154 | ] 155 | 156 | 157 | # -- Options for Texinfo output ------------------------------------------- 158 | 159 | # Grouping the document tree into Texinfo files. List of tuples 160 | # (source start file, target name, title, author, 161 | # dir menu entry, description, category) 162 | texinfo_documents = [ 163 | (master_doc, 'EntityFrameworkCoreBootKit', 'EntityFrameworkCore.BootKit Documentation', 164 | author, 'EntityFrameworkCoreBootKit', 'One line description of project.', 165 | 'Miscellaneous'), 166 | ] 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | /bootkit.db 290 | /docs/_build -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/DatabaseExtensions/TransactionDatabaseExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.Storage; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace EntityFrameworkCore.BootKit 10 | { 11 | public static class TransactionDatabaseExtension 12 | { 13 | public static DatabaseFacade GetDatabaseFacade(this Database db) 14 | => db.GetMaster(typeof(TTableInterface)).Database; 15 | public static IDbContextTransaction BeginTransaction(this Database db) 16 | { 17 | var masterDb = db.GetMaster(typeof(TTableInterface)).Database; 18 | if (masterDb.CurrentTransaction == null) 19 | return masterDb.BeginTransaction(); 20 | else 21 | return masterDb.CurrentTransaction; 22 | } 23 | 24 | public static void RollbackTransaction(this Database db) 25 | { 26 | var masterDb = db.GetMaster(typeof(TTableInterface)).Database; 27 | if (masterDb.CurrentTransaction != null) 28 | masterDb.RollbackTransaction(); 29 | } 30 | 31 | public static void EndTransaction(this Database db) 32 | { 33 | var masterDb = db.GetMaster(typeof(TTableInterface)).Database; 34 | // current transaction will be null if it's been rollbacked. 35 | if (masterDb.CurrentTransaction == null) 36 | return; 37 | try 38 | { 39 | db.SaveChanges(); 40 | masterDb.CurrentTransaction.Commit(); 41 | } 42 | catch (Exception ex) 43 | { 44 | masterDb.CurrentTransaction.Rollback(); 45 | if (ex.Message.Contains("See the inner exception for details")) 46 | throw ex.InnerException; 47 | else 48 | throw ex; 49 | } 50 | } 51 | 52 | public static void DiscardChanges(DbContext context) 53 | { 54 | // Clear Change Tracker to discard changes 55 | // Available in EF Core 7+ 56 | #if NET8_0_OR_GREATER 57 | context.ChangeTracker.Clear(); 58 | #else 59 | foreach (var entry in context.ChangeTracker.Entries()) 60 | { 61 | entry.State = EntityState.Detached; 62 | } 63 | #endif 64 | } 65 | 66 | public static int Transaction(this Database db, Action action) 67 | { 68 | var dbContext = db.GetMaster(typeof(TTableInterface)); 69 | var masterDb = dbContext.Database; 70 | int affected = 0; 71 | 72 | if (masterDb.CurrentTransaction == null) 73 | { 74 | using (var transaction = masterDb.BeginTransaction()) 75 | { 76 | try 77 | { 78 | action(); 79 | affected = db.SaveChanges(); 80 | transaction.Commit(); 81 | } 82 | catch (Exception ex) 83 | { 84 | transaction.Rollback(); 85 | DiscardChanges(dbContext); 86 | if (ex.Message.Contains("See the inner exception for details")) 87 | throw ex.InnerException; 88 | else 89 | throw ex; 90 | } 91 | } 92 | } 93 | else 94 | { 95 | try 96 | { 97 | action(); 98 | affected = db.SaveChanges(); 99 | } 100 | catch (Exception ex) 101 | { 102 | if (masterDb.CurrentTransaction != null) 103 | { 104 | masterDb.CurrentTransaction.Rollback(); 105 | DiscardChanges(dbContext); 106 | } 107 | if (ex.Message.Contains("See the inner exception for details")) 108 | throw ex.InnerException; 109 | else 110 | throw ex; 111 | } 112 | } 113 | 114 | return affected; 115 | } 116 | 117 | public static TResult Transaction(this Database db, Func action) 118 | { 119 | using (IDbContextTransaction transaction = db.GetMaster(typeof(T)).Database.BeginTransaction()) 120 | { 121 | TResult result = default(TResult); 122 | try 123 | { 124 | result = action(); 125 | db.SaveChanges(); 126 | transaction.Commit(); 127 | } 128 | catch (Exception ex) 129 | { 130 | transaction.Rollback(); 131 | if (ex.Message.Contains("See the inner exception for details")) 132 | throw ex.InnerException; 133 | else 134 | throw ex; 135 | } 136 | 137 | return result; 138 | } 139 | } 140 | 141 | public static async Task TransactionAsync(this Database db, Func func) 142 | { 143 | var masterDb = db.GetMaster(typeof(TTableInterface)).Database; 144 | int affected = 0; 145 | if (masterDb.CurrentTransaction == null) 146 | { 147 | using (var transaction = await masterDb.BeginTransactionAsync()) 148 | { 149 | try 150 | { 151 | await func(); 152 | affected = await db.SaveChangesAsync(); 153 | await transaction.CommitAsync(); 154 | } 155 | catch (Exception ex) 156 | { 157 | await transaction.RollbackAsync(); 158 | if (ex.Message.Contains("See the inner exception for details")) 159 | throw ex.InnerException; 160 | else throw ex; 161 | } 162 | } 163 | } 164 | else 165 | { 166 | try 167 | { 168 | await func(); 169 | affected = await db.SaveChangesAsync(); 170 | } 171 | catch (Exception ex) 172 | { 173 | if (masterDb.CurrentTransaction != null) 174 | await masterDb.CurrentTransaction.RollbackAsync(); 175 | else if (ex.Message.Contains("See the inner exception for details")) 176 | throw ex.InnerException; 177 | else throw ex; 178 | } 179 | } 180 | return affected; 181 | } 182 | 183 | public static async Task TransactionAsync(this Database db, Func> func) 184 | { 185 | using (IDbContextTransaction transaction = await db.GetMaster(typeof(T)).Database.BeginTransactionAsync()) 186 | { 187 | TResult result = default; 188 | try 189 | { 190 | result = await func(); 191 | await db.SaveChangesAsync(); 192 | await transaction.CommitAsync(); 193 | } 194 | catch (Exception ex) 195 | { 196 | await transaction.RollbackAsync(); 197 | if (ex.Message.Contains("See the inner exception for details")) 198 | throw ex.InnerException; 199 | else 200 | throw ex; 201 | } 202 | return result; 203 | } 204 | } 205 | 206 | /// 207 | /// Shortcut for IDbRecord Transaction 208 | /// 209 | /// 210 | /// 211 | public static int DbTran(this Database db, Action action) 212 | { 213 | return db.Transaction(action); 214 | } 215 | 216 | public static IDbContextTransaction GetDbContextTransaction(this Database db) 217 | { 218 | var masterDb = db.GetMaster(typeof(T)).Database; 219 | 220 | if (masterDb.CurrentTransaction == null) 221 | return masterDb.BeginTransaction(); 222 | else 223 | return masterDb.CurrentTransaction; 224 | } 225 | 226 | public static bool IsTransactionOpen(this Database db) 227 | { 228 | var masterDb = db.GetMaster(typeof(T)).Database; 229 | return masterDb.CurrentTransaction != null; 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit.UnitTest/DatabaseTest.cs: -------------------------------------------------------------------------------- 1 | using EntityFrameworkCore.BootKit.DbContexts; 2 | using EntityFrameworkCore.BootKit.Sqlite; 3 | using EntityFrameworkCore.BootKit.UnitTest.Tables; 4 | using Microsoft.Data.SqlClient; 5 | using Microsoft.Data.Sqlite; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using MySqlConnector; 9 | using Npgsql; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | 15 | namespace EntityFrameworkCore.BootKit.UnitTest; 16 | 17 | [TestClass] 18 | public class DatabaseTest 19 | { 20 | [TestMethod] 21 | public void TestSqlite() 22 | { 23 | AddRecord(GetDb(DatabaseType.Sqlite)); 24 | AddRecordByTableName(GetDb(DatabaseType.Sqlite)); 25 | GetRecordsByTableName(GetDb(DatabaseType.Sqlite)); 26 | UpdateRecordsByTableName(GetDb(DatabaseType.Sqlite)); 27 | PatchRecord(GetDb(DatabaseType.Sqlite)); 28 | } 29 | 30 | [TestMethod] 31 | public void TestSqlServer() 32 | { 33 | AddRecord(GetDb(DatabaseType.SqlServer)); 34 | AddRecordByTableName(GetDb(DatabaseType.SqlServer)); 35 | GetRecordsByTableName(GetDb(DatabaseType.SqlServer)); 36 | UpdateRecordsByTableName(GetDb(DatabaseType.SqlServer)); 37 | PatchRecord(GetDb(DatabaseType.SqlServer)); 38 | } 39 | 40 | [TestMethod] 41 | public void TestMySql() 42 | { 43 | AddRecord(GetDb(DatabaseType.MySql)); 44 | AddRecordByTableName(GetDb(DatabaseType.MySql)); 45 | GetRecordsByTableName(GetDb(DatabaseType.MySql)); 46 | UpdateRecordsByTableName(GetDb(DatabaseType.MySql)); 47 | PatchRecord(GetDb(DatabaseType.MySql)); 48 | } 49 | 50 | [TestMethod] 51 | public void TestPostgreSql() 52 | { 53 | AddRecord(GetDb(DatabaseType.PostgreSql)); 54 | AddRecordByTableName(GetDb(DatabaseType.PostgreSql)); 55 | GetRecordsByTableName(GetDb(DatabaseType.PostgreSql)); 56 | UpdateRecordsByTableName(GetDb(DatabaseType.PostgreSql)); 57 | PatchRecord(GetDb(DatabaseType.PostgreSql)); 58 | } 59 | 60 | [TestMethod] 61 | public void TestRedshift() 62 | { 63 | AddRecord(GetDb(DatabaseType.Redshift)); 64 | AddRecordByTableName(GetDb(DatabaseType.Redshift)); 65 | GetRecordsByTableName(GetDb(DatabaseType.Redshift)); 66 | UpdateRecordsByTableName(GetDb(DatabaseType.Redshift)); 67 | PatchRecord(GetDb(DatabaseType.Redshift)); 68 | } 69 | 70 | [TestMethod] 71 | public void TestMongoDb() 72 | { 73 | var db = GetDb(DatabaseType.MongoDb); 74 | var collection = db.Collection().FirstOrDefault(); 75 | 76 | // Add new record 77 | db.Collection().InsertOne(new MongoDbCollection 78 | { 79 | Id = Guid.NewGuid().ToString(), 80 | Name = "Pizza" 81 | }); 82 | 83 | // Update record 84 | db.Collection().UpdateOne(x => x.Name == "Pizza", x => x.Name, "Pizza 2"); 85 | } 86 | 87 | [TestMethod] 88 | public void TestRawQuery() 89 | { 90 | var db = GetDb(DatabaseType.SqlServer); 91 | AddRecord(db); 92 | var pizza = db.Query("SELECT Id, OrderNumber FROM PizzaOrder WHERE Id=@Id", new 93 | { 94 | Id = PIZZA_ORDER_ID 95 | }).First(); 96 | Assert.AreEqual(pizza.Id, PIZZA_ORDER_ID); 97 | 98 | var dynamic_pizza = db.Query("SELECT Id, OrderNumber FROM PizzaOrder WHERE Id=@Id", new 99 | { 100 | Id = PIZZA_ORDER_ID 101 | }).First(); 102 | Assert.AreEqual(dynamic_pizza.Id, PIZZA_ORDER_ID); 103 | } 104 | 105 | private Database GetDb(DatabaseType databaseType) 106 | { 107 | var db = new Database(); 108 | AppDomain.CurrentDomain.SetData("Assemblies", new string[] { "EntityFrameworkCore.BootKit.UnitTest" }); 109 | 110 | if (databaseType == DatabaseType.Sqlite) 111 | { 112 | db.BindDbContext(new DatabaseBind 113 | { 114 | MasterConnection = new SqliteConnection($"Data Source={Directory.GetCurrentDirectory()}\\..\\..\\..\\..\\Bootkit.db"), 115 | CreateDbIfNotExist = true, 116 | }); 117 | } 118 | else if (databaseType == DatabaseType.SqlServer) 119 | { 120 | db.BindDbContext(new DatabaseBind 121 | { 122 | MasterConnection = new SqlConnection("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Bootkit;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"), 123 | CreateDbIfNotExist = true 124 | }); 125 | } 126 | else if (databaseType == DatabaseType.MySql) 127 | { 128 | db.BindDbContext(new DatabaseBind 129 | { 130 | MasterConnection = new MySqlConnection("Data Source=;port=3306;Initial Catalog=;user id=;password=;CharSet=utf8;Allow User Variables=True;"), 131 | CreateDbIfNotExist = false 132 | }); 133 | } 134 | else if (databaseType == DatabaseType.MongoDb) 135 | { 136 | db.BindDbContext(new DatabaseBind 137 | { 138 | MasterConnection = new MongoDbConnection("mongodb://user:password@localhost:27017/db"), 139 | }); 140 | } 141 | else if (databaseType == DatabaseType.PostgreSql) 142 | { 143 | db.BindDbContext(new DatabaseBind 144 | { 145 | MasterConnection = new NpgsqlConnection("Server=; Port=5439;User ID=;Password=;Database=;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"), 146 | }); 147 | } 148 | else if (databaseType == DatabaseType.Redshift) 149 | { 150 | db.BindDbContext(new DatabaseBind 151 | { 152 | MasterConnection = new NpgsqlConnection("Server=*.us-east-1.redshift.amazonaws.com; Port=5439;User ID=;Password=;Database=;Server Compatibility Mode=Redshift;SSL Mode=Require;Trust Server Certificate=True;Use SSL Stream=True"), 153 | }); 154 | } 155 | 156 | return db; 157 | } 158 | 159 | private void GetRecordsByTableName(Database db) 160 | { 161 | var table = db.Table("PizzaOrder"); 162 | var pizzaOrder = table.First(x => x.Id == PIZZA_ORDER_ID) as PizzaOrder; 163 | Assert.IsNotNull(pizzaOrder.Id); 164 | } 165 | 166 | public static String PIZZA_ORDER_ID = "7974f8d9-9124-4e24-a906-2e5bb3323e01"; 167 | 168 | private void AddRecord(Database db) 169 | { 170 | var pizza = new PizzaOrder 171 | { 172 | Id = PIZZA_ORDER_ID, 173 | OrderNumber = new Random().Next(1000).ToString(), 174 | CustomerName = "Haiping Chen", 175 | CreatedTime = DateTime.UtcNow, 176 | PizzaTypes = new List { 177 | new PizzaType { Name = "Pizza Type 1", Amount = 10.99M }, 178 | new PizzaType { Name = "Pizza Type 2", Amount = 9.9M } 179 | } 180 | }; 181 | 182 | if (db.Table().Any(x => x.Id == PIZZA_ORDER_ID)) return; 183 | 184 | db.DbTran(() => db.Table().Add(pizza)); 185 | 186 | var order = db.Table().Include(x => x.PizzaTypes).FirstOrDefault(x => x.Id == PIZZA_ORDER_ID); 187 | 188 | Assert.IsNotNull(order.Id); 189 | Assert.IsTrue(order.PizzaTypes.Count == 2); 190 | } 191 | 192 | private void UpdateRecordsByTableName(Database db) 193 | { 194 | DateTime dt = DateTime.UtcNow; 195 | 196 | var table = db.Table("PizzaOrder"); 197 | var pizzaOrder = table.First(x => x.Id == PIZZA_ORDER_ID) as PizzaOrder; 198 | pizzaOrder.CreatedTime = dt; 199 | 200 | var po = db.Table().Find(PIZZA_ORDER_ID); 201 | Assert.IsTrue(po.CreatedTime == dt); 202 | } 203 | 204 | private void PatchRecord(Database db) 205 | { 206 | DateTime dt = DateTime.UtcNow.AddMinutes(-5); 207 | 208 | var patch = new DbPatchModel 209 | { 210 | Table = "PizzaOrder", 211 | Id = PIZZA_ORDER_ID 212 | }; 213 | 214 | patch.Values.Add("CreatedTime", dt); 215 | 216 | int row = db.DbTran(() => db.Patch(patch)); 217 | 218 | var po = db.Table().Find(PIZZA_ORDER_ID); 219 | Assert.IsTrue(po.CreatedTime.ToString() == dt.ToString()); 220 | } 221 | 222 | private void AddRecordByTableName(Database db) 223 | { 224 | var entity = new PizzaType { 225 | Name = "PIZZA" + DateTime.UtcNow.Ticks, 226 | OrderId = PIZZA_ORDER_ID, 227 | Amount = new Random().Next(10000) 228 | }; 229 | 230 | db.Add("PizzaType", entity); 231 | 232 | Assert.IsNotNull(entity.Id); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /EntityFrameworkCore.BootKit/Database.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using EntityFrameworkCore.BootKit.Models; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Data.Common; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace EntityFrameworkCore.BootKit; 13 | 14 | public class Database 15 | { 16 | public List DbContextBinds; 17 | 18 | public Database() 19 | { 20 | DbContextBinds = new List(); 21 | } 22 | 23 | public DatabaseBind GetBinding(Type tableInterface) 24 | { 25 | var binding = DbContextBinds.FirstOrDefault(x => (x.TableInterface != null && x.TableInterface.Equals(tableInterface)) || 26 | (x.Entities != null && x.Entities.Select(e => e.Name).Contains(tableInterface.Name))); 27 | 28 | if (binding == null) 29 | { 30 | throw new Exception($"Can't find binding for interface {tableInterface.ToString()}"); 31 | } 32 | 33 | return binding; 34 | } 35 | 36 | public DatabaseBind GetBinding(string tableName) 37 | { 38 | var binding = DbContextBinds.FirstOrDefault(x => x.Entities != null && x.Entities.Select(entity => entity.Name.ToLower()).Contains(tableName.ToLower())); 39 | 40 | if (binding == null) 41 | { 42 | throw new Exception($"Can't find binding for table {tableName}"); 43 | } 44 | 45 | return binding; 46 | } 47 | 48 | private List GetAllEntityTypes(DatabaseBind bind) 49 | { 50 | var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies"); 51 | return Utility.GetClassesWithInterface(bind.TableInterface, assemblies); 52 | } 53 | 54 | public void BindDbContext(DatabaseBind bind) 55 | { 56 | bind.Entities = GetAllEntityTypes(bind).ToList(); 57 | 58 | DbContextBinds.Add(bind); 59 | } 60 | 61 | public void BindDbContext(DatabaseBind bind) 62 | { 63 | bind.TableInterface = typeof(TTableInterface); 64 | bind.DbContextType = typeof(TDbContextType); 65 | 66 | bind.Entities = GetAllEntityTypes(bind).ToList(); 67 | 68 | if (bind.SlaveConnections == null) 69 | bind.SlaveConnections = new List(); 70 | 71 | if (bind.SlaveConnections.Count == 0) 72 | bind.SlaveConnections.Add(bind.MasterConnection); 73 | 74 | // random 75 | bind.SlaveId = new Random().Next(bind.SlaveConnections.Count); 76 | 77 | DbContextBinds.Add(bind); 78 | 79 | if (bind.CreateDbIfNotExist) 80 | GetMaster(bind.TableInterface).Database.EnsureCreated(); 81 | } 82 | 83 | public DataContext GetMaster(Type tableInterface) 84 | { 85 | var binding = GetBinding(tableInterface); 86 | 87 | if (binding.DbContextMaster == null) 88 | { 89 | DbContextOptions options = new DbContextOptions(); 90 | DataContext dbContext = Activator.CreateInstance(binding.DbContextType, options, binding.ServiceProvider) as DataContext; 91 | dbContext.ConnectionString = binding.MasterConnection.ConnectionString; 92 | dbContext.EntityTypes = binding.Entities; 93 | binding.DbContextMaster = dbContext; 94 | } 95 | 96 | return binding.DbContextMaster; 97 | } 98 | 99 | public DataContext GetReader(Type tableInterface) 100 | { 101 | var binding = GetBinding(tableInterface); 102 | 103 | if (binding.DbContextSlaver == null) 104 | { 105 | DbContextOptions options = new DbContextOptions(); 106 | 107 | DataContext dbContext = Activator.CreateInstance(binding.DbContextType, options, binding.ServiceProvider) as DataContext; 108 | dbContext.EntityTypes = binding.Entities; 109 | 110 | if (binding.SlaveConnections == null || binding.SlaveConnections.Count == 0) 111 | dbContext.ConnectionString = binding.MasterConnection.ConnectionString; 112 | else 113 | dbContext.ConnectionString = binding.SlaveConnection.ConnectionString; 114 | 115 | binding.DbContextSlaver = dbContext; 116 | } 117 | 118 | return binding.DbContextSlaver; 119 | } 120 | 121 | private string RandomConn(List connetions) 122 | { 123 | int idx = new Random().Next(connetions.Count); 124 | return connetions[idx].ConnectionString; 125 | } 126 | 127 | /*public IMongoCollection Collection(string collection) where T : class 128 | { 129 | DatabaseBind binding = GetBinding(typeof(T)); 130 | if (binding.DbContextMaster == null) 131 | { 132 | binding.DbContext = new MongoDbContext(binding.ConnectionString); 133 | } 134 | 135 | return binding.DbContextSlavers.First(); 136 | }*/ 137 | 138 | public Object Find(Type type, params string[] keys) 139 | { 140 | DatabaseBind binding = DbContextBinds.First(x => x.Entities.Contains(type)); 141 | if (binding.DbContextMaster == null || binding.DbContextMaster.Database.CurrentTransaction == null) 142 | { 143 | return GetReader(type).Find(type, keys); 144 | } 145 | else 146 | { 147 | return GetMaster(type).Find(type, keys); 148 | } 149 | } 150 | 151 | public void Add(object entity) 152 | { 153 | var db = GetMaster(typeof(TTableInterface)); 154 | db.Add(entity); 155 | } 156 | 157 | public void Add(IEnumerable entities) 158 | { 159 | var db = GetMaster(typeof(TTableInterface)); 160 | db.AddRange(entities); 161 | } 162 | 163 | public void Delete(object entity) 164 | { 165 | var db = GetMaster(typeof(TTableInterface)); 166 | db.Remove(entity); 167 | } 168 | 169 | public void Delete(IEnumerable entities) 170 | { 171 | var db = GetMaster(typeof(TTableInterface)); 172 | db.RemoveRange(entities); 173 | } 174 | 175 | public DbSet Table() where T : class 176 | { 177 | Type entityType = typeof(T); 178 | 179 | DatabaseBind binding = DbContextBinds.First(x => x.Entities.Contains(entityType)); 180 | if (binding.DbContextMaster == null || binding.DbContextMaster.Database.CurrentTransaction == null) 181 | { 182 | return GetReader(entityType).Set(); 183 | } 184 | else 185 | { 186 | return GetMaster(entityType).Set(); 187 | } 188 | } 189 | 190 | public int ExecuteSqlCommand(string sql, params object[] parameterms) 191 | { 192 | var db = GetMaster(typeof(T)).Database; 193 | return db.ExecuteSqlRaw(sql, parameterms); 194 | } 195 | 196 | public Task ExecuteAsync(string sql, 197 | IEnumerable parameters = default, 198 | DbExecutionOptions options = null, 199 | CancellationToken cancellationToken = default) 200 | { 201 | var db = GetMaster(typeof(T)).Database; 202 | if (options != null) 203 | { 204 | db.SetCommandTimeout(options.Timeout); 205 | } 206 | return db.ExecuteSqlRawAsync(sql, parameters ?? [], cancellationToken); 207 | } 208 | 209 | public IEnumerable Query(string sql, object parameterms = null) 210 | { 211 | var conn = GetBinding(typeof(TTableInterface)).SlaveConnection; 212 | try 213 | { 214 | conn.Open(); 215 | return conn.Query(sql, parameterms); 216 | } 217 | finally 218 | { 219 | conn.Close(); 220 | } 221 | } 222 | 223 | public IEnumerable Query(string sql, object parameterms = null) 224 | { 225 | var conn = GetBinding(typeof(TTableInterface)).SlaveConnection; 226 | try 227 | { 228 | conn.Open(); 229 | return conn.Query(sql, parameterms); 230 | } 231 | finally 232 | { 233 | conn.Close(); 234 | } 235 | } 236 | 237 | public int SaveChanges() 238 | { 239 | var bindings = DbContextBinds.Where(x => x.DbContextType != null) 240 | .Where(x => x.IsRelational && x.DbContextMaster != null) 241 | .ToList(); 242 | 243 | if (bindings.Count() == 0) 244 | { 245 | throw new Exception($"Current transaction is not open."); 246 | } 247 | 248 | var affectedRows = 0; 249 | foreach (var binding in bindings) 250 | { 251 | if (binding.DbContextMaster.Database.CurrentTransaction != null) 252 | affectedRows += binding.DbContextMaster.SaveChanges(); 253 | } 254 | 255 | return affectedRows; 256 | } 257 | 258 | public async Task SaveChangesAsync() 259 | { 260 | var bindings = DbContextBinds.Where(x => x.DbContextType != null).Where(x => x.DbContextMaster != null); 261 | if (bindings.Count() == 0) 262 | { 263 | throw new Exception($"Current transaction is not open."); 264 | } 265 | var affectedRows = 0; 266 | var tasks = new List>(); 267 | foreach (var binding in bindings) 268 | { 269 | if (binding.DbContextMaster.Database.CurrentTransaction != null) 270 | tasks.Add(binding.DbContextMaster.SaveChangesAsync()); 271 | } 272 | await Task.WhenAll(); 273 | foreach (var task in tasks) 274 | affectedRows += task.Result; 275 | return affectedRows; 276 | } 277 | 278 | } 279 | --------------------------------------------------------------------------------