Ver código fonte

support Async methods

Lith 9 meses atrás
pai
commit
71855d49c8
34 arquivos alterados com 1520 adições e 372 exclusões
  1. 23 2
      src/Vitorm.MySql/DbContext_Extensions_UseMySql.cs
  2. 1 0
      src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs
  3. 52 0
      src/Vitorm/Async/DataProvider/SqlDataProvider.Async.cs
  4. 40 0
      src/Vitorm/Async/DbContext.Async.cs
  5. 39 0
      src/Vitorm/Async/DbSet.Async.cs
  6. 35 0
      src/Vitorm/Async/IDbContext.Async.cs
  7. 39 0
      src/Vitorm/Async/IDbSet.Async.cs
  8. 42 0
      src/Vitorm/Async/Sql/SqlDbContext.Async.cs
  9. 67 0
      src/Vitorm/Async/Sql/SqlDbContext.ExecuteAsync.cs
  10. 245 0
      src/Vitorm/Async/Sql/SqlDbSet.Async.cs
  11. 119 0
      src/Vitorm/Async/Sql/SqlExecutor.Async.cs
  12. 1 1
      src/Vitorm/DataProvider/SqlDataProvider.cs
  13. 18 17
      src/Vitorm/DbContext.cs
  14. 1 1
      src/Vitorm/DbSet.cs
  15. 1 1
      src/Vitorm/IDbContext.cs
  16. 2 2
      src/Vitorm/IDbSet.cs
  17. 70 0
      src/Vitorm/Sql/SqlDbContext.Execute.cs
  18. 42 0
      src/Vitorm/Sql/SqlDbContext.Query.Async/SqlDbContext.Query.ToListAsync.cs
  19. 52 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.Count.cs
  20. 25 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteDelete.cs
  21. 31 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteUpdate.cs
  22. 24 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.First.cs
  23. 22 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToExecuteString.cs
  24. 34 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToList.cs
  25. 84 0
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToListAndTotalCount.cs
  26. 139 0
      src/Vitorm/Sql/SqlDbContext.Query.cs
  27. 25 0
      src/Vitorm/Sql/SqlDbContext.Transaction.cs
  28. 1 295
      src/Vitorm/Sql/SqlDbContext.cs
  29. 36 44
      src/Vitorm/Sql/SqlDbSet.cs
  30. 1 3
      src/Vitorm/Sql/SqlExecutor.cs
  31. 2 2
      src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs
  32. 1 0
      src/Vitorm/StreamQuery/StreamToUpdate.cs
  33. 172 0
      test/Vitorm.Sqlite.MsTest/CommonTest/CRUDAsync_Test.cs
  34. 34 4
      test/Vitorm.Sqlite.MsTest/CommonTest/CRUD_Test.cs

+ 23 - 2
src/Vitorm.MySql/DbContext_Extensions_UseMySql.cs

@@ -1,7 +1,11 @@
-using Vitorm.MySql;
+using System.Threading.Tasks;
+
+using Vitorm.MySql;
 using Vitorm.Sql;
 using Vitorm.Sql.Transaction;
 
+using DbConnection = MySqlConnector.MySqlConnection;
+
 namespace Vitorm
 {
     public static class DbContext_Extensions_UseMySql
@@ -13,16 +17,33 @@ namespace Vitorm
         {
             dbContext.Init(
                 sqlTranslateService: Vitorm.MySql.SqlTranslateService.Instance,
-                dbConnectionProvider: config.ToDbConnectionProvider()
+                dbConnectionProvider: config.ToDbConnectionProvider(),
+                sqlExecutor: sqlExecutor
                 );
 
             dbContext.createTransactionScope = createTransactionScope;
 
+
             if (config.commandTimeout.HasValue) dbContext.commandTimeout = config.commandTimeout.Value;
 
             return dbContext;
         }
 
+
+        #region sqlExecutor
+        static SqlExecutor sqlExecutor = new SqlExecutor() { CloseAsync = CloseAsync };
+        static async Task CloseAsync(System.Data.Common.DbConnection conn)
+        {
+            if (conn is DbConnection mySqlConn)
+            {
+                await mySqlConn.CloseAsync();
+                return;
+            }
+            conn.Close();
+        }
+        #endregion
+
+
         static ITransactionScope createTransactionScope(SqlDbContext dbContext) => new Vitorm.MySql.SqlTransactionScope(dbContext);
         //static ITransactionScope createTransactionScope(SqlDbContext dbContext) => new Vitorm.MySql.SqlTransactionScope_Command(dbContext);
 

+ 1 - 0
src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs

@@ -59,6 +59,7 @@ ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
 
                 case "" or null or nameof(Enumerable.ToList):
                 case nameof(Orm_Extensions.ToExecuteString):
+                case nameof(Queryable_Extensions.ToListAsync):
                     {
                         var reader = new DataReader();
                         return prefix + " " + BuildDataReader(arg, stream, reader);

+ 52 - 0
src/Vitorm/Async/DataProvider/SqlDataProvider.Async.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Vitorm.Sql;
+
+namespace Vitorm.DataProvider
+{
+    public abstract partial class SqlDataProvider : IDataProvider
+    {
+        public virtual async Task<T> InvokeInDbAsync<T>(Func<SqlDbContext, Task<T>> func)
+        {
+            using var dbContext = CreateDbContext();
+            return await func(dbContext);
+        }
+
+        public virtual async Task InvokeInDbAsync(Func<SqlDbContext, Task> func)
+        {
+            using var dbContext = CreateDbContext();
+            await func(dbContext);
+        }
+
+
+        // #0 Schema :  Create Drop Truncate
+        public virtual async Task TryCreateTableAsync<Entity>() => await InvokeInDbAsync(db => db.TryCreateTableAsync<Entity>());
+        public virtual async Task TryDropTableAsync<Entity>() => await InvokeInDbAsync(db => db.TryDropTableAsync<Entity>());
+        public virtual async Task TruncateAsync<Entity>() => await InvokeInDbAsync(db => db.TruncateAsync<Entity>());
+
+
+        // #1 Create :  Add AddRange
+        public virtual async Task<Entity> AddAsync<Entity>(Entity entity) => await InvokeInDbAsync(db => db.AddAsync<Entity>(entity));
+        public virtual async Task AddRangeAsync<Entity>(IEnumerable<Entity> entities) => await InvokeInDbAsync(db => db.AddRangeAsync<Entity>(entities));
+
+        // #2 Retrieve : Get Query
+        public virtual async Task<Entity> GetAsync<Entity>(object keyValue) => await InvokeInDbAsync(db => db.GetAsync<Entity>(keyValue));
+        //public virtual IQueryable<Entity> Query<Entity>() => CreateDbContext().AutoDisposeAfterQuery().Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        public virtual async Task<int> UpdateAsync<Entity>(Entity entity) => await InvokeInDbAsync(db => db.UpdateAsync<Entity>(entity));
+        public virtual async Task<int> UpdateRangeAsync<Entity>(IEnumerable<Entity> entities) => await InvokeInDbAsync(db => db.UpdateRangeAsync<Entity>(entities));
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public virtual async Task<int> DeleteAsync<Entity>(Entity entity) => await InvokeInDbAsync(db => db.DeleteAsync<Entity>(entity));
+        public virtual async Task<int> DeleteRangeAsync<Entity>(IEnumerable<Entity> entities) => await InvokeInDb(db => db.DeleteRangeAsync<Entity>(entities));
+
+        public virtual async Task<int> DeleteByKeyAsync<Entity>(object keyValue) => await InvokeInDbAsync(db => db.DeleteByKeyAsync<Entity>(keyValue));
+        public virtual async Task<int> DeleteByKeysAsync<Entity, Key>(IEnumerable<Key> keys) => await InvokeInDbAsync(db => db.DeleteByKeysAsync<Entity, Key>(keys));
+
+    }
+}

+ 40 - 0
src/Vitorm/Async/DbContext.Async.cs

@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Vitorm
+{
+    public abstract partial class DbContext
+    {
+
+        // #0 Schema :  Create Drop Truncate
+        public abstract Task TryCreateTableAsync<Entity>();
+        public abstract Task TryDropTableAsync<Entity>();
+        public abstract Task TruncateAsync<Entity>();
+
+
+        // #1 Create :  Add AddRange
+        public abstract Task<Entity> AddAsync<Entity>(Entity entity);
+        public abstract Task AddRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+        // #2 Retrieve : Get Query
+        public abstract Task<Entity> GetAsync<Entity>(object keyValue);
+        //public virtual IQueryable<Entity> Query<Entity>() => throw new NotImplementedException();
+
+
+        // #3 Update: Update UpdateRange
+        public abstract Task<int> UpdateAsync<Entity>(Entity entity);
+        public abstract Task<int> UpdateRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public abstract Task<int> DeleteAsync<Entity>(Entity entity);
+        public abstract Task<int> DeleteRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+
+        public abstract Task<int> DeleteByKeyAsync<Entity>(object keyValue);
+        public abstract Task<int> DeleteByKeysAsync<Entity, Key>(IEnumerable<Key> keys);
+
+
+
+    }
+}

+ 39 - 0
src/Vitorm/Async/DbSet.Async.cs

@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Vitorm
+{
+
+
+    public partial class DbSet<Entity> : IDbSet<Entity>
+    {
+        // #0 Schema :  Create Drop Truncate
+        public virtual async Task TryCreateTableAsync() => await dbContext.TryCreateTableAsync<Entity>();
+        public virtual async Task TryDropTableAsync() => await dbContext.TryDropTableAsync<Entity>();
+        public virtual async Task TruncateAsync() => await dbContext.TruncateAsync<Entity>();
+
+
+        // #1 Create :  Add AddRange
+        public virtual async Task<Entity> AddAsync(Entity entity) => await dbContext.AddAsync<Entity>(entity);
+        public virtual async Task AddRangeAsync(IEnumerable<Entity> entities) => await dbContext.AddRangeAsync<Entity>(entities);
+
+
+        // #2 Retrieve : Get Query
+        public virtual async Task<Entity> GetAsync(object keyValue) => await dbContext.GetAsync<Entity>(keyValue);
+        //public virtual IQueryable<Entity> Query() => dbContext.Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        public virtual async Task<int> UpdateAsync(Entity entity) => await dbContext.UpdateAsync<Entity>(entity);
+        public virtual async Task<int> UpdateRangeAsync(IEnumerable<Entity> entities) => await dbContext.UpdateRangeAsync<Entity>(entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public virtual async Task<int> DeleteAsync(Entity entity) => await dbContext.DeleteAsync<Entity>(entity);
+        public virtual async Task<int> DeleteRangeAsync(IEnumerable<Entity> entities) => await dbContext.DeleteRangeAsync<Entity>(entities);
+        public virtual async Task<int> DeleteByKeyAsync(object keyValue) => await dbContext.DeleteByKeyAsync<Entity>(keyValue);
+        public virtual async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys) => await dbContext.DeleteByKeysAsync<Entity, Key>(keys);
+
+
+    }
+}

+ 35 - 0
src/Vitorm/Async/IDbContext.Async.cs

@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Vitorm
+{
+    public partial interface IDbContext
+    {
+        // #0 Schema :  Create Drop
+        Task TryCreateTableAsync<Entity>();
+        Task TryDropTableAsync<Entity>();
+        Task TruncateAsync<Entity>();
+
+
+        // #1 Create :  Add AddRange
+        Task<Entity> AddAsync<Entity>(Entity entity);
+        Task AddRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+        // #2 Retrieve : Get Query
+        Task<Entity> GetAsync<Entity>(object keyValue);
+        //IQueryable<Entity> Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        Task<int> UpdateAsync<Entity>(Entity entity);
+        Task<int> UpdateRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        Task<int> DeleteAsync<Entity>(Entity entity);
+        Task<int> DeleteRangeAsync<Entity>(IEnumerable<Entity> entities);
+
+        Task<int> DeleteByKeyAsync<Entity>(object keyValue);
+        Task<int> DeleteByKeysAsync<Entity, Key>(IEnumerable<Key> keys);
+    }
+}

+ 39 - 0
src/Vitorm/Async/IDbSet.Async.cs

@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Vitorm
+{
+    public partial interface IDbSet
+    {
+
+        // #0 Schema :  Create Drop Truncate
+        Task TryCreateTableAsync();
+        Task TryDropTableAsync();
+        Task TruncateAsync();
+    }
+
+    public partial interface IDbSet<Entity> : IDbSet
+    {
+        // #1 Create :  Add AddRange
+        Task<Entity> AddAsync(Entity entity);
+        Task AddRangeAsync(IEnumerable<Entity> entities);
+
+        // #2 Retrieve : Get Query
+        Task<Entity> GetAsync(object keyValue);
+        //IQueryable<Entity> Query();
+
+
+        // #3 Update: Update UpdateRange
+        Task<int> UpdateAsync(Entity entity);
+        Task<int> UpdateRangeAsync(IEnumerable<Entity> entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        Task<int> DeleteAsync(Entity entity);
+        Task<int> DeleteRangeAsync(IEnumerable<Entity> entities);
+        Task<int> DeleteByKeyAsync(object keyValue);
+        Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys);
+
+
+    }
+}

+ 42 - 0
src/Vitorm/Async/Sql/SqlDbContext.Async.cs

@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext
+    {
+
+        // #0 Schema :  Create Drop Truncate
+        public override async Task TryCreateTableAsync<Entity>() => await DbSet<Entity>().TryCreateTableAsync();
+        public override async Task TryDropTableAsync<Entity>() => await DbSet<Entity>().TryDropTableAsync();
+        public override async Task TruncateAsync<Entity>() => await DbSet<Entity>().TruncateAsync();
+
+
+        // #1 Create :  Add AddRange
+        public override async Task<Entity> AddAsync<Entity>(Entity entity) => await DbSet<Entity>().AddAsync(entity);
+        public override async Task AddRangeAsync<Entity>(IEnumerable<Entity> entities) => await DbSet<Entity>().AddRangeAsync(entities);
+
+
+
+        // #2 Retrieve : Get Query
+        public override async Task<Entity> GetAsync<Entity>(object keyValue) => await DbSet<Entity>().GetAsync(keyValue);
+        //public virtual IQueryable<Entity> Query<Entity>() => throw new NotImplementedException();
+
+
+        // #3 Update: Update UpdateRange
+        public override async Task<int> UpdateAsync<Entity>(Entity entity) => await DbSet<Entity>().UpdateAsync(entity);
+        public override async Task<int> UpdateRangeAsync<Entity>(IEnumerable<Entity> entities) => await DbSet<Entity>().UpdateRangeAsync(entities);
+
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public override async Task<int> DeleteAsync<Entity>(Entity entity) => await DbSet<Entity>().DeleteAsync(entity);
+        public override async Task<int> DeleteRangeAsync<Entity>(IEnumerable<Entity> entities) => await DbSet<Entity>().DeleteRangeAsync(entities);
+
+        public override async Task<int> DeleteByKeyAsync<Entity>(object keyValue) => await DbSet<Entity>().DeleteByKeyAsync(keyValue);
+        public override async Task<int> DeleteByKeysAsync<Entity, Key>(IEnumerable<Key> keys) => await DbSet<Entity>().DeleteByKeysAsync<Key>(keys);
+
+
+
+    }
+}

+ 67 - 0
src/Vitorm/Async/Sql/SqlDbContext.ExecuteAsync.cs

@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Threading.Tasks;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        #region ExecuteAsync 
+        public virtual async Task<int> ExecuteWithTransactionAsync(string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+
+            return await sqlExecutor.ExecuteAsync(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+        }
+
+        public virtual async Task<int> ExecuteAsync(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return await sqlExecutor.ExecuteAsync(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return await sqlExecutor.ExecuteAsync(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+
+        public virtual async Task<IDataReader> ExecuteReaderAsync(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return await sqlExecutor.ExecuteReaderAsync(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return await sqlExecutor.ExecuteReaderAsync(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+
+        public virtual async Task<object> ExecuteScalarAsync(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return await sqlExecutor.ExecuteScalarAsync(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return await sqlExecutor.ExecuteScalarAsync(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+        #endregion
+
+
+
+    }
+}

+ 245 - 0
src/Vitorm/Async/Sql/SqlDbSet.Async.cs

@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbSet<Entity> : DbSet<Entity>
+    {
+        #region #0 Schema :  Create Drop Truncate
+        public override async Task TryCreateTableAsync()
+        {
+            string sql = sqlTranslateService.PrepareTryCreateTable(entityDescriptor);
+            await sqlDbContext.ExecuteAsync(sql: sql);
+        }
+
+        public override async Task TryDropTableAsync()
+        {
+            string sql = sqlTranslateService.PrepareTryDropTable(entityDescriptor);
+            await sqlDbContext.ExecuteAsync(sql: sql);
+        }
+        public override async Task TruncateAsync()
+        {
+            string sql = sqlTranslateService.PrepareTruncate(entityDescriptor);
+            await sqlDbContext.ExecuteAsync(sql: sql);
+        }
+        #endregion
+
+
+        #region #1 Create :  Add AddRange
+        public override async Task<Entity> AddAsync(Entity entity)
+        {
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+            var addType = sqlTranslateService.Entity_GetAddType(arg, entity);
+            //if (addType == EAddType.unexpectedEmptyKey) throw new ArgumentException("Key could not be empty.");
+
+            if (addType == EAddType.identityKey)
+            {
+                // #1 prepare sql
+                (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareIdentityAdd(arg);
+
+                // #2 get sql params
+                var sqlParam = GetSqlParams(entity);
+
+                // #3 add
+                var newKeyValue = await sqlDbContext.ExecuteScalarAsync(sql: sql, param: sqlParam);
+
+                // #4 set key value to entity
+                var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
+                newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
+                if (newKeyValue != null)
+                {
+                    entityDescriptor.key.SetValue(entity, newKeyValue);
+                }
+            }
+            else
+            {
+                // #1 prepare sql
+                (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareAdd(arg);
+
+                // #2 get sql params
+                var sqlParam = GetSqlParams(entity);
+
+                // #3 add
+                await sqlDbContext.ExecuteAsync(sql: sql, param: sqlParam);
+            }
+
+            return entity;
+        }
+
+        public override async Task AddRangeAsync(IEnumerable<Entity> entities)
+        {
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) sql_IdentityKey = default;
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) sql_Others = default;
+            var affectedRowCount = 0;
+
+            foreach (var entity in entities)
+            {
+                var addType = sqlTranslateService.Entity_GetAddType(arg, entity);
+                //if (addType == EAddType.unexpectedEmptyKey) throw new ArgumentException("Key could not be empty.");
+
+                if (addType == EAddType.identityKey)
+                {
+                    // #1 prepare sql
+                    if (sql_IdentityKey == default)
+                        sql_IdentityKey = sqlTranslateService.PrepareIdentityAdd(arg);
+
+                    // #2 get sql params
+                    var sqlParam = sql_IdentityKey.GetSqlParams(entity);
+
+                    // #3 add
+                    var newKeyValue = await sqlDbContext.ExecuteScalarAsync(sql: sql_IdentityKey.sql, param: sqlParam);
+
+                    // #4 set key value to entity
+                    var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
+                    newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
+                    if (newKeyValue != null)
+                    {
+                        entityDescriptor.key.SetValue(entity, newKeyValue);
+                    }
+
+                    affectedRowCount++;
+                }
+                else
+                {
+                    // #1 prepare sql
+                    if (sql_Others == default)
+                        sql_Others = sqlTranslateService.PrepareAdd(arg);
+
+                    // #2 get sql params
+                    var sqlParam = sql_Others.GetSqlParams(entity);
+
+                    // #3 add
+                    await sqlDbContext.ExecuteAsync(sql: sql_Others.sql, param: sqlParam);
+
+                    affectedRowCount++;
+                }
+            }
+        }
+        #endregion
+
+
+        #region #2 Retrieve : Get Query
+        public override async Task<Entity> GetAsync(object keyValue)
+        {
+            // #0 get arg
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+
+            // #1 prepare sql
+            string sql = sqlTranslateService.PrepareGet(arg);
+
+            // #2 get sql params
+            var sqlParam = new Dictionary<string, object>();
+            sqlParam[entityDescriptor.keyName] = keyValue;
+
+            // #3 execute
+            using var reader = await sqlDbContext.ExecuteReaderAsync(sql: sql, param: sqlParam, useReadOnly: true);
+
+            if (reader is DbDataReader dataReader ? await dataReader.ReadAsync() : reader.Read())
+            {
+                var entity = (Entity)Activator.CreateInstance(entityDescriptor.entityType);
+                foreach (var column in entityDescriptor.allColumns)
+                {
+                    var value = TypeUtil.ConvertToType(reader[column.columnName], column.type);
+                    if (value != null)
+                        column.SetValue(entity, value);
+                }
+                return entity;
+            }
+
+            return default;
+
+        }
+        //public override IQueryable<Entity> Query() => dbContext.Query<Entity>();
+        #endregion
+
+        #region #3 Update: Update UpdateRange
+        public override async Task<int> UpdateAsync(Entity entity)
+        {
+            // #0 get arg
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+            // #1 prepare sql
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareUpdate(arg);
+
+            // #2 get sql params
+            var sqlParam = GetSqlParams(entity);
+
+            // #3 execute
+            var affectedRowCount = await sqlDbContext.ExecuteAsync(sql: sql, param: sqlParam);
+
+            return affectedRowCount;
+        }
+        public override async Task<int> UpdateRangeAsync(IEnumerable<Entity> entities)
+        {
+            // #0 get arg
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+            // #1 prepare sql
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareUpdate(arg);
+
+            // #2 execute
+            var affectedRowCount = 0;
+
+            foreach (var entity in entities)
+            {
+                var sqlParam = GetSqlParams(entity);
+                affectedRowCount += await sqlDbContext.ExecuteAsync(sql: sql, param: sqlParam);
+            }
+            return affectedRowCount;
+        }
+        #endregion
+
+        #region #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public override async Task<int> DeleteAsync(Entity entity)
+        {
+            var key = entityDescriptor.key.GetValue(entity);
+            return await DeleteByKeyAsync(key);
+        }
+
+        public override async Task<int> DeleteRangeAsync(IEnumerable<Entity> entities)
+        {
+            var keys = entities.Select(entity => entityDescriptor.key.GetValue(entity)).ToList();
+            return await DeleteByKeysAsync(keys);
+        }
+
+        public override async Task<int> DeleteByKeyAsync(object keyValue)
+        {
+            // #0 get arg
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+            // #1 prepare sql
+            string sql = sqlTranslateService.PrepareDelete(arg);
+
+            // #2 get sql params
+            var sqlParam = new Dictionary<string, object>();
+            sqlParam[entityDescriptor.keyName] = keyValue;
+
+            // #3 execute
+            var affectedRowCount = await sqlDbContext.ExecuteAsync(sql: sql, param: sqlParam);
+
+            return affectedRowCount;
+        }
+
+        public override async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys)
+        {
+            // #0 get arg
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+
+            // #1 prepare sql
+            var sql = sqlTranslateService.PrepareDeleteByKeys(arg, keys);
+
+            // #2 execute
+            var affectedRowCount = await sqlDbContext.ExecuteAsync(sql: sql, param: arg.sqlParam);
+            return affectedRowCount;
+        }
+        #endregion
+    }
+}

+ 119 - 0
src/Vitorm/Async/Sql/SqlExecutor.Async.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Threading.Tasks;
+
+using DbCommand = System.Data.Common.DbCommand;
+using DbTransaction = System.Data.Common.DbTransaction;
+namespace Vitorm.Sql
+{
+
+    public partial class SqlExecutor
+    {
+
+        public Func<DbConnection, Task> CloseAsync = (DbConnection conn) => { conn.Close(); return Task.CompletedTask; };
+
+
+        public virtual async Task<int> ExecuteAsync(IDbConnection _conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+            if (_conn is DbConnection conn)
+            {
+                // #1 setup command
+                using var cmd = conn.CreateCommand();
+                if (transaction != null) cmd.Transaction = (DbTransaction)transaction;
+                if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+                cmd.Connection = conn;
+                cmd.CommandText = sql;
+                AddParameter(cmd, param);
+
+
+                // #2 execute
+                bool wasClosed = conn.State == ConnectionState.Closed;
+                try
+                {
+                    if (wasClosed) await conn.OpenAsync();
+                    return await cmd.ExecuteNonQueryAsync();
+                }
+                finally
+                {
+                    if (wasClosed) await CloseAsync(conn);
+                }
+            }
+
+            return await Task.Run(() => Execute(_conn, sql, param: param, transaction: transaction, commandTimeout: commandTimeout));
+        }
+
+        public virtual async Task<object> ExecuteScalarAsync(IDbConnection _conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+            if (_conn is DbConnection conn)
+            {
+                // #1 setup command
+                using var cmd = conn.CreateCommand();
+                if (transaction != null) cmd.Transaction = (DbTransaction)transaction;
+                if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+                cmd.Connection = conn;
+                cmd.CommandText = sql;
+                AddParameter(cmd, param);
+
+                // #2 execute
+                bool wasClosed = conn.State == ConnectionState.Closed;
+                try
+                {
+                    if (wasClosed) await conn.OpenAsync();
+                    return await cmd.ExecuteScalarAsync();
+                }
+                finally
+                {
+
+                    if (wasClosed) await CloseAsync(conn);
+                }
+            }
+
+            return await Task.Run(() => ExecuteScalar(_conn, sql, param: param, transaction: transaction, commandTimeout: commandTimeout));
+        }
+
+        public virtual async Task<IDataReader> ExecuteReaderAsync(IDbConnection _conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+            if (_conn is DbConnection conn)
+            {
+                DbCommand cmd = null;
+
+                bool wasClosed = conn.State == ConnectionState.Closed, disposeCommand = true;
+                try
+                {
+                    // #1 setup command
+                    cmd = conn.CreateCommand();
+                    if (transaction != null) cmd.Transaction = (DbTransaction)transaction;
+                    if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+                    cmd.Connection = conn;
+                    cmd.CommandText = sql;
+                    AddParameter(cmd, param);
+
+                    // #2 execute
+                    var commandBehavior = wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default;
+                    if (wasClosed) await conn.OpenAsync();
+
+                    var reader = await cmd.ExecuteReaderAsync(commandBehavior);
+
+                    wasClosed = false; // don't dispose before giving it to them!
+                    disposeCommand = false;
+                    return reader;
+                }
+                finally
+                {
+                    if (wasClosed) await CloseAsync(conn);
+
+                    if (disposeCommand)
+                    {
+                        //cmd.Parameters.Clear();
+                        cmd.Dispose();
+                    }
+                }
+            }
+
+            return await Task.Run(() => ExecuteReader(_conn, sql, param: param, transaction: transaction, commandTimeout: commandTimeout));
+        }
+
+    }
+}

+ 1 - 1
src/Vitorm/DataProvider/SqlDataProvider.cs

@@ -6,7 +6,7 @@ using Vitorm.Sql;
 
 namespace Vitorm.DataProvider
 {
-    public abstract class SqlDataProvider : IDataProvider
+    public abstract partial class SqlDataProvider : IDataProvider
     {
         DbContext IDataProvider.CreateDbContext() => this.CreateDbContext();
 

+ 18 - 17
src/Vitorm/DbContext.cs

@@ -10,7 +10,7 @@ using Vitorm.Entity.Loader;
 
 namespace Vitorm
 {
-    public class DbContext : IDbContext, IDisposable
+    public abstract partial class DbContext : IDbContext, IDisposable
     {
         public DbContext()
         {
@@ -106,42 +106,43 @@ namespace Vitorm
 
 
 
+        public virtual void Dispose()
+        {
+        }
 
 
 
         // #0 Schema :  Create Drop
-        public virtual void TryCreateTable<Entity>() => throw new NotImplementedException();
-        public virtual void TryDropTable<Entity>() => throw new NotImplementedException();
-        public virtual void Truncate<Entity>() => throw new NotImplementedException();
+        public abstract void TryCreateTable<Entity>();
+        public abstract void TryDropTable<Entity>();
+        public abstract void Truncate<Entity>();
 
 
         // #1 Create :  Add AddRange
-        public virtual Entity Add<Entity>(Entity entity) => throw new NotImplementedException();
-        public virtual void AddRange<Entity>(IEnumerable<Entity> entities) => throw new NotImplementedException();
+        public abstract Entity Add<Entity>(Entity entity);
+        public abstract void AddRange<Entity>(IEnumerable<Entity> entities);
 
         // #2 Retrieve : Get Query
-        public virtual Entity Get<Entity>(object keyValue) => throw new NotImplementedException();
-        public virtual IQueryable<Entity> Query<Entity>() => throw new NotImplementedException();
+        public abstract Entity Get<Entity>(object keyValue);
+        public abstract IQueryable<Entity> Query<Entity>();
 
 
         // #3 Update: Update UpdateRange
-        public virtual int Update<Entity>(Entity entity) => throw new NotImplementedException();
-        public virtual int UpdateRange<Entity>(IEnumerable<Entity> entities) => throw new NotImplementedException();
+        public abstract int Update<Entity>(Entity entity);
+        public abstract int UpdateRange<Entity>(IEnumerable<Entity> entities);
 
 
         // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
-        public virtual int Delete<Entity>(Entity entity) => throw new NotImplementedException();
-        public virtual int DeleteRange<Entity>(IEnumerable<Entity> entities) => throw new NotImplementedException();
+        public abstract int Delete<Entity>(Entity entity);
+        public abstract int DeleteRange<Entity>(IEnumerable<Entity> entities);
 
 
-        public virtual int DeleteByKey<Entity>(object keyValue) => throw new NotImplementedException();
-        public virtual int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys) => throw new NotImplementedException();
+        public abstract int DeleteByKey<Entity>(object keyValue);
+        public abstract int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys);
+
 
 
 
 
-        public virtual void Dispose()
-        {
-        }
     }
 }

+ 1 - 1
src/Vitorm/DbSet.cs

@@ -25,7 +25,7 @@ namespace Vitorm
     }
 
 
-    public class DbSet<Entity> : IDbSet<Entity>
+    public partial class DbSet<Entity> : IDbSet<Entity>
     {
         public virtual DbContext dbContext { get; protected set; }
 

+ 1 - 1
src/Vitorm/IDbContext.cs

@@ -3,7 +3,7 @@ using System.Linq;
 
 namespace Vitorm
 {
-    public interface IDbContext
+    public partial interface IDbContext
     {
         // #0 Schema :  Create Drop
         void TryCreateTable<Entity>();

+ 2 - 2
src/Vitorm/IDbSet.cs

@@ -5,7 +5,7 @@ using Vitorm.Entity;
 
 namespace Vitorm
 {
-    public interface IDbSet
+    public partial interface IDbSet
     {
         IEntityDescriptor entityDescriptor { get; }
         DbContext dbContext { get; }
@@ -20,7 +20,7 @@ namespace Vitorm
         void Truncate();
     }
 
-    public interface IDbSet<Entity> : IDbSet
+    public partial interface IDbSet<Entity> : IDbSet
     {
         // #1 Create :  Add AddRange
         Entity Add(Entity entity);

+ 70 - 0
src/Vitorm/Sql/SqlDbContext.Execute.cs

@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Data;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        #region Execute
+        protected SqlExecutor sqlExecutor;
+        public static int? defaultCommandTimeout;
+        public int? commandTimeout;
+
+        public virtual int ExecuteWithTransaction(string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+
+            return sqlExecutor.Execute(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+        }
+
+        public virtual int Execute(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return sqlExecutor.Execute(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return sqlExecutor.Execute(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+
+        public virtual IDataReader ExecuteReader(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return sqlExecutor.ExecuteReader(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return sqlExecutor.ExecuteReader(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+
+        public virtual object ExecuteScalar(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
+        {
+            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
+            var transaction = GetCurrentTransaction();
+
+            if (useReadOnly && transaction == null)
+            {
+                return sqlExecutor.ExecuteScalar(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
+            }
+            else
+            {
+                return sqlExecutor.ExecuteScalar(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            }
+        }
+        #endregion
+
+
+
+    }
+}

+ 42 - 0
src/Vitorm/Sql/SqlDbContext.Query.Async/SqlDbContext.Query.ToListAsync.cs

@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+        static object Query_ToListAsync(QueryExecutorArgument execArg)
+        {
+            var resultEntityType = execArg.expression.Type.GetGenericArguments()?.FirstOrDefault().GetGenericArguments()?.FirstOrDefault();
+            return Query_ToListAsync_MethodInfo(resultEntityType).Invoke(null, new object[] { execArg });
+        }
+
+
+        static async Task<List<Entity>> Query_ToListAsync<Entity>(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            var arg = new QueryTranslateArgument(dbContext, typeof(Entity));
+
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+            using var reader = await dbContext.ExecuteReaderAsync(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            return (List<Entity>)arg.dataReader.ReadData(reader);
+        }
+
+
+        private static MethodInfo Query_ToListAsync_MethodInfo_;
+        static MethodInfo Query_ToListAsync_MethodInfo(Type entityType) =>
+            (Query_ToListAsync_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<List<string>>>(Query_ToListAsync<string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType);
+
+    }
+}

+ 52 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.Count.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+        /// <summary>
+        /// Queryable.Count or Queryable_Extensions.TotalCount
+        /// </summary>
+        /// <param name="execArg"></param>
+        /// <returns></returns>
+        static object Query_Count(QueryExecutorArgument execArg)
+        {
+            return ExecuteQuery_Count(execArg);
+        }
+
+
+        static int ExecuteQuery_Count(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // deal with skip and take , no need to pass to PrepareCountQuery
+            var queryArg = (combinedStream.orders, combinedStream.skip, combinedStream.take);
+            (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
+
+            // get arg
+            var arg = new QueryTranslateArgument(dbContext, null);
+
+            var sql = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
+
+            var countValue = dbContext.ExecuteScalar(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            var count = Convert.ToInt32(countValue);
+            if (count > 0 && combinedStream.method == nameof(Queryable.Count))
+            {
+                if (queryArg.skip > 0) count = Math.Max(count - queryArg.skip.Value, 0);
+
+                if (queryArg.take.HasValue)
+                    count = Math.Min(count, queryArg.take.Value);
+            }
+
+            (combinedStream.orders, combinedStream.skip, combinedStream.take) = queryArg;
+            return count;
+        }
+
+    }
+}

+ 25 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteDelete.cs

@@ -0,0 +1,25 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        static object Query_ExecuteDelete(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+
+            var entityType = (combinedStream.source as SourceStream)?.GetEntityType();
+            var arg = new QueryTranslateArgument(dbContext, entityType);
+
+            var sql = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
+            var count = dbContext.Execute(sql: sql, param: arg.sqlParam);
+            return count;
+        }
+
+    }
+}

+ 31 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteUpdate.cs

@@ -0,0 +1,31 @@
+using System;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        static object Query_ExecuteUpdate(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+
+
+            if (combinedStream is not StreamToUpdate streamToUpdate) throw new NotSupportedException("not supported query type: " + combinedStream.method);
+
+
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // get arg
+            var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+
+            var sql = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
+
+            return dbContext.Execute(sql: sql, param: arg.sqlParam);
+        }
+    }
+}

+ 24 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.First.cs

@@ -0,0 +1,24 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        static object Query_First(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            var resultEntityType = execArg.expression.Type;
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+            using var reader = dbContext.ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            return arg.dataReader.ReadData(reader);
+        }
+
+    }
+}

+ 22 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToExecuteString.cs

@@ -0,0 +1,22 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+        static object Query_ToExecuteString(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // get arg
+            var arg = new QueryTranslateArgument(dbContext, null);
+
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+            return sql;
+        }
+
+    }
+}

+ 34 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToList.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        static object Query_ToList(QueryExecutorArgument execArg)
+        {
+            var resultEntityType = execArg.expression.Type.GetGenericArguments()?.FirstOrDefault();
+            return Query_ToList(execArg, resultEntityType);
+        }
+
+
+        static object Query_ToList(QueryExecutorArgument execArg, Type resultEntityType)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+            using var reader = dbContext.ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            return arg.dataReader.ReadData(reader);
+        }
+
+    }
+}

+ 84 - 0
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToListAndTotalCount.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+using Vit.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+        protected bool query_ToListAndTotalCount_InvokeInOneExecute = true;
+        static object Query_ToListAndTotalCount(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            var originMethod = combinedStream.method;
+            var resultEntityType = execArg.expression.Type.GetGenericArguments()?.FirstOrDefault()?.GetGenericArguments()?.FirstOrDefault();
+
+            object list; int totalCount;
+
+            if (dbContext.query_ToListAndTotalCount_InvokeInOneExecute)
+            {
+                // get arg
+                var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+
+                string sqlToList, sqlCount;
+                IDbDataReader dataReader;
+
+                // #1 TotalCount
+                {
+                    combinedStream.method = nameof(Enumerable.Count);
+                    var originParams = (combinedStream.orders, combinedStream.skip, combinedStream.take);
+                    (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
+
+                    sqlCount = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
+
+                    (combinedStream.orders, combinedStream.skip, combinedStream.take) = originParams;
+                }
+
+                // #2 ToList
+                {
+                    combinedStream.method = nameof(Enumerable.ToList);
+                    sqlToList = sqlTranslateService.PrepareQuery(arg, combinedStream);
+                    dataReader = arg.dataReader;
+                }
+
+                // #3 read data
+                {
+                    var sql = sqlCount + " ;\r\n" + sqlToList;
+                    using var reader = dbContext.ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
+                    reader.Read();
+                    totalCount = Convert.ToInt32(reader[0]);
+                    reader.NextResult();
+                    list = dataReader.ReadData(reader);
+                }
+            }
+            else
+            {
+                combinedStream.method = nameof(Enumerable.ToList);
+                list = Query_ToList(execArg, resultEntityType);
+
+                combinedStream.method = nameof(Queryable_Extensions.TotalCount);
+                totalCount = ExecuteQuery_Count(execArg);
+            }
+
+            combinedStream.method = originMethod;
+
+            return Query_ToListAndTotalCount_MethodInfo(list.GetType(), typeof(int))
+                .Invoke(null, new[] { list, totalCount });
+        }
+
+
+        private static MethodInfo Query_ToListAndTotalCount_MethodInfo_;
+        static MethodInfo Query_ToListAndTotalCount_MethodInfo(Type type1, Type type2) =>
+            (Query_ToListAndTotalCount_MethodInfo_ ??= new Func<object, int, (object, int)>(ValueTuple.Create<object, int>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(type1, type2);
+
+    }
+}

+ 139 - 0
src/Vitorm/Sql/SqlDbContext.Query.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+using Vit.Linq;
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+using Vitorm.StreamQuery;
+
+using QueryExecutor = System.Func<Vitorm.Sql.SqlDbContext.QueryExecutorArgument, object>;
+using StreamReader = Vitorm.StreamQuery.StreamReader;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+        public Action<SqlDbContext, Expression, Type, object> AfterQuery;
+        public virtual SqlDbContext AutoDisposeAfterQuery()
+        {
+            AfterQuery += (_, _, _, _) => Dispose();
+            return this;
+        }
+
+
+
+        public override IQueryable<Entity> Query<Entity>()
+        {
+            return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
+        }
+
+        protected object QueryExecutor(Expression expression, Type expressionResultType)
+        {
+            object result = null;
+            try
+            {
+                return result = ExecuteQuery(expression, expressionResultType);
+            }
+            finally
+            {
+                AfterQuery?.Invoke(this, expression, expressionResultType, result);
+            }
+        }
+
+
+
+        #region QueryExecutor
+
+        public class QueryExecutorArgument
+        {
+            public CombinedStream combinedStream;
+            public SqlDbContext dbContext;
+
+            public Expression expression;
+            public Type expressionResultType;
+        }
+
+
+        public static Dictionary<string, QueryExecutor> defaultQueryExecutors = new Dictionary<string, QueryExecutor>()
+        {
+            #region Sync
+            [nameof(Orm_Extensions.ExecuteUpdate)] = Query_ExecuteUpdate,
+            [nameof(Orm_Extensions.ExecuteDelete)] = Query_ExecuteDelete,
+            [nameof(Orm_Extensions.ToExecuteString)] = Query_ToExecuteString,
+
+            [nameof(Queryable.Count)] = Query_Count,
+            [nameof(Queryable_Extensions.TotalCount)] = Query_Count,
+
+            [nameof(Enumerable.ToList)] = Query_ToList,
+
+            [nameof(Queryable_Extensions.ToListAndTotalCount)] = Query_ToListAndTotalCount,
+
+            [nameof(Queryable.FirstOrDefault)] = Query_First,
+            [nameof(Queryable.First)] = Query_First,
+            [nameof(Queryable.LastOrDefault)] = Query_First,
+            [nameof(Queryable.Last)] = Query_First,
+            #endregion
+
+
+            #region Async
+            [nameof(Queryable_Extensions.ToListAsync)] = Query_ToListAsync,
+
+            #endregion
+
+        };
+
+
+        public Dictionary<string, QueryExecutor> queryExecutors = defaultQueryExecutors;
+
+        #endregion
+
+
+        #region StreamReader
+        public static StreamReader defaultStreamReader = new StreamReader();
+        public StreamReader streamReader = defaultStreamReader;
+        #endregion
+
+
+
+        protected bool QueryIsFromSameDb(object query, Type elementType)
+        {
+            return dbGroupName == QueryableBuilder.GetQueryConfig(query as IQueryable) as string;
+        }
+        protected virtual object ExecuteQuery(Expression expression, Type expressionResultType)
+        {
+            // #1 convert to ExpressionNode 
+            ExpressionNode_Lambda node = convertService.ConvertToData_LambdaNode(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
+            //var strNode = Json.Serialize(node);
+
+
+            // #2 convert to Stream
+            var stream = streamReader.ReadFromNode(node);
+            //var strStream = Json.Serialize(stream);
+
+
+            // #3 Execute
+            if (stream is not CombinedStream combinedStream) combinedStream = new CombinedStream("tmp") { source = stream };
+
+            var method = combinedStream.method;
+            if (string.IsNullOrWhiteSpace(method)) method = nameof(Enumerable.ToList);
+            queryExecutors.TryGetValue(method, out var executor);
+
+            if (executor != null)
+                return executor(new QueryExecutorArgument
+                {
+                    combinedStream = combinedStream,
+                    dbContext = this,
+                    expression = expression,
+                    expressionResultType = expressionResultType
+                });
+
+            throw new NotSupportedException("not supported query method: " + combinedStream.method);
+        }
+
+
+
+
+    }
+}

+ 25 - 0
src/Vitorm/Sql/SqlDbContext.Transaction.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Data;
+
+using Vitorm.Sql.Transaction;
+
+namespace Vitorm.Sql
+{
+    public partial class SqlDbContext : DbContext
+    {
+
+        #region Transaction
+        public virtual Func<SqlDbContext, ITransactionScope> createTransactionScope { set; get; }
+                    = (dbContext) => new SqlTransactionScope(dbContext);
+        protected virtual ITransactionScope transactionScope { get; set; }
+
+        public virtual IDbTransaction BeginTransaction()
+        {
+            transactionScope ??= createTransactionScope(this);
+            return transactionScope.BeginTransaction();
+        }
+        public virtual IDbTransaction GetCurrentTransaction() => transactionScope?.GetCurrentTransaction();
+
+        #endregion
+    }
+}

+ 1 - 295
src/Vitorm/Sql/SqlDbContext.cs

@@ -1,17 +1,12 @@
 using System;
 using System.Collections.Generic;
 using System.Data;
-using System.Linq;
-using System.Linq.Expressions;
 
 using Vit.Linq;
-using Vit.Linq.ExpressionTree.ComponentModel;
 
 using Vitorm.Entity;
 using Vitorm.Sql.DataReader.EntityReader;
 using Vitorm.Sql.SqlTranslate;
-using Vitorm.Sql.Transaction;
-using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql
 {
@@ -147,229 +142,10 @@ namespace Vitorm.Sql
 
 
 
-
-        #region #2 Retrieve : Get Query
-
+        // #2 Retrieve : Get
         public override Entity Get<Entity>(object keyValue) => DbSet<Entity>().Get(keyValue);
 
 
-        public override IQueryable<Entity> Query<Entity>()
-        {
-            return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
-        }
-
-        protected bool QueryIsFromSameDb(object query, Type elementType)
-        {
-            return dbGroupName == QueryableBuilder.GetQueryConfig(query as IQueryable) as string;
-        }
-        public Action<SqlDbContext, Expression, Type, object> AfterQuery;
-        protected object QueryExecutor(Expression expression, Type expressionResultType)
-        {
-            object result = null;
-            try
-            {
-                return result = ExecuteQuery(expression, expressionResultType);
-            }
-            finally
-            {
-                AfterQuery?.Invoke(this, expression, expressionResultType, result);
-            }
-        }
-        public virtual SqlDbContext AutoDisposeAfterQuery()
-        {
-            AfterQuery += (_, _, _, _) => Dispose();
-            return this;
-        }
-
-
-        #region StreamReader
-        public static StreamReader defaultStreamReader = new StreamReader();
-        public StreamReader streamReader = defaultStreamReader;
-        #endregion
-
-        protected virtual object ExecuteQuery(Expression expression, Type expressionResultType)
-        {
-            // #1 convert to ExpressionNode 
-            ExpressionNode_Lambda node = convertService.ConvertToData_LambdaNode(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
-            //var strNode = Json.Serialize(node);
-
-
-            // #2 convert to Stream
-            var stream = streamReader.ReadFromNode(node);
-            //var strStream = Json.Serialize(stream);
-
-
-            // #3.1 ExecuteUpdate
-            if (stream is StreamToUpdate streamToUpdate)
-            {
-                // get arg
-                var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
-                var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                var sql = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
-
-                return Execute(sql: sql, param: arg.sqlParam);
-            }
-
-
-            // #3.3 Query
-            // #3.3.1
-            if (stream is not CombinedStream combinedStream) combinedStream = new CombinedStream("tmp") { source = stream };
-
-            // #3.3.2 execute and read result
-            switch (combinedStream.method)
-            {
-                case nameof(Orm_Extensions.ToExecuteString):
-                    {
-                        // ToExecuteString
-
-                        // get arg
-                        var arg = new QueryTranslateArgument(this, null);
-
-                        var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
-                        return sql;
-                    }
-                case nameof(Orm_Extensions.ExecuteDelete):
-                    {
-                        // ExecuteDelete
-
-                        // get arg
-                        var entityType = (combinedStream.source as SourceStream)?.GetEntityType();
-                        var arg = new QueryTranslateArgument(this, entityType);
-
-                        var sql = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
-                        var count = Execute(sql: sql, param: arg.sqlParam);
-                        return count;
-                    }
-                case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last):
-                    {
-                        // get arg
-                        var resultEntityType = expression.Type;
-                        var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                        var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
-                        using var reader = ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
-                        return arg.dataReader.ReadData(reader);
-                    }
-                case nameof(Queryable.Count) or nameof(Queryable_Extensions.TotalCount):
-                    {
-                        return ExecuteQuery_Count(combinedStream);
-                    }
-                case nameof(Queryable_Extensions.ToListAndTotalCount):
-                    {
-                        var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault()?.GetGenericArguments()?.FirstOrDefault();
-                        return ExecuteQuery_ToListAndTotalCount(combinedStream, resultEntityType);
-                    }
-                case nameof(Enumerable.ToList):
-                case "":
-                case null:
-                    {
-                        // ToList
-                        var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault();
-                        return ExecuteQuery_ToList(combinedStream, resultEntityType);
-                    }
-            }
-            throw new NotSupportedException("not supported query type: " + combinedStream.method);
-        }
-
-        protected bool query_ToListAndTotalCount_InvokeInOneExecute = true;
-        protected virtual object ExecuteQuery_ToListAndTotalCount(CombinedStream combinedStream, Type resultEntityType)
-        {
-            object list; int totalCount;
-
-            if (query_ToListAndTotalCount_InvokeInOneExecute)
-            {
-                // get arg
-                var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                string sqlToList, sqlCount;
-                IDbDataReader dataReader;
-                // #1 ToList
-                {
-                    combinedStream.method = nameof(Enumerable.ToList);
-                    sqlToList = sqlTranslateService.PrepareQuery(arg, combinedStream);
-                    dataReader = arg.dataReader;
-                }
-
-                // #2 TotalCount
-                {
-                    combinedStream.method = nameof(Enumerable.Count);
-                    (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
-
-                    sqlCount = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
-                }
-
-                // #3 read data
-                {
-                    var sql = sqlCount + " ;\r\n" + sqlToList;
-                    using var reader = ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
-                    reader.Read();
-                    totalCount = Convert.ToInt32(reader[0]);
-                    reader.NextResult();
-                    list = dataReader.ReadData(reader);
-                }
-            }
-            else
-            {
-                combinedStream.method = nameof(Enumerable.ToList);
-                list = ExecuteQuery_ToList(combinedStream, resultEntityType);
-
-                combinedStream.method = nameof(Queryable_Extensions.TotalCount);
-                totalCount = ExecuteQuery_Count(combinedStream);
-            }
-
-            //combinedStream.method = nameof(Queryable_Extensions.ToListAndTotalCount);
-
-            return new Func<object, int, (object, int)>(ValueTuple.Create<object, int>)
-                .Method.GetGenericMethodDefinition()
-                .MakeGenericMethod(list.GetType(), typeof(int))
-                .Invoke(null, new[] { list, totalCount });
-        }
-
-
-        /// <summary>
-        /// Queryable.Count or Queryable_Extensions.TotalCount
-        /// </summary>
-        /// <param name="combinedStream"></param>
-        /// <returns></returns>
-        protected virtual int ExecuteQuery_Count(CombinedStream combinedStream)
-        {
-            // deal with skip and take , no need to pass to PrepareCountQuery
-            var queryArg = (combinedStream.orders, combinedStream.skip, combinedStream.take);
-            (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
-
-            // get arg
-            var arg = new QueryTranslateArgument(this, null);
-
-            var sql = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
-
-            var countValue = ExecuteScalar(sql: sql, param: arg.sqlParam, useReadOnly: true);
-            var count = Convert.ToInt32(countValue);
-            if (count > 0 && combinedStream.method == nameof(Queryable.Count))
-            {
-                if (queryArg.skip > 0) count = Math.Max(count - queryArg.skip.Value, 0);
-
-                if (queryArg.take.HasValue)
-                    count = Math.Min(count, queryArg.take.Value);
-            }
-
-            (combinedStream.orders, combinedStream.skip, combinedStream.take) = queryArg;
-            return count;
-        }
-
-        protected virtual object ExecuteQuery_ToList(CombinedStream combinedStream, Type resultEntityType)
-        {
-            var arg = new QueryTranslateArgument(this, resultEntityType);
-
-            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
-
-            using var reader = ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
-            return arg.dataReader.ReadData(reader);
-        }
-
-        #endregion
-
-
 
         // #3 Update: Update UpdateRange
         public override int Update<Entity>(Entity entity) => DbSet<Entity>().Update(entity);
@@ -388,79 +164,9 @@ namespace Vitorm.Sql
 
 
 
-        #region Transaction
-        public virtual Func<SqlDbContext, ITransactionScope> createTransactionScope { set; get; }
-                    = (dbContext) => new SqlTransactionScope(dbContext);
-        protected virtual ITransactionScope transactionScope { get; set; }
 
-        public virtual IDbTransaction BeginTransaction()
-        {
-            transactionScope ??= createTransactionScope(this);
-            return transactionScope.BeginTransaction();
-        }
-        public virtual IDbTransaction GetCurrentTransaction() => transactionScope?.GetCurrentTransaction();
 
-        #endregion
-
-
-
-        #region Execute
-        protected SqlExecutor sqlExecutor;
-        public static int? defaultCommandTimeout;
-        public int? commandTimeout;
-
-        public virtual int ExecuteWithTransaction(string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null)
-        {
-            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
-
-            return sqlExecutor.Execute(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
-        }
-
-        public virtual int Execute(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
-        {
-            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
-            var transaction = GetCurrentTransaction();
-
-            if (useReadOnly && transaction == null)
-            {
-                return sqlExecutor.Execute(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
-            }
-            else
-            {
-                return sqlExecutor.Execute(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
-            }
-        }
 
-        public virtual IDataReader ExecuteReader(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
-        {
-            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
-            var transaction = GetCurrentTransaction();
-
-            if (useReadOnly && transaction == null)
-            {
-                return sqlExecutor.ExecuteReader(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
-            }
-            else
-            {
-                return sqlExecutor.ExecuteReader(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
-            }
-        }
-
-        public virtual object ExecuteScalar(string sql, IDictionary<string, object> param = null, int? commandTimeout = null, bool useReadOnly = false)
-        {
-            commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
-            var transaction = GetCurrentTransaction();
-
-            if (useReadOnly && transaction == null)
-            {
-                return sqlExecutor.ExecuteScalar(readOnlyDbConnection, sql, param: param, commandTimeout: commandTimeout);
-            }
-            else
-            {
-                return sqlExecutor.ExecuteScalar(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
-            }
-        }
-        #endregion
 
     }
 }

+ 36 - 44
src/Vitorm/Sql/SqlDbSet.cs

@@ -26,7 +26,7 @@ namespace Vitorm.Sql
     }
 
 
-    public class SqlDbSet<Entity> : DbSet<Entity>
+    public partial class SqlDbSet<Entity> : DbSet<Entity>
     {
 
         public virtual SqlDbContext sqlDbContext => (SqlDbContext)dbContext;
@@ -37,7 +37,7 @@ namespace Vitorm.Sql
 
         protected virtual ISqlTranslateService sqlTranslateService => sqlDbContext.sqlTranslateService;
 
-        #region #0 Schema :  Create Drop
+        #region #0 Schema :  Create Drop Truncate
         public override void TryCreateTable()
         {
             string sql = sqlTranslateService.PrepareTryCreateTable(entityDescriptor);
@@ -99,63 +99,55 @@ namespace Vitorm.Sql
         }
         public override void AddRange(IEnumerable<Entity> entities)
         {
-            // #0 get arg
-            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
-            List<(Entity entity, EAddType addType)> entityAndTypes = entities.Select(entity => (entity, sqlTranslateService.Entity_GetAddType(arg, entity))).ToList();
-            //if (entityAndTypes.Any(row => row.addType == EAddType.unexpectedEmptyKey)) throw new ArgumentException("Key could not be empty.");
-
 
+            SqlTranslateArgument arg = new SqlTranslateArgument(sqlDbContext, entityDescriptor);
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) sql_IdentityKey = default;
+            (string sql, Func<object, Dictionary<string, object>> GetSqlParams) sql_Others = default;
             var affectedRowCount = 0;
 
-            // #2 keyWithValue
+            foreach (var entity in entities)
             {
-                var rows = entityAndTypes.Where(row => row.addType == EAddType.keyWithValue);
-                if (rows.Any())
+                var addType = sqlTranslateService.Entity_GetAddType(arg, entity);
+                //if (addType == EAddType.unexpectedEmptyKey) throw new ArgumentException("Key could not be empty.");
+
+                if (addType == EAddType.identityKey)
                 {
-                    // ##1 prepare sql
-                    (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareAdd(arg);
+                    // #1 prepare sql
+                    if (sql_IdentityKey == default)
+                        sql_IdentityKey = sqlTranslateService.PrepareIdentityAdd(arg);
 
-                    foreach ((var entity, _) in rows)
-                    {
-                        // #2 get sql params
-                        var sqlParam = GetSqlParams(entity);
+                    // #2 get sql params
+                    var sqlParam = sql_IdentityKey.GetSqlParams(entity);
 
-                        // #3 add
-                        sqlDbContext.Execute(sql: sql, param: sqlParam);
-                        affectedRowCount++;
-                    }
-                }
-            }
+                    // #3 add
+                    var newKeyValue = sqlDbContext.ExecuteScalar(sql: sql_IdentityKey.sql, param: sqlParam);
 
-            // #3 identityKey
-            {
-                var rows = entityAndTypes.Where(row => row.addType == EAddType.identityKey);
-                if (rows.Any())
-                {
+                    // #4 set key value to entity
                     var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
-
-                    // ##1 prepare sql
-                    (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareIdentityAdd(arg);
-
-                    foreach ((var entity, _) in rows)
+                    newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
+                    if (newKeyValue != null)
                     {
-                        // ##2 get sql params
-                        var sqlParam = GetSqlParams(entity);
+                        entityDescriptor.key.SetValue(entity, newKeyValue);
+                    }
 
-                        // ##3 add
-                        var newKeyValue = sqlDbContext.ExecuteScalar(sql: sql, param: sqlParam);
+                    affectedRowCount++;
+                }
+                else
+                {
+                    // #1 prepare sql
+                    if (sql_Others == default)
+                        sql_Others = sqlTranslateService.PrepareAdd(arg);
 
-                        // ##4 set key value to entity
-                        newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
-                        if (newKeyValue != null)
-                        {
-                            entityDescriptor.key.SetValue(entity, newKeyValue);
-                        }
+                    // #2 get sql params
+                    var sqlParam = sql_Others.GetSqlParams(entity);
 
-                        affectedRowCount++;
-                    }
+                    // #3 add
+                    sqlDbContext.Execute(sql: sql_Others.sql, param: sqlParam);
+
+                    affectedRowCount++;
                 }
             }
+
         }
         #endregion
 

+ 1 - 3
src/Vitorm/Sql/SqlExecutor.cs

@@ -4,7 +4,7 @@ using System.Data;
 
 namespace Vitorm.Sql
 {
-    public class SqlExecutor
+    public partial class SqlExecutor
     {
         public readonly static SqlExecutor Instance = new SqlExecutor();
 
@@ -59,7 +59,6 @@ namespace Vitorm.Sql
 
         public virtual IDataReader ExecuteReader(IDbConnection conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
         {
-
             IDbCommand cmd = null;
 
             bool wasClosed = conn.State == ConnectionState.Closed, disposeCommand = true;
@@ -91,7 +90,6 @@ namespace Vitorm.Sql
                     cmd.Dispose();
                 }
             }
-
         }
 
 

+ 2 - 2
src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs

@@ -40,9 +40,9 @@ namespace Vitorm.Sql.SqlTranslate
                 case nameof(Queryable.Count):
                 case "" or null or nameof(Enumerable.ToList) or nameof(Orm_Extensions.ToExecuteString):
                 case nameof(Queryable_Extensions.ToListAndTotalCount) or nameof(Queryable_Extensions.TotalCount):
+                case nameof(Queryable_Extensions.ToListAsync):
                     {
-                        var reader = new DataReader.DataReader();
-                        return prefix + " " + BuildDataReader(arg, stream, reader);
+                        return prefix + " " + BuildDataReader(arg, stream, new DataReader.DataReader());
                     }
                 case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last):
                     {

+ 1 - 0
src/Vitorm/StreamQuery/StreamToUpdate.cs

@@ -24,6 +24,7 @@ namespace Vitorm.StreamQuery
             {
                 base.source = source;
             }
+            method = nameof(Orm_Extensions.ExecuteUpdate);
         }
 
         // ExpressionNode_New   new { name = name + "_" }

+ 172 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/CRUDAsync_Test.cs

@@ -0,0 +1,172 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public partial class CRUDAsync_Test
+    {
+        static DbContext CreateDbContext() => DataSource.CreateDbContextForWriting();
+
+        #region #0 Schema
+        [TestMethod]
+        public async Task Test_Schema()
+        {
+            using var dbContext = CreateDbContext();
+
+            await dbContext.TryDropTableAsync<User>();
+            await dbContext.TryDropTableAsync<User>();
+
+            await dbContext.TryCreateTableAsync<User>();
+            await dbContext.TryCreateTableAsync<User>();
+
+            await dbContext.TruncateAsync<User>();
+        }
+        #endregion
+
+
+        #region #1 Create
+        [TestMethod]
+        public async Task Test_Create()
+        {
+            using var dbContext = CreateDbContext();
+
+            var newUserList = User.NewUsers(7, 4, forAdd: true);
+
+
+            // #1 Add
+            await dbContext.AddAsync(newUserList[0]);
+
+            // #2 AddRange
+            await dbContext.AddRangeAsync(newUserList.Skip(1));
+
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().Where(user => user.id >= 7).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+            try
+            {
+                await dbContext.AddAsync(newUserList[0]);
+                Assert.Fail("should not be able to add same key twice");
+            }
+            catch (Exception ex) when (ex is not AssertFailedException)
+            {
+            }
+
+        }
+        #endregion
+
+        #region #2 Retrieve : Get Query
+        [TestMethod]
+        public async Task Test_Retrieve()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Get
+            {
+                var user = await dbContext.GetAsync<User>(1);
+                Assert.AreEqual(1, user.id);
+            }
+
+            // #2 Query
+            {
+                var userList = await dbContext.Query<User>().ToListAsync();
+                Assert.AreEqual(6, userList.Count());
+            }
+        }
+        #endregion
+
+
+        #region #3 Update
+        [TestMethod]
+        public async Task Test_Update()
+        {
+            using var dbContext = CreateDbContext();
+
+            // Update
+            {
+                var rowCount = await dbContext.UpdateAsync(User.NewUser(4));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // UpdateRange
+            {
+                var rowCount = await dbContext.UpdateRangeAsync(User.NewUsers(5, 3));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var newUserList = User.NewUsers(4, 3, forAdd: false);
+                var userList = dbContext.Query<User>().Where(m => m.id >= 4).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+        }
+        #endregion
+
+
+        #region #4 Delete
+        [TestMethod]
+        public async Task Test_Delete()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Delete
+            {
+                var rowCount = await dbContext.DeleteAsync(User.NewUser(1));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #2 DeleteRange
+            {
+                var rowCount = await dbContext.DeleteRangeAsync(User.NewUsers(2, 2));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            // #3 DeleteByKey
+            {
+                var user = User.NewUser(4);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValue = key.GetValue(user);
+                var rowCount = await dbContext.DeleteByKeyAsync<User>(keyValue);
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #4 DeleteByKeys
+            {
+                var users = User.NewUsers(5, 2);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValues = users.Select(user => key.GetValue(user));
+                var rowCount = await dbContext.DeleteByKeysAsync<User, object>(keyValues);
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().ToList();
+                Assert.AreEqual(0, userList.Count());
+            }
+        }
+        #endregion
+
+
+    }
+}

+ 34 - 4
test/Vitorm.Sqlite.MsTest/CommonTest/CRUD_Test.cs

@@ -10,10 +10,23 @@ namespace Vitorm.MsTest.CommonTest
     {
         static DbContext CreateDbContext() => DataSource.CreateDbContextForWriting();
 
+        #region #0 Schema
+        [TestMethod]
+        public void Test_Schema()
+        {
+            using var dbContext = CreateDbContext();
 
+            dbContext.TryDropTable<User>();
+            dbContext.TryDropTable<User>();
 
-        #region #1 Create
+            dbContext.TryCreateTable<User>();
+            dbContext.TryCreateTable<User>();
+
+            dbContext.Truncate<User>();
+        }
+        #endregion
 
+        #region #1 Create
         [TestMethod]
         public void Test_Create()
         {
@@ -52,9 +65,28 @@ namespace Vitorm.MsTest.CommonTest
         }
         #endregion
 
+        #region #2 Retrieve : Get Query
+        [TestMethod]
+        public void Test_Retrieve()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Get
+            {
+                var user = dbContext.Get<User>(1);
+                Assert.AreEqual(1, user.id);
+            }
+
+            // #2 Query
+            {
+                var userList = dbContext.Query<User>().ToList();
+                Assert.AreEqual(6, userList.Count());
+            }
+        }
+        #endregion
 
-        #region #3 Update
 
+        #region #3 Update
         [TestMethod]
         public void Test_Update()
         {
@@ -88,8 +120,6 @@ namespace Vitorm.MsTest.CommonTest
 
 
         #region #4 Delete
-
-
         [TestMethod]
         public void Test_Delete()
         {