Преглед изворни кода

- support Async IQueryable Methods
- ToListAsync
- CountAsync TotalCountAsync
- ToListAndTotalCountAsync
- ExecuteDeleteAsync
- ExecuteUpdateAsync
- FirstOrDefaultAsync FirstAsync LastOrDefaultAsync LastAsync

Lith пре 9 месеци
родитељ
комит
4dc571b81d
63 измењених фајлова са 1582 додато и 395 уклоњено
  1. 15 2
      doc/ReleaseLog.md
  2. 12 15
      src/Vitorm/Async/DbSet.Async.cs
  3. 12 13
      src/Vitorm/Async/Sql/SqlDbContext.Async.cs
  4. 0 2
      src/Vitorm/Async/Sql/SqlDbContext.ExecuteAsync.cs
  5. 1 1
      src/Vitorm/DbFunction.cs
  6. 34 0
      src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.CountAsync.cs
  7. 93 0
      src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.FirstOrDefaultAsync.cs
  8. 35 0
      src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.ToListAndTotalCountAsync.cs
  9. 34 0
      src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.TotalCountAsync.cs
  10. 6 0
      src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteDelete.cs
  11. 35 0
      src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteDeleteAsync.cs
  12. 51 1
      src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteUpdate.cs
  13. 31 0
      src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteUpdateAsync.cs
  14. 5 0
      src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ToExecuteString.cs
  15. 16 0
      src/Vitorm/Sql/Extensions/CombinedStream_Extensions_QueryExecutor.cs
  16. 56 0
      src/Vitorm/Sql/QueryExecutor/Async/CountAsync.cs
  17. 35 0
      src/Vitorm/Sql/QueryExecutor/Async/ExecuteDeleteAsync.cs
  18. 38 0
      src/Vitorm/Sql/QueryExecutor/Async/ExecuteUpdateAsync.cs
  19. 50 0
      src/Vitorm/Sql/QueryExecutor/Async/FirstOrDefaultAsync.cs
  20. 95 0
      src/Vitorm/Sql/QueryExecutor/Async/ToListAndTotalCountAsync.cs
  21. 50 0
      src/Vitorm/Sql/QueryExecutor/Async/ToListAsync.cs
  22. 8 0
      src/Vitorm/Sql/QueryExecutor/IQueryExecutor.cs
  23. 17 0
      src/Vitorm/Sql/QueryExecutor/QueryExecutorArgument.cs
  24. 17 0
      src/Vitorm/Sql/QueryExecutor/QueryExecutor_Delegate.cs
  25. 19 15
      src/Vitorm/Sql/QueryExecutor/Sync/Count.cs
  26. 10 7
      src/Vitorm/Sql/QueryExecutor/Sync/ExecuteDelete.cs
  27. 8 7
      src/Vitorm/Sql/QueryExecutor/Sync/ExecuteUpdate.cs
  28. 11 4
      src/Vitorm/Sql/QueryExecutor/Sync/FirstOrDefault.cs
  29. 24 0
      src/Vitorm/Sql/QueryExecutor/Sync/ToExecuteString.cs
  30. 9 5
      src/Vitorm/Sql/QueryExecutor/Sync/ToList.cs
  31. 13 11
      src/Vitorm/Sql/QueryExecutor/Sync/ToListAndTotalCount.cs
  32. 0 0
      src/Vitorm/Sql/SqlDbContext.Database.Execute.cs
  33. 0 0
      src/Vitorm/Sql/SqlDbContext.Database.Transaction.cs
  34. 0 42
      src/Vitorm/Sql/SqlDbContext.Query.Async/SqlDbContext.Query.ToListAsync.cs
  35. 0 22
      src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToExecuteString.cs
  36. 75 42
      src/Vitorm/Sql/SqlDbContext.Query.cs
  37. 32 0
      src/Vitorm/StreamQuery/Attribute/StreamQuery_CustomMethodAttribute.cs
  38. 13 0
      src/Vitorm/StreamQuery/ComponentModel/CombinedStream.cs
  39. 0 0
      src/Vitorm/StreamQuery/ComponentModel/EJoinType.cs
  40. 0 0
      src/Vitorm/StreamQuery/ComponentModel/IStream.cs
  41. 0 0
      src/Vitorm/StreamQuery/ComponentModel/SourceStream.cs
  42. 0 0
      src/Vitorm/StreamQuery/ComponentModel/StreamToJoin.cs
  43. 2 2
      src/Vitorm/StreamQuery/ComponentModel/StreamToUpdate.cs
  44. 26 0
      src/Vitorm/StreamQuery/ExpressionNode_RenameableMember.cs
  45. 7 0
      src/Vitorm/StreamQuery/MethodCall/IMethodConvertor.cs
  46. 2 2
      src/Vitorm/StreamQuery/MethodCall/MethodCallConvertArgrument.cs
  47. 12 0
      src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_Delegate.cs
  48. 4 5
      src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_ExecuteEnd.cs
  49. 0 41
      src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_ExecuteUpdate.cs
  50. 22 0
      src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_FromAttribute.cs
  51. 1 1
      src/Vitorm/StreamQuery/StreamReader.GroupBy.cs
  52. 1 1
      src/Vitorm/StreamQuery/StreamReader.Join.cs
  53. 11 4
      src/Vitorm/StreamQuery/StreamReader.MethodCallConvertor.cs
  54. 2 2
      src/Vitorm/StreamQuery/StreamReader.SelectMany.cs
  55. 12 137
      src/Vitorm/StreamQuery/StreamReader.cs
  56. 110 0
      src/Vitorm/StreamQuery/StreamReaderArgument.cs
  57. 1 1
      test/Vitorm.MsTest/StreamQuery/Queryable_Extensions_Batch.cs
  58. 54 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteDeleteAsync_Test.cs
  59. 0 2
      test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteDelete_Test.cs
  60. 78 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteUpdateAsync_Test.cs
  61. 0 2
      test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteUpdate_Test.cs
  62. 235 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_AsyncMethods_Test.cs
  63. 42 6
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Test.cs

+ 15 - 2
doc/ReleaseLog.md

@@ -1,12 +1,25 @@
 # Vitorm ReleaseLog
 
-
+-----------------------
+# 2.0.5
+- support Async Methods
+  - AddAsync AddRangeAsync
+  - GetAsync
+  - UpdateAsync UpdateRangeAsync
+  - DeleteAsync DeleteRangeAsync DeleteByKeyAsync DeleteByKeysAsync
+
+- support Async IQueryable Methods
+  - ToListAsync
+  - CountAsync TotalCountAsync
+  - ToListAndTotalCountAsync
+  - ExecuteDeleteAsync
+  - ExecuteUpdateAsync
+  - FirstOrDefaultAsync FirstAsync LastOrDefaultAsync LastAsync
 
 -----------------------
 # 2.0.4
 - [Vitorm] support Truncate
 
-
 -----------------------
 # 2.0.3
 - [Vitorm] StreamReader support custom methodCallConvertor and add test case

+ 12 - 15
src/Vitorm/Async/DbSet.Async.cs

@@ -3,36 +3,33 @@ 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>();
+        public virtual Task TryCreateTableAsync() => dbContext.TryCreateTableAsync<Entity>();
+        public virtual Task TryDropTableAsync() => dbContext.TryDropTableAsync<Entity>();
+        public virtual Task TruncateAsync() => 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);
+        public virtual Task<Entity> AddAsync(Entity entity) => dbContext.AddAsync<Entity>(entity);
+        public virtual Task AddRangeAsync(IEnumerable<Entity> entities) => 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>();
+        public virtual Task<Entity> GetAsync(object keyValue) => dbContext.GetAsync<Entity>(keyValue);
 
 
         // #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);
+        public virtual Task<int> UpdateAsync(Entity entity) => dbContext.UpdateAsync<Entity>(entity);
+        public virtual Task<int> UpdateRangeAsync(IEnumerable<Entity> entities) => 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);
+        public virtual Task<int> DeleteAsync(Entity entity) => dbContext.DeleteAsync<Entity>(entity);
+        public virtual Task<int> DeleteRangeAsync(IEnumerable<Entity> entities) => dbContext.DeleteRangeAsync<Entity>(entities);
+        public virtual Task<int> DeleteByKeyAsync(object keyValue) => dbContext.DeleteByKeyAsync<Entity>(keyValue);
+        public virtual Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys) => dbContext.DeleteByKeysAsync<Entity, Key>(keys);
 
 
     }

+ 12 - 13
src/Vitorm/Async/Sql/SqlDbContext.Async.cs

@@ -7,34 +7,33 @@ namespace Vitorm.Sql
     {
 
         // #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();
+        public override Task TryCreateTableAsync<Entity>() => DbSet<Entity>().TryCreateTableAsync();
+        public override Task TryDropTableAsync<Entity>() => DbSet<Entity>().TryDropTableAsync();
+        public override Task TruncateAsync<Entity>() => 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);
+        public override Task<Entity> AddAsync<Entity>(Entity entity) => DbSet<Entity>().AddAsync(entity);
+        public override Task AddRangeAsync<Entity>(IEnumerable<Entity> entities) => 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();
+        public override Task<Entity> GetAsync<Entity>(object keyValue) => DbSet<Entity>().GetAsync(keyValue);
 
 
         // #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);
+        public override Task<int> UpdateAsync<Entity>(Entity entity) => DbSet<Entity>().UpdateAsync(entity);
+        public override Task<int> UpdateRangeAsync<Entity>(IEnumerable<Entity> entities) => 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 Task<int> DeleteAsync<Entity>(Entity entity) => DbSet<Entity>().DeleteAsync(entity);
+        public override Task<int> DeleteRangeAsync<Entity>(IEnumerable<Entity> entities) => 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);
+        public override Task<int> DeleteByKeyAsync<Entity>(object keyValue) => DbSet<Entity>().DeleteByKeyAsync(keyValue);
+        public override Task<int> DeleteByKeysAsync<Entity, Key>(IEnumerable<Key> keys) => DbSet<Entity>().DeleteByKeysAsync<Key>(keys);
 
 
 

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

@@ -7,7 +7,6 @@ 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;
@@ -59,7 +58,6 @@ namespace Vitorm.Sql
                 return await sqlExecutor.ExecuteScalarAsync(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
             }
         }
-        #endregion
 
 
 

+ 1 - 1
src/Vitorm/DbFunction.cs

@@ -5,7 +5,7 @@ using Vit.Linq.ExpressionNodes;
 namespace Vitorm
 {
 
-    [DataValueType(EDataValueType.other)]
+    [ExpressionNode_DataValueType(EDataValueType.other)]
     public static partial class DbFunction
     {
         public static Return Call<Return>(string functionName) => throw new NotImplementedException();

+ 34 - 0
src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.CountAsync.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+
+    public static partial class Queryable_AsyncExtensions
+    {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<int> CountAsync<T>(this IQueryable<T> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<int>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<T>, Task<int>>(CountAsync<T>).Method
+                    , source.Expression));
+        }
+    }
+
+
+
+
+
+}

+ 93 - 0
src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.FirstOrDefaultAsync.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+
+    public static partial class Queryable_AsyncExtensions
+    {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<TSource> FirstOrDefaultAsync<TSource>(this IQueryable<TSource> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<TSource>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<TSource>, Task<TSource>>(FirstOrDefaultAsync<TSource>).Method
+                    , source.Expression));
+        }
+        public static Task<TSource> FirstOrDefaultAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
+            => source?.Where(predicate).FirstOrDefaultAsync();
+
+
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<TSource> FirstAsync<TSource>(this IQueryable<TSource> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<TSource>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<TSource>, Task<TSource>>(FirstAsync<TSource>).Method
+                    , source.Expression));
+        }
+        public static Task<TSource> FirstAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
+            => source?.Where(predicate).FirstAsync();
+
+
+
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<TSource> LastOrDefaultAsync<TSource>(this IQueryable<TSource> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<TSource>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<TSource>, Task<TSource>>(LastOrDefaultAsync<TSource>).Method
+                    , source.Expression));
+        }
+        public static Task<TSource> LastOrDefaultAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
+            => source?.Where(predicate).LastOrDefaultAsync();
+
+
+
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<TSource> LastAsync<TSource>(this IQueryable<TSource> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<TSource>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<TSource>, Task<TSource>>(LastAsync<TSource>).Method
+                    , source.Expression));
+        }
+        public static Task<TSource> LastAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
+            => source?.Where(predicate).LastAsync();
+
+
+
+
+    }
+
+
+
+
+
+}

+ 35 - 0
src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.ToListAndTotalCountAsync.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+
+    public static partial class Queryable_AsyncExtensions
+    {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<(List<TSource> list, int totalCount)> ToListAndTotalCountAsync<TSource>(this IQueryable<TSource> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<(List<TSource> list, int totalCount)>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<TSource>, Task<(List<TSource> list, int totalCount)>>(ToListAndTotalCountAsync<TSource>).Method
+                    , source.Expression));
+        }
+    }
+
+
+
+
+
+}

+ 34 - 0
src/Vitorm/Extensions/Async/Queryable_AsyncExtensions.TotalCountAsync.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+
+    public static partial class Queryable_AsyncExtensions
+    {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<int> TotalCountAsync<T>(this IQueryable<T> source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<int>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<T>, Task<int>>(TotalCountAsync<T>).Method
+                    , source.Expression));
+        }
+    }
+
+
+
+
+
+}

+ 6 - 0
src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteDelete.cs

@@ -2,6 +2,10 @@
 using System.Linq;
 using System.Linq.Expressions;
 
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
 namespace Vitorm
 {
 
@@ -13,6 +17,8 @@ namespace Vitorm
         /// <param name="source"></param>
         /// <returns></returns>
         /// <exception cref="ArgumentNullException"></exception>
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
         public static int ExecuteDelete(this IQueryable source)
         {
             if (source == null)

+ 35 - 0
src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteDeleteAsync.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+
+    public static partial class Orm_Extensions
+    {
+        /// <summary>
+        /// delete from first collection if joined multiple collections
+        /// </summary>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
+        public static Task<int> ExecuteDeleteAsync(this IQueryable source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<int>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable, Task<int>>(ExecuteDeleteAsync).Method
+                    , source.Expression));
+        }
+    }
+}

+ 51 - 1
src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteUpdate.cs

@@ -2,11 +2,18 @@
 using System.Linq;
 using System.Linq.Expressions;
 
+using Vit.Linq.ExpressionNodes.ComponentModel;
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+using Vitorm.StreamQuery;
+using Vitorm.StreamQuery.MethodCall;
+
 namespace Vitorm
 {
-
     public static partial class Orm_Extensions
     {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_MethodConvertor_ExecuteUpdate]
         public static int ExecuteUpdate<Entity, EntityToUpdate>(this IQueryable<Entity> source, Expression<Func<Entity, EntityToUpdate>> update)
         {
             if (source == null)
@@ -19,5 +26,48 @@ namespace Vitorm
                     , source.Expression
                     , update));
         }
+
     }
+
+    /// <summary>
+    /// Mark this method to be able to convert to IStream from ExpressionNode when executing query. For example : query.ToListAsync() 
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+    public class StreamQuery_MethodConvertor_ExecuteUpdateAttribute : Attribute, Vitorm.StreamQuery.MethodCall.IMethodConvertor
+    {
+        public IStream Convert(MethodCallConvertArgrument methodConvertArg)
+        {
+            ExpressionNode_MethodCall call = methodConvertArg.node;
+            var reader = methodConvertArg.reader;
+            var arg = methodConvertArg.arg;
+
+            var source = reader.ReadStream(arg, call.arguments[0]);
+            ExpressionNode_Lambda resultSelector = call.arguments[1];
+            switch (source)
+            {
+                case SourceStream sourceStream:
+                    {
+                        var parameterName = resultSelector.parameterNames[0];
+                        var parameterValue = ExpressionNode_RenameableMember.Member(stream: sourceStream, resultSelector.Lambda_GetParamTypes()[0]);
+
+                        var select = reader.ReadResultSelector(arg.WithParameter(parameterName, parameterValue), resultSelector);
+                        return new StreamToUpdate(sourceStream, call.methodName) { fieldsToUpdate = select.fields };
+                    }
+                case CombinedStream combinedStream:
+                    {
+                        var parameterName = resultSelector.parameterNames[0];
+                        var parameterValue = combinedStream.select.fields;
+                        var select = reader.ReadResultSelector(arg.WithParameter(parameterName, parameterValue), resultSelector);
+
+                        return new StreamToUpdate(source, call.methodName) { fieldsToUpdate = select.fields };
+                    }
+            }
+
+            return null;
+
+        }
+    }
+
+
+
 }

+ 31 - 0
src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ExecuteUpdateAsync.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+
+namespace Vitorm
+{
+    public static partial class Orm_Extensions
+    {
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_MethodConvertor_ExecuteUpdate]
+        public static Task<int> ExecuteUpdateAsync<Entity, EntityToUpdate>(this IQueryable<Entity> source, Expression<Func<Entity, EntityToUpdate>> update)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<Task<int>>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<Entity>, Expression<Func<Entity, EntityToUpdate>>, Task<int>>(ExecuteUpdateAsync).Method
+                    , source.Expression
+                    , update));
+        }
+
+    }
+
+
+
+}

+ 5 - 0
src/Vitorm/Extensions/Orm_Extensions/Orm_Extensions_ToExecuteString.cs

@@ -2,6 +2,9 @@
 using System.Linq;
 using System.Linq.Expressions;
 
+using Vit.Linq.ExpressionNodes.ExpressionConvertor.MethodCalls;
+using Vitorm.StreamQuery;
+
 namespace Vitorm
 {
 
@@ -13,6 +16,8 @@ namespace Vitorm
         /// <param name="source"></param>
         /// <returns></returns>
         /// <exception cref="ArgumentNullException"></exception>
+        [ExpressionNode_CustomMethod]
+        [StreamQuery_CustomMethod]
         public static string ToExecuteString(this IQueryable source)
         {
             if (source == null)

+ 16 - 0
src/Vitorm/Sql/Extensions/CombinedStream_Extensions_QueryExecutor.cs

@@ -0,0 +1,16 @@
+using Vitorm.Sql;
+using Vitorm.StreamQuery;
+
+namespace Vitorm
+{
+    public static partial class CombinedStream_Extensions_QueryExecutor
+    {
+        public static IQueryExecutor GetQueryExecutor(this CombinedStream data)
+            => data?.GetExtraArg("QueryExecutor") as IQueryExecutor;
+
+        public static CombinedStream SetQueryExecutor(this CombinedStream data, IQueryExecutor queryExecutor)
+            => data?.SetExtraArg("QueryExecutor", queryExecutor);
+
+    }
+
+}

+ 56 - 0
src/Vitorm/Sql/QueryExecutor/Async/CountAsync.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    /// <summary>
+    /// Queryable.Count or Queryable_Extensions.TotalCount
+    /// </summary>
+    public partial class CountAsync : IQueryExecutor
+    {
+        public static readonly CountAsync Instance = new();
+
+        public string methodName => nameof(Queryable_AsyncExtensions.CountAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg) => Execute(execArg);
+
+
+        public static async Task<int> Execute(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+            var queryArg = (combinedStream.orders, combinedStream.skip, combinedStream.take);
+
+            // #2 Prepare sql
+            // deal with skip and take , no need to pass to PrepareCountQuery
+            var arg = new QueryTranslateArgument(dbContext, null);
+            (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
+            string method = combinedStream.method;
+            combinedStream.method = nameof(Queryable.Count);
+            var sql = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
+            combinedStream.method = method;
+
+            // #3 Execute
+            var countValue = await dbContext.ExecuteScalarAsync(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            var count = Convert.ToInt32(countValue);
+
+            // Count and TotalCount
+            if (count > 0 && method == nameof(Queryable_AsyncExtensions.CountAsync))
+            {
+                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;
+        }
+
+    }
+}

+ 35 - 0
src/Vitorm/Sql/QueryExecutor/Async/ExecuteDeleteAsync.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql.QueryExecutor
+{
+    public partial class ExecuteDeleteAsync : IQueryExecutor
+    {
+        public static readonly ExecuteDeleteAsync Instance = new();
+
+        public string methodName => nameof(Orm_Extensions.ExecuteDeleteAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg) => Execute(execArg);
+
+        public async Task<int> Execute(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // #2 Prepare sql
+            var entityType = (combinedStream.source as SourceStream)?.GetEntityType();
+            var arg = new QueryTranslateArgument(dbContext, entityType);
+            string method = combinedStream.method;
+            combinedStream.method = nameof(Orm_Extensions.ExecuteDelete);
+            var sql = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
+            combinedStream.method = method;
+
+            // #3 Execute
+            return await dbContext.ExecuteAsync(sql: sql, param: arg.sqlParam);
+        }
+    }
+}

+ 38 - 0
src/Vitorm/Sql/QueryExecutor/Async/ExecuteUpdateAsync.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql.QueryExecutor
+{
+    public partial class ExecuteUpdateAsync : IQueryExecutor
+    {
+        public static readonly ExecuteUpdateAsync Instance = new();
+
+        public string methodName => nameof(Orm_Extensions.ExecuteUpdateAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg) => Execute(execArg);
+
+        public async Task<int> Execute(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;
+
+            // #2 Prepare sql
+            var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+            string method = combinedStream.method;
+            combinedStream.method = nameof(Orm_Extensions.ExecuteDelete);
+            var sql = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
+            combinedStream.method = method;
+
+            // #3 Execute
+            return await dbContext.ExecuteAsync(sql: sql, param: arg.sqlParam);
+        }
+    }
+}

+ 50 - 0
src/Vitorm/Sql/QueryExecutor/Async/FirstOrDefaultAsync.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class FirstOrDefaultAsync : IQueryExecutor
+    {
+        public static readonly FirstOrDefaultAsync Instance = new();
+
+        public string methodName => nameof(Queryable_AsyncExtensions.FirstOrDefaultAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
+        {
+            var resultEntityType = execArg.expression.Type.GetGenericArguments().FirstOrDefault();
+            return Execute_MethodInfo(resultEntityType).Invoke(null, new object[] { execArg });
+        }
+
+        public static async Task<Result> Execute<Result>(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // #2 Prepare sql
+            var resultEntityType = typeof(Result);
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+
+            var method = combinedStream.method;
+            combinedStream.method = method.Substring(0, method.Length - "Async".Length);
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+            combinedStream.method = method;
+
+            // #3 Execute
+            using var reader = await dbContext.ExecuteReaderAsync(sql: sql, param: arg.sqlParam, useReadOnly: true);
+
+            return (Result)arg.dataReader.ReadData(reader);
+        }
+
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<string>>(Execute<string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType);
+
+    }
+}

+ 95 - 0
src/Vitorm/Sql/QueryExecutor/Async/ToListAndTotalCountAsync.cs

@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using Vit.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class ToListAndTotalCountAsync : IQueryExecutor
+    {
+        public static readonly ToListAndTotalCountAsync Instance = new();
+
+        public string methodName => nameof(Queryable_AsyncExtensions.ToListAndTotalCountAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
+        {
+            // Task<(List<Result> list, int totalCount)>
+            var resultEntityType = execArg.expression.Type.GetGenericArguments().FirstOrDefault().GetGenericArguments().FirstOrDefault().GetGenericArguments().FirstOrDefault();
+            return Execute_MethodInfo(resultEntityType).Invoke(null, new object[] { execArg });
+        }
+
+
+        static async Task<(List<Result> list, int totalCount)> Execute<Result>(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            var originMethod = combinedStream.method;
+
+            List<Result> list; int totalCount;
+
+            if (dbContext.query_ToListAndTotalCount_InvokeInOneExecute)
+            {
+                var resultEntityType = typeof(Result);
+                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 = await dbContext.ExecuteReaderAsync(sql: sql, param: arg.sqlParam, useReadOnly: true);
+                    reader.Read();
+                    totalCount = Convert.ToInt32(reader[0]);
+                    reader.NextResult();
+                    list = (List<Result>)dataReader.ReadData(reader);
+                }
+            }
+            else
+            {
+                combinedStream.method = nameof(Enumerable.ToList);
+                list = await ToListAsync.Execute<Result>(execArg);
+
+                combinedStream.method = nameof(Queryable_Extensions.TotalCount);
+                totalCount = await CountAsync.Execute(execArg);
+            }
+
+            combinedStream.method = originMethod;
+
+            return (list, totalCount);
+        }
+
+
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<(List<string> list, int totalCount)>>(Execute<string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType);
+
+    }
+}

+ 50 - 0
src/Vitorm/Sql/QueryExecutor/Async/ToListAsync.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using Vit.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public partial class ToListAsync : IQueryExecutor
+    {
+        public static readonly ToListAsync Instance = new();
+
+        public string methodName => nameof(Queryable_Extensions.ToListAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
+        {
+            var resultEntityType = execArg.expression.Type.GetGenericArguments().FirstOrDefault().GetGenericArguments().FirstOrDefault();
+            return Execute_MethodInfo(resultEntityType).Invoke(null, new object[] { execArg });
+        }
+
+
+        public static async Task<List<Result>> Execute<Result>(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // #2 Prepare sql
+            var resultEntityType = typeof(Result);
+            var arg = new QueryTranslateArgument(dbContext, resultEntityType);
+            var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+            // #3 Execute
+            using var reader = await dbContext.ExecuteReaderAsync(sql: sql, param: arg.sqlParam, useReadOnly: true);
+            return (List<Result>)arg.dataReader.ReadData(reader);
+        }
+
+
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<List<string>>>(Execute<string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType);
+
+    }
+}

+ 8 - 0
src/Vitorm/Sql/QueryExecutor/IQueryExecutor.cs

@@ -0,0 +1,8 @@
+namespace Vitorm.Sql
+{
+    public interface IQueryExecutor
+    {
+        public string methodName { get; }
+        object ExecuteQuery(QueryExecutorArgument execArg);
+    }
+}

+ 17 - 0
src/Vitorm/Sql/QueryExecutor/QueryExecutorArgument.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Linq.Expressions;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql
+{
+    public class QueryExecutorArgument
+    {
+        public CombinedStream combinedStream;
+        public SqlDbContext dbContext;
+
+        public Expression expression;
+        public Type expressionResultType;
+    }
+
+}

+ 17 - 0
src/Vitorm/Sql/QueryExecutor/QueryExecutor_Delegate.cs

@@ -0,0 +1,17 @@
+
+using FuncQueryExecutor = System.Func<Vitorm.Sql.QueryExecutorArgument, object>;
+
+namespace Vitorm.Sql
+{
+    public class QueryExecutor_Delegate : IQueryExecutor
+    {
+        public QueryExecutor_Delegate(string methodName, FuncQueryExecutor queryExecutor)
+        {
+            this.methodName = methodName;
+            this.queryExecutor = queryExecutor;
+        }
+        public string methodName { get; set; }
+        FuncQueryExecutor queryExecutor;
+        public object ExecuteQuery(QueryExecutorArgument execArg) => queryExecutor(execArg);
+    }
+}

+ 19 - 15
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.Count.cs → src/Vitorm/Sql/QueryExecutor/Sync/Count.cs

@@ -6,37 +6,41 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql
 {
-    public partial class SqlDbContext : DbContext
+    /// <summary>
+    /// Queryable.Count or Queryable_Extensions.TotalCount
+    /// </summary>
+    public partial class Count : IQueryExecutor
     {
-        /// <summary>
-        /// Queryable.Count or Queryable_Extensions.TotalCount
-        /// </summary>
-        /// <param name="execArg"></param>
-        /// <returns></returns>
-        static object Query_Count(QueryExecutorArgument execArg)
+        public static readonly Count Instance = new();
+
+        public string methodName => nameof(Queryable.Count);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
         {
-            return ExecuteQuery_Count(execArg);
+            return Execute(execArg);
         }
 
 
-        static int ExecuteQuery_Count(QueryExecutorArgument execArg)
+        public static int Execute(QueryExecutorArgument execArg)
         {
             CombinedStream combinedStream = execArg.combinedStream;
             var dbContext = execArg.dbContext;
             var sqlTranslateService = dbContext.sqlTranslateService;
 
+            // #2 Prepare sql
             // 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);
+            var queryArg = (combinedStream.orders, combinedStream.skip, combinedStream.take, combinedStream.method);
 
-            // get arg
+            (combinedStream.orders, combinedStream.skip, combinedStream.take, combinedStream.method) = (null, null, null, nameof(Queryable.Count));
             var arg = new QueryTranslateArgument(dbContext, null);
-
             var sql = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
 
+            // #3 Execute
             var countValue = dbContext.ExecuteScalar(sql: sql, param: arg.sqlParam, useReadOnly: true);
             var count = Convert.ToInt32(countValue);
-            if (count > 0 && combinedStream.method == nameof(Queryable.Count))
+
+            // Count and TotalCount
+            if (count > 0 && queryArg.method == nameof(Queryable.Count))
             {
                 if (queryArg.skip > 0) count = Math.Max(count - queryArg.skip.Value, 0);
 
@@ -44,7 +48,7 @@ namespace Vitorm.Sql
                     count = Math.Min(count, queryArg.take.Value);
             }
 
-            (combinedStream.orders, combinedStream.skip, combinedStream.take) = queryArg;
+            (combinedStream.orders, combinedStream.skip, combinedStream.take, combinedStream.method) = queryArg;
             return count;
         }
 

+ 10 - 7
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteDelete.cs → src/Vitorm/Sql/QueryExecutor/Sync/ExecuteDelete.cs

@@ -1,24 +1,27 @@
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
-namespace Vitorm.Sql
+namespace Vitorm.Sql.QueryExecutor
 {
-    public partial class SqlDbContext : DbContext
+    public partial class ExecuteDelete : IQueryExecutor
     {
+        public static readonly ExecuteDelete Instance = new();
 
-        static object Query_ExecuteDelete(QueryExecutorArgument execArg)
+        public string methodName => nameof(Orm_Extensions.ExecuteDelete);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
         {
             CombinedStream combinedStream = execArg.combinedStream;
             var dbContext = execArg.dbContext;
             var sqlTranslateService = dbContext.sqlTranslateService;
 
-
+            // #2 Prepare sql
             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;
+
+            // #3 Execute
+            return dbContext.Execute(sql: sql, param: arg.sqlParam);
         }
 
     }

+ 8 - 7
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ExecuteUpdate.cs → src/Vitorm/Sql/QueryExecutor/Sync/ExecuteUpdate.cs

@@ -3,28 +3,29 @@
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
-namespace Vitorm.Sql
+namespace Vitorm.Sql.QueryExecutor
 {
-    public partial class SqlDbContext : DbContext
+    public partial class ExecuteUpdate : IQueryExecutor
     {
+        public static readonly ExecuteUpdate Instance = new();
 
-        static object Query_ExecuteUpdate(QueryExecutorArgument execArg)
+        public string methodName => nameof(Orm_Extensions.ExecuteUpdate);
+
+        public object ExecuteQuery(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
+            // #2 Prepare sql
             var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
             var arg = new QueryTranslateArgument(dbContext, resultEntityType);
-
             var sql = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
 
+            // #3 Execute
             return dbContext.Execute(sql: sql, param: arg.sqlParam);
         }
     }

+ 11 - 4
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.First.cs → src/Vitorm/Sql/QueryExecutor/Sync/FirstOrDefault.cs

@@ -1,21 +1,28 @@
-using Vitorm.Sql.SqlTranslate;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql
 {
-    public partial class SqlDbContext : DbContext
+    public partial class FirstOrDefault : IQueryExecutor
     {
+        public static readonly FirstOrDefault Instance = new();
+
+        public string methodName => nameof(Queryable.FirstOrDefault);
 
-        static object Query_First(QueryExecutorArgument execArg)
+        public object ExecuteQuery(QueryExecutorArgument execArg)
         {
             CombinedStream combinedStream = execArg.combinedStream;
             var dbContext = execArg.dbContext;
             var sqlTranslateService = dbContext.sqlTranslateService;
 
+            // #2 Prepare sql
             var resultEntityType = execArg.expression.Type;
             var arg = new QueryTranslateArgument(dbContext, resultEntityType);
-
             var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+            // #3 Execute
             using var reader = dbContext.ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
             return arg.dataReader.ReadData(reader);
         }

+ 24 - 0
src/Vitorm/Sql/QueryExecutor/Sync/ToExecuteString.cs

@@ -0,0 +1,24 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sql.QueryExecutor
+{
+    public partial class ToExecuteString : IQueryExecutor
+    {
+        public static readonly ToExecuteString Instance = new();
+
+        public string methodName => nameof(Orm_Extensions.ToExecuteString);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var sqlTranslateService = dbContext.sqlTranslateService;
+
+            // #2 Prepare sql
+            var arg = new QueryTranslateArgument(dbContext, null);
+            return sqlTranslateService.PrepareQuery(arg, combinedStream);
+        }
+
+    }
+}

+ 9 - 5
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToList.cs → src/Vitorm/Sql/QueryExecutor/Sync/ToList.cs

@@ -6,26 +6,30 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql
 {
-    public partial class SqlDbContext : DbContext
+    public partial class ToList : IQueryExecutor
     {
+        public static readonly ToList Instance = new();
 
-        static object Query_ToList(QueryExecutorArgument execArg)
+        public string methodName => nameof(Enumerable.ToList);
+
+        public object ExecuteQuery(QueryExecutorArgument execArg)
         {
             var resultEntityType = execArg.expression.Type.GetGenericArguments()?.FirstOrDefault();
-            return Query_ToList(execArg, resultEntityType);
+            return Execute(execArg, resultEntityType);
         }
 
 
-        static object Query_ToList(QueryExecutorArgument execArg, Type resultEntityType)
+        public static object Execute(QueryExecutorArgument execArg, Type resultEntityType)
         {
             CombinedStream combinedStream = execArg.combinedStream;
             var dbContext = execArg.dbContext;
             var sqlTranslateService = dbContext.sqlTranslateService;
 
+            // #2 Prepare sql
             var arg = new QueryTranslateArgument(dbContext, resultEntityType);
-
             var sql = sqlTranslateService.PrepareQuery(arg, combinedStream);
 
+            // #3 Execute
             using var reader = dbContext.ExecuteReader(sql: sql, param: arg.sqlParam, useReadOnly: true);
             return arg.dataReader.ReadData(reader);
         }

+ 13 - 11
src/Vitorm/Sql/SqlDbContext.Query.Sync/SqlDbContext.Query.ToListAndTotalCount.cs → src/Vitorm/Sql/QueryExecutor/Sync/ToListAndTotalCount.cs

@@ -9,23 +9,25 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql
 {
-    public partial class SqlDbContext : DbContext
+    public partial class ToListAndTotalCount : IQueryExecutor
     {
-        protected bool query_ToListAndTotalCount_InvokeInOneExecute = true;
-        static object Query_ToListAndTotalCount(QueryExecutorArgument execArg)
+        public static readonly ToListAndTotalCount Instance = new();
+
+        public string methodName => nameof(Queryable_Extensions.ToListAndTotalCount);
+
+        public object ExecuteQuery(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();
+            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;
@@ -62,22 +64,22 @@ namespace Vitorm.Sql
             else
             {
                 combinedStream.method = nameof(Enumerable.ToList);
-                list = Query_ToList(execArg, resultEntityType);
+                list = ToList.Execute(execArg, resultEntityType);
 
                 combinedStream.method = nameof(Queryable_Extensions.TotalCount);
-                totalCount = ExecuteQuery_Count(execArg);
+                totalCount = Count.Execute(execArg);
             }
 
             combinedStream.method = originMethod;
 
-            return Query_ToListAndTotalCount_MethodInfo(list.GetType(), typeof(int))
+            return Tuple_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())
+        private static MethodInfo Tuple_MethodInfo_;
+        static MethodInfo Tuple_MethodInfo(Type type1, Type type2) =>
+            (Tuple_MethodInfo_ ??= new Func<object, int, (object, int)>(ValueTuple.Create<object, int>).Method.GetGenericMethodDefinition())
             .MakeGenericMethod(type1, type2);
 
     }

+ 0 - 0
src/Vitorm/Sql/SqlDbContext.Execute.cs → src/Vitorm/Sql/SqlDbContext.Database.Execute.cs


+ 0 - 0
src/Vitorm/Sql/SqlDbContext.Transaction.cs → src/Vitorm/Sql/SqlDbContext.Database.Transaction.cs


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

@@ -1,42 +0,0 @@
-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);
-
-    }
-}

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

@@ -1,22 +0,0 @@
-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;
-        }
-
-    }
-}

+ 75 - 42
src/Vitorm/Sql/SqlDbContext.Query.cs

@@ -6,9 +6,9 @@ using System.Linq.Expressions;
 using Vit.Linq;
 using Vit.Linq.ExpressionNodes.ComponentModel;
 
+using Vitorm.Sql.QueryExecutor;
 using Vitorm.StreamQuery;
 
-using QueryExecutor = System.Func<Vitorm.Sql.SqlDbContext.QueryExecutorArgument, object>;
 using StreamReader = Vitorm.StreamQuery.StreamReader;
 
 namespace Vitorm.Sql
@@ -23,7 +23,6 @@ namespace Vitorm.Sql
         }
 
 
-
         public override IQueryable<Entity> Query<Entity>()
         {
             return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
@@ -42,50 +41,70 @@ namespace Vitorm.Sql
             }
         }
 
-
-
         #region QueryExecutor
 
-        public class QueryExecutorArgument
+        public static Dictionary<string, IQueryExecutor> defaultQueryExecutors = CreateDefaultQueryExecutors();
+        public static Dictionary<string, IQueryExecutor> CreateDefaultQueryExecutors()
         {
-            public CombinedStream combinedStream;
-            public SqlDbContext dbContext;
+            Dictionary<string, IQueryExecutor> defaultQueryExecutors = new();
 
-            public Expression expression;
-            public Type expressionResultType;
-        }
+            #region AddDefaultQueryExecutor
+            void AddDefaultQueryExecutor(IQueryExecutor queryExecutor, string methodName = null)
+            {
+                defaultQueryExecutors[methodName ?? queryExecutor.methodName] = queryExecutor;
+            }
+            #endregion
 
 
-        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,
+            // Orm_Extensions
+            AddDefaultQueryExecutor(ExecuteUpdate.Instance);
+            AddDefaultQueryExecutor(ExecuteDelete.Instance);
+            AddDefaultQueryExecutor(ToExecuteString.Instance);
+
+            // ToList
+            AddDefaultQueryExecutor(ToList.Instance);
+            // Count TotalCount
+            AddDefaultQueryExecutor(Count.Instance);
+            AddDefaultQueryExecutor(Count.Instance, methodName: nameof(Queryable_Extensions.TotalCount));
+
+            // ToListAndTotalCount
+            AddDefaultQueryExecutor(ToListAndTotalCount.Instance);
+
+            // FirstOrDefault First LastOrDefault Last
+            AddDefaultQueryExecutor(FirstOrDefault.Instance);
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.First));
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.LastOrDefault));
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.Last));
+            #endregion
 
-            [nameof(Enumerable.ToList)] = Query_ToList,
 
-            [nameof(Queryable_Extensions.ToListAndTotalCount)] = Query_ToListAndTotalCount,
+            #region Async
+            // Orm_Extensions
+            AddDefaultQueryExecutor(ExecuteUpdateAsync.Instance);
+            AddDefaultQueryExecutor(ExecuteDeleteAsync.Instance);
 
-            [nameof(Queryable.FirstOrDefault)] = Query_First,
-            [nameof(Queryable.First)] = Query_First,
-            [nameof(Queryable.LastOrDefault)] = Query_First,
-            [nameof(Queryable.Last)] = Query_First,
-            #endregion
+            // ToList
+            AddDefaultQueryExecutor(ToListAsync.Instance);
+            // Count TotalCount
+            AddDefaultQueryExecutor(CountAsync.Instance);
+            AddDefaultQueryExecutor(CountAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.TotalCountAsync));
 
+            // ToListAndTotalCount
+            AddDefaultQueryExecutor(ToListAndTotalCountAsync.Instance);
 
-            #region Async
-            [nameof(Queryable_Extensions.ToListAsync)] = Query_ToListAsync,
+            // FirstOrDefault First LastOrDefault Last
+            AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance);
+            AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.FirstAsync));
+            AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.LastOrDefaultAsync));
+            AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.LastAsync));
 
             #endregion
 
-        };
-
+            return defaultQueryExecutors;
+        }
 
-        public Dictionary<string, QueryExecutor> queryExecutors = defaultQueryExecutors;
+        public Dictionary<string, IQueryExecutor> queryExecutors = defaultQueryExecutors;
 
         #endregion
 
@@ -96,6 +115,7 @@ namespace Vitorm.Sql
         #endregion
 
 
+        public bool query_ToListAndTotalCount_InvokeInOneExecute = true;
 
         protected bool QueryIsFromSameDb(object query, Type elementType)
         {
@@ -112,22 +132,35 @@ namespace Vitorm.Sql
             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);
+            var executorArg = new QueryExecutorArgument
+            {
+                combinedStream = combinedStream,
+                dbContext = this,
+                expression = expression,
+                expressionResultType = expressionResultType
+            };
+
+
+            #region #3 Execute by executor from CombinedStream
+            {
+                var queryExecutor = combinedStream.GetQueryExecutor();
+                if (queryExecutor != null)
+                    return queryExecutor.ExecuteQuery(executorArg);
+            }
+            #endregion
 
-            if (executor != null)
-                return executor(new QueryExecutorArgument
+            #region #4 Execute by registered executor
+            {
+                var method = combinedStream.method;
+                if (string.IsNullOrWhiteSpace(method)) method = nameof(Enumerable.ToList);
+                if (queryExecutors.TryGetValue(method, out var queryExecutor))
                 {
-                    combinedStream = combinedStream,
-                    dbContext = this,
-                    expression = expression,
-                    expressionResultType = expressionResultType
-                });
+                    return queryExecutor.ExecuteQuery(executorArg);
+                }
+            }
+            #endregion
 
             throw new NotSupportedException("not supported query method: " + combinedStream.method);
         }

+ 32 - 0
src/Vitorm/StreamQuery/Attribute/StreamQuery_CustomMethodAttribute.cs

@@ -0,0 +1,32 @@
+using System;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.StreamQuery.MethodCall;
+
+namespace Vitorm.StreamQuery
+{
+    /// <summary>
+    /// Mark this method to be able to convert to IStream from ExpressionNode when executing query. Arguments count must be 0, for example : query.ToListAsync() 
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+    public class StreamQuery_CustomMethodAttribute : Attribute, Vitorm.StreamQuery.MethodCall.IMethodConvertor
+    {
+        public IStream Convert(MethodCallConvertArgrument methodConvertArg)
+        {
+            ExpressionNode_MethodCall call = methodConvertArg.node;
+            var reader = methodConvertArg.reader;
+            var arg = methodConvertArg.arg;
+
+            if (call.arguments?.Length != 1) return null;
+
+            var source = reader.ReadStream(arg, call.arguments[0]);
+            CombinedStream combinedStream = reader.AsCombinedStream(arg, source);
+
+            combinedStream.method = call.methodName;
+            return combinedStream;
+        }
+    }
+
+
+}

+ 13 - 0
src/Vitorm/StreamQuery/CombinedStream.cs → src/Vitorm/StreamQuery/ComponentModel/CombinedStream.cs

@@ -97,5 +97,18 @@ value(Vit.Linq.Converter.OrderedQueryable`1[Vit.Linq.MsTest.Converter.Join_Test+
 
         public object[] methodArguments { get; set; }
         public object extra { get; set; }
+
+
+        #region ExtraArg
+        public Dictionary<string, object> extraArg;
+        public object GetExtraArg(string key) => extraArg?.TryGetValue(key, out var value) == true ? value : null;
+
+        public CombinedStream SetExtraArg(string key, object arg)
+        {
+            extraArg ??= new();
+            extraArg[key] = arg;
+            return this;
+        }
+        #endregion
     }
 }

+ 0 - 0
src/Vitorm/StreamQuery/EJoinType.cs → src/Vitorm/StreamQuery/ComponentModel/EJoinType.cs


+ 0 - 0
src/Vitorm/StreamQuery/IStream.cs → src/Vitorm/StreamQuery/ComponentModel/IStream.cs


+ 0 - 0
src/Vitorm/StreamQuery/SourceStream.cs → src/Vitorm/StreamQuery/ComponentModel/SourceStream.cs


+ 0 - 0
src/Vitorm/StreamQuery/StreamToJoin.cs → src/Vitorm/StreamQuery/ComponentModel/StreamToJoin.cs


+ 2 - 2
src/Vitorm/StreamQuery/StreamToUpdate.cs → src/Vitorm/StreamQuery/ComponentModel/StreamToUpdate.cs

@@ -5,7 +5,7 @@ namespace Vitorm.StreamQuery
 {
     public partial class StreamToUpdate : CombinedStream
     {
-        public StreamToUpdate(IStream source) : base(source.alias)
+        public StreamToUpdate(IStream source, string method) : base(source.alias)
         {
             if (source is CombinedStream combinedStream)
             {
@@ -24,7 +24,7 @@ namespace Vitorm.StreamQuery
             {
                 base.source = source;
             }
-            method = nameof(Orm_Extensions.ExecuteUpdate);
+            this.method = method;
         }
 
         // ExpressionNode_New   new { name = name + "_" }

+ 26 - 0
src/Vitorm/StreamQuery/ExpressionNode_RenameableMember.cs

@@ -0,0 +1,26 @@
+using System;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public class ExpressionNode_RenameableMember : ExpressionNode
+    {
+        protected IStream stream;
+        public override string parameterName
+        {
+            get => stream?.alias;
+            set { }
+        }
+        public static ExpressionNode Member(IStream stream, Type memberType)
+        {
+            var node = new ExpressionNode_RenameableMember
+            {
+                nodeType = NodeType.Member,
+                stream = stream
+            };
+            node.Member_SetType(memberType);
+            return node;
+        }
+    }
+}

+ 7 - 0
src/Vitorm/StreamQuery/MethodCall/IMethodConvertor.cs

@@ -0,0 +1,7 @@
+namespace Vitorm.StreamQuery.MethodCall
+{
+    public interface IMethodConvertor
+    {
+        IStream Convert(MethodCallConvertArgrument methodConvertArg);
+    }
+}

+ 2 - 2
src/Vitorm/StreamQuery/MethodCall/MethodCallConvertArgrument.cs

@@ -5,8 +5,8 @@ namespace Vitorm.StreamQuery.MethodCall
     public class MethodCallConvertArgrument
     {
         public StreamReader reader { get; set; }
-        public Argument arg { get; set; }
+        public StreamReaderArgument arg { get; set; }
 
-        public ExpressionNode node { get; set; }
+        public ExpressionNode_MethodCall node { get; set; }
     }
 }

+ 12 - 0
src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_Delegate.cs

@@ -0,0 +1,12 @@
+using Convertor = System.Func<Vitorm.StreamQuery.MethodCall.MethodCallConvertArgrument, Vitorm.StreamQuery.IStream>;
+
+namespace Vitorm.StreamQuery.MethodCall
+{
+    public class MethodCallConvertor_Delegate : IMethodConvertor
+    {
+        public MethodCallConvertor_Delegate(Convertor convertor) => this.convertor = convertor;
+
+        Convertor convertor;
+        public IStream Convert(MethodCallConvertArgrument methodConvertArg) => convertor(methodConvertArg);
+    }
+}

+ 4 - 5
src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_ExecuteEnd.cs

@@ -1,20 +1,19 @@
 using System.Collections.Generic;
 
-using Vit.Linq;
 using Vit.Linq.ExpressionNodes.ComponentModel;
 
 namespace Vitorm.StreamQuery.MethodCall
 {
-    public class MethodCallConvertor_ExecuteEnd
+    public class MethodCallConvertor_ExecuteEnd : IMethodConvertor
     {
 
         public static MethodCallConvertor_ExecuteEnd Instance =
             new MethodCallConvertor_ExecuteEnd(
                 new List<string>()
                 {
-                    nameof(Queryable_Extensions.ToListAndTotalCount),
-                    nameof(Queryable_Extensions.TotalCount),
-                    nameof(Queryable_Extensions.ToListAsync),
+                    nameof(Vit.Linq.Queryable_Extensions.ToListAndTotalCount),
+                    nameof(Vit.Linq.Queryable_Extensions.TotalCount),
+                    nameof(Vit.Linq.Queryable_Extensions.ToListAsync),
 
                     nameof(Orm_Extensions.ExecuteDelete),
                     nameof(Orm_Extensions.ToExecuteString),

+ 0 - 41
src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_ExecuteUpdate.cs

@@ -1,41 +0,0 @@
-using Vit.Linq.ExpressionNodes.ComponentModel;
-
-namespace Vitorm.StreamQuery.MethodCall
-{
-    public class MethodCallConvertor_ExecuteUpdate
-    {
-        public static IStream Convert(MethodCallConvertArgrument methodConvertArg)
-        {
-            ExpressionNode_MethodCall call = methodConvertArg.node;
-            var reader = methodConvertArg.reader;
-            var arg = methodConvertArg.arg;
-
-            if (call.methodName != nameof(Orm_Extensions.ExecuteUpdate)) return null;
-
-
-            var source = reader.ReadStream(arg, call.arguments[0]);
-            ExpressionNode_Lambda resultSelector = call.arguments[1];
-            switch (source)
-            {
-                case SourceStream sourceStream:
-                    {
-                        var parameterName = resultSelector.parameterNames[0];
-                        var parameterValue = ExpressionNode_RenameableMember.Member(stream: sourceStream, resultSelector.Lambda_GetParamTypes()[0]);
-
-                        var select = reader.ReadResultSelector(arg.WithParameter(parameterName, parameterValue), resultSelector);
-                        return new StreamToUpdate(sourceStream) { fieldsToUpdate = select.fields };
-                    }
-                case CombinedStream combinedStream:
-                    {
-                        var parameterName = resultSelector.parameterNames[0];
-                        var parameterValue = combinedStream.select.fields;
-                        var select = reader.ReadResultSelector(arg.WithParameter(parameterName, parameterValue), resultSelector);
-
-                        return new StreamToUpdate(source) { fieldsToUpdate = select.fields };
-                    }
-            }
-
-            return null;
-        }
-    }
-}

+ 22 - 0
src/Vitorm/StreamQuery/MethodCall/MethodCallConvertor_FromAttribute.cs

@@ -0,0 +1,22 @@
+using System.Linq;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+namespace Vitorm.StreamQuery.MethodCall
+{
+    public class MethodCallConvertor_FromAttribute : IMethodConvertor
+    {
+
+        public static MethodCallConvertor_FromAttribute Instance = new();
+
+
+        public IStream Convert(MethodCallConvertArgrument methodConvertArg)
+        {
+            ExpressionNode_MethodCall call = methodConvertArg.node;
+
+            IMethodConvertor convertor = call.MethodCall_GetMethod()?.GetCustomAttributes(true).FirstOrDefault(attr => attr is IMethodConvertor) as IMethodConvertor;
+
+            return convertor?.Convert(methodConvertArg);
+        }
+    }
+}

+ 1 - 1
src/Vitorm/StreamQuery/StreamReader.GroupBy.cs

@@ -6,7 +6,7 @@ namespace Vitorm.StreamQuery
 {
     public partial class StreamReader
     {
-        CombinedStream GroupBy(Argument arg, IStream source, ExpressionNode_Lambda resultSelector)
+        CombinedStream GroupBy(StreamReaderArgument arg, IStream source, ExpressionNode_Lambda resultSelector)
         {
             switch (source)
             {

+ 1 - 1
src/Vitorm/StreamQuery/StreamReader.Join.cs

@@ -16,7 +16,7 @@ namespace Vitorm.StreamQuery
         //       , (user, father) => new { user, father }
         //   );
 
-        CombinedStream Join(Argument arg, IStream source, IStream rightStream, ExpressionNode_Lambda leftKeySelector, ExpressionNode_Lambda rightKeySelector, ExpressionNode_Lambda resultSelector)
+        CombinedStream Join(StreamReaderArgument arg, IStream source, IStream rightStream, ExpressionNode_Lambda leftKeySelector, ExpressionNode_Lambda rightKeySelector, ExpressionNode_Lambda resultSelector)
         {
 
             CombinedStream finalStream;

+ 11 - 4
src/Vitorm/StreamQuery/StreamReader.MethodCallConvertor.cs

@@ -4,21 +4,28 @@ using Vitorm.StreamQuery.MethodCall;
 
 using Convertor = System.Func<Vitorm.StreamQuery.MethodCall.MethodCallConvertArgrument, Vitorm.StreamQuery.IStream>;
 
+
 namespace Vitorm.StreamQuery
 {
     public partial class StreamReader
     {
 
-        public List<Convertor> methodCallConvertors = new()
+        public List<IMethodConvertor> methodCallConvertors = new()
         {
-            MethodCallConvertor_ExecuteUpdate.Convert,
-            MethodCallConvertor_ExecuteEnd.Instance.Convert,
+            MethodCallConvertor_ExecuteEnd.Instance,
+
+            MethodCallConvertor_FromAttribute.Instance,
         };
 
 
-        public virtual void AddMethodCallConvertor(Convertor convertor)
+        public virtual void AddMethodCallConvertor(IMethodConvertor convertor)
         {
             methodCallConvertors.Add(convertor);
         }
+
+        public virtual void AddMethodCallConvertor(Convertor convertor)
+        {
+            methodCallConvertors.Add(new MethodCallConvertor_Delegate(convertor));
+        }
     }
 }

+ 2 - 2
src/Vitorm/StreamQuery/StreamReader.SelectMany.cs

@@ -14,7 +14,7 @@ namespace Vitorm.StreamQuery
         //      (user, father) => new <>f__AnonymousType4`2(user = user, father = father)
         //  )
 
-        CombinedStream SelectMany(Argument arg, IStream source, ExpressionNode_Lambda rightSelector, ExpressionNode_Lambda resultSelector)
+        CombinedStream SelectMany(StreamReaderArgument arg, IStream source, ExpressionNode_Lambda rightSelector, ExpressionNode_Lambda resultSelector)
         {
             CombinedStream finalStream;
             ExpressionNode parameterValueForLeftStream;
@@ -63,7 +63,7 @@ namespace Vitorm.StreamQuery
 
                 ReadNode(argForRightStream, rightSelector.body);
 
-                void ReadNode(Argument argForRightStream, ExpressionNode node)
+                void ReadNode(StreamReaderArgument argForRightStream, ExpressionNode node)
                 {
                     switch (node.nodeType)
                     {

+ 12 - 137
src/Vitorm/StreamQuery/StreamReader.cs

@@ -8,131 +8,6 @@ using Vitorm.StreamQuery.MethodCall;
 
 namespace Vitorm.StreamQuery
 {
-
-    public class ExpressionNode_RenameableMember : ExpressionNode
-    {
-        protected IStream stream;
-        public override string parameterName
-        {
-            get => stream?.alias;
-            set { }
-        }
-        public static ExpressionNode Member(IStream stream, Type memberType)
-        {
-            var node = new ExpressionNode_RenameableMember
-            {
-                nodeType = NodeType.Member,
-                stream = stream
-            };
-            node.Member_SetType(memberType);
-            return node;
-        }
-    }
-
-
-
-    public class Argument
-    {
-        public Argument(Argument arg) : this(arg.aliasConfig)
-        {
-        }
-        public Argument(AliasConfig aliasConfig)
-        {
-            this.aliasConfig = aliasConfig;
-        }
-
-        public class AliasConfig
-        {
-            int aliasNameCount = 0;
-            public string NewAliasName()
-            {
-                return "t" + (aliasNameCount++);
-            }
-        }
-
-        protected AliasConfig aliasConfig;
-
-        public string NewAliasName() => aliasConfig.NewAliasName();
-
-        Dictionary<string, ExpressionNode> parameterMap { get; set; }
-
-        public virtual ExpressionNode GetParameter(ExpressionNode_Member member)
-        {
-            if (member.nodeType == NodeType.Member && !string.IsNullOrWhiteSpace(member.parameterName))
-            {
-                if (parameterMap?.TryGetValue(member.parameterName, out var parameterValue) == true)
-                {
-                    if (string.IsNullOrWhiteSpace(member.memberName))
-                    {
-                        return parameterValue;
-                    }
-                    else
-                    {
-                        return ExpressionNode.Member(objectValue: parameterValue, memberName: member.memberName).Member_SetType(member.Member_GetType());
-                    }
-                }
-            }
-            return null;
-        }
-
-
-        public Argument SetParameter(string parameterName, ExpressionNode parameterValue)
-        {
-            parameterMap ??= new();
-            parameterMap[parameterName] = parameterValue;
-            return this;
-        }
-
-
-        public Argument WithParameter(string parameterName, ExpressionNode parameterValue)
-        {
-            var arg = new Argument(aliasConfig);
-
-            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
-            arg.parameterMap[parameterName] = parameterValue;
-            return arg;
-        }
-
-        #region SupportNoChildParameter
-        public Argument WithParameter(string parameterName, ExpressionNode parameterValue, ExpressionNode noChildParameterValue)
-        {
-            var arg = new Argument_SupportNoChildParameter(this) { noChildParameterName = parameterName, noChildParameterValue = noChildParameterValue };
-
-            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
-            arg.parameterMap[parameterName] = parameterValue;
-            return arg;
-        }
-
-        class Argument_SupportNoChildParameter : Argument
-        {
-            public Argument_SupportNoChildParameter(Argument arg) : base(arg)
-            {
-            }
-
-            public string noChildParameterName;
-            public ExpressionNode noChildParameterValue;
-            public override ExpressionNode GetParameter(ExpressionNode_Member member)
-            {
-                if (member.nodeType == NodeType.Member && member.parameterName == noChildParameterName && member.memberName == null)
-                {
-                    return noChildParameterValue;
-                }
-                return base.GetParameter(member);
-            }
-
-        }
-        #endregion
-
-
-        public ExpressionNode DeepClone(ExpressionNode node)
-        {
-            Func<ExpressionNode_Member, ExpressionNode> GetParameter = this.GetParameter;
-
-            return StreamReader.DeepClone(node, GetParameter);
-        }
-
-    }
-
     public partial class StreamReader
     {
         public static StreamReader Instance = new StreamReader();
@@ -157,12 +32,12 @@ namespace Vitorm.StreamQuery
         /// <returns> </returns>
         public IStream ReadFromNode(ExpressionNode_Lambda lambda)
         {
-            var arg = new Argument(new Argument.AliasConfig());
+            var arg = new StreamReaderArgument(new StreamReaderArgument.AliasConfig());
             return ReadStream(arg, lambda.body);
         }
 
 
-        CombinedStream ReadStreamWithWhere(Argument arg, IStream source, ExpressionNode_Lambda predicateLambda)
+        CombinedStream ReadStreamWithWhere(StreamReaderArgument arg, IStream source, ExpressionNode_Lambda predicateLambda)
         {
             switch (source)
             {
@@ -217,7 +92,7 @@ namespace Vitorm.StreamQuery
             }
             return default;
         }
-        CombinedStream ToCombinedStream(Argument arg, SourceStream source)
+        CombinedStream ToCombinedStream(StreamReaderArgument arg, SourceStream source)
         {
             Type entityType = source.GetEntityType();
             var selectedFields = ExpressionNode.Member(parameterName: source.alias, memberName: null).Member_SetType(entityType);
@@ -225,7 +100,7 @@ namespace Vitorm.StreamQuery
 
             return new CombinedStream(arg.NewAliasName()) { source = source, select = select };
         }
-        public CombinedStream AsCombinedStream(Argument arg, IStream source)
+        public CombinedStream AsCombinedStream(StreamReaderArgument arg, IStream source)
         {
             if (source is CombinedStream combinedStream) return combinedStream;
             if (source is SourceStream sourceStream) return ToCombinedStream(arg, sourceStream);
@@ -234,7 +109,7 @@ namespace Vitorm.StreamQuery
 
 
         // query.SelectMany(query2).Where().Where().OrderBy().Skip().Take().Select()
-        public IStream ReadStream(Argument arg, ExpressionNode node)
+        public IStream ReadStream(StreamReaderArgument arg, ExpressionNode node)
         {
             switch (node.nodeType)
             {
@@ -405,7 +280,7 @@ namespace Vitorm.StreamQuery
                             var methodConvertArg = new MethodCallConvertArgrument { reader = this, arg = arg, node = node };
                             foreach (var convertor in methodCallConvertors)
                             {
-                                var stream = convertor(methodConvertArg);
+                                var stream = convertor.Convert(methodConvertArg);
                                 if (stream != null) return stream;
                             }
                         }
@@ -421,7 +296,7 @@ namespace Vitorm.StreamQuery
 
 
         // predicateLambda:          father => (father.id == user.fatherId)
-        ExpressionNode ReadWhere(Argument arg, IStream source, ExpressionNode_Lambda predicateLambda)
+        ExpressionNode ReadWhere(StreamReaderArgument arg, IStream source, ExpressionNode_Lambda predicateLambda)
         {
             var parameterName = predicateLambda.parameterNames[0];
             var parameterValue = ExpressionNode_RenameableMember.Member(stream: source, predicateLambda.Lambda_GetParamTypes()[0]);
@@ -431,13 +306,13 @@ namespace Vitorm.StreamQuery
         }
 
         // predicate:           (father.id == user.fatherId)
-        ExpressionNode ReadWhere(Argument arg, ExpressionNode predicate)
+        ExpressionNode ReadWhere(StreamReaderArgument arg, ExpressionNode predicate)
         {
             return arg.DeepClone(predicate);
         }
 
 
-        ExpressionNode ReadFields(Argument arg, ExpressionNode_Lambda resultSelector)
+        ExpressionNode ReadFields(StreamReaderArgument arg, ExpressionNode_Lambda resultSelector)
         {
             ExpressionNode node = resultSelector.body;
             if (node?.nodeType != NodeType.New && node?.nodeType != NodeType.Member && node?.nodeType != NodeType.Convert)
@@ -446,7 +321,7 @@ namespace Vitorm.StreamQuery
             var fields = arg.DeepClone(node);
             return fields;
         }
-        public ResultSelector ReadResultSelector(Argument arg, ExpressionNode_Lambda resultSelector)
+        public ResultSelector ReadResultSelector(StreamReaderArgument arg, ExpressionNode_Lambda resultSelector)
         {
             ExpressionNode node = resultSelector.body;
             //if (node?.nodeType != NodeType.New && node?.nodeType != NodeType.Member && node?.nodeType != NodeType.Convert)  // could be calculated result like  query.Select(u=>u.id+10)
@@ -476,7 +351,7 @@ namespace Vitorm.StreamQuery
             return new() { fields = fields, isDefaultSelect = isDefaultSelect, resultSelector = resultSelector };
         }
 
-        ExpressionNode ReadSortField(Argument arg, ExpressionNode_Lambda resultSelector, CombinedStream stream)
+        ExpressionNode ReadSortField(StreamReaderArgument arg, ExpressionNode_Lambda resultSelector, CombinedStream stream)
         {
             ExpressionNode parameterValue;
             if (stream.isGroupedStream)
@@ -492,7 +367,7 @@ namespace Vitorm.StreamQuery
             }
 
             var parameterName = resultSelector.parameterNames[0];
-            var argForClone = new Argument(arg).SetParameter(parameterName, parameterValue);
+            var argForClone = new StreamReaderArgument(arg).SetParameter(parameterName, parameterValue);
 
             ExpressionNode sortField = resultSelector.body;
 

+ 110 - 0
src/Vitorm/StreamQuery/StreamReaderArgument.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public class StreamReaderArgument
+    {
+        public StreamReaderArgument(StreamReaderArgument arg) : this(arg.aliasConfig)
+        {
+        }
+        public StreamReaderArgument(AliasConfig aliasConfig)
+        {
+            this.aliasConfig = aliasConfig;
+        }
+
+        public class AliasConfig
+        {
+            int aliasNameCount = 0;
+            public string NewAliasName()
+            {
+                return "t" + (aliasNameCount++);
+            }
+        }
+
+        protected AliasConfig aliasConfig;
+
+        public string NewAliasName() => aliasConfig.NewAliasName();
+
+        Dictionary<string, ExpressionNode> parameterMap { get; set; }
+
+        public virtual ExpressionNode GetParameter(ExpressionNode_Member member)
+        {
+            if (member.nodeType == NodeType.Member && !string.IsNullOrWhiteSpace(member.parameterName))
+            {
+                if (parameterMap?.TryGetValue(member.parameterName, out var parameterValue) == true)
+                {
+                    if (string.IsNullOrWhiteSpace(member.memberName))
+                    {
+                        return parameterValue;
+                    }
+                    else
+                    {
+                        return ExpressionNode.Member(objectValue: parameterValue, memberName: member.memberName).Member_SetType(member.Member_GetType());
+                    }
+                }
+            }
+            return null;
+        }
+
+
+        public StreamReaderArgument SetParameter(string parameterName, ExpressionNode parameterValue)
+        {
+            parameterMap ??= new();
+            parameterMap[parameterName] = parameterValue;
+            return this;
+        }
+
+
+        public StreamReaderArgument WithParameter(string parameterName, ExpressionNode parameterValue)
+        {
+            var arg = new StreamReaderArgument(aliasConfig);
+
+            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
+            arg.parameterMap[parameterName] = parameterValue;
+            return arg;
+        }
+
+        #region SupportNoChildParameter
+        public StreamReaderArgument WithParameter(string parameterName, ExpressionNode parameterValue, ExpressionNode noChildParameterValue)
+        {
+            var arg = new Argument_SupportNoChildParameter(this) { noChildParameterName = parameterName, noChildParameterValue = noChildParameterValue };
+
+            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
+            arg.parameterMap[parameterName] = parameterValue;
+            return arg;
+        }
+
+        class Argument_SupportNoChildParameter : StreamReaderArgument
+        {
+            public Argument_SupportNoChildParameter(StreamReaderArgument arg) : base(arg)
+            {
+            }
+
+            public string noChildParameterName;
+            public ExpressionNode noChildParameterValue;
+            public override ExpressionNode GetParameter(ExpressionNode_Member member)
+            {
+                if (member.nodeType == NodeType.Member && member.parameterName == noChildParameterName && member.memberName == null)
+                {
+                    return noChildParameterValue;
+                }
+                return base.GetParameter(member);
+            }
+
+        }
+        #endregion
+
+
+        public ExpressionNode DeepClone(ExpressionNode node)
+        {
+            Func<ExpressionNode_Member, ExpressionNode> GetParameter = this.GetParameter;
+
+            return StreamReader.DeepClone(node, GetParameter);
+        }
+
+    }
+}

+ 1 - 1
test/Vitorm.MsTest/StreamQuery/Queryable_Extensions_Batch.cs

@@ -12,7 +12,7 @@ namespace Vitorm.MsTest.StreamQuery
     public static partial class Queryable_Extensions_Batch
     {
 
-        [CustomMethod]
+        [ExpressionNode_CustomMethod]
         public static IEnumerable<List<Result>> Batch<Result>(this IQueryable<Result> source, int batchSize = 5000)
         {
             if (source == null)

+ 54 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteDeleteAsync_Test.cs

@@ -0,0 +1,54 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Orm_Extensions_ExecuteDeleteAsync_Test
+    {
+
+        [TestMethod]
+        public async Task Test_ExecuteDelete()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father
+                            };
+
+                var rowCount = await query.ExecuteDeleteAsync();
+
+                Assert.AreEqual(3, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(3, newUsers.Count());
+                Assert.AreEqual(4, newUsers.First().id);
+                Assert.AreEqual(6, newUsers.Last().id);
+            }
+
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = await userQuery.Where(m => m.id == 2 || m.id == 4).ExecuteDeleteAsync();
+
+                Assert.AreEqual(2, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(4, newUsers.Count());
+                Assert.AreEqual(1, newUsers.First().id);
+                Assert.AreEqual(3, newUsers[1].id);
+                Assert.AreEqual(5, newUsers[2].id);
+            }
+        }
+    }
+}

+ 0 - 2
test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteDelete_Test.cs

@@ -12,7 +12,6 @@ namespace Vitorm.MsTest.CommonTest
         [TestMethod]
         public void Test_ExecuteDelete()
         {
-            if (1 == 1)
             {
                 using var dbContext = DataSource.CreateDbContext();
                 var userQuery = dbContext.Query<User>();
@@ -36,7 +35,6 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual(6, newUsers.Last().id);
             }
 
-            if (1 == 1)
             {
                 using var dbContext = DataSource.CreateDbContext();
                 var userQuery = dbContext.Query<User>();

+ 78 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteUpdateAsync_Test.cs

@@ -0,0 +1,78 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Orm_Extensions_ExecuteUpdateAsync_Test
+    {
+        [TestMethod]
+        public async Task Test_ExecuteUpdate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = await userQuery.ExecuteUpdateAsync(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? ""),
+                    birth = DateTime.Parse("2021-01-11 00:00:00")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                Assert.AreEqual(DateTime.Parse("2021-01-11 00:00:00"), userList.First().birth);
+                Assert.AreEqual("u_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = await query.ExecuteUpdateAsync(row => new User
+                {
+                    name = "u2_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(6, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u2_1_4_6", userList.First().name);
+                Assert.AreEqual("u2_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = await query.ExecuteUpdateAsync(row => new User
+                {
+                    name = "u3_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(3, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u3_1_4_6", userList[0].name);
+                Assert.AreEqual("u3_3_5_6", userList[2].name);
+                Assert.AreEqual("u2_4__", userList[3].name);
+            }
+        }
+    }
+}

+ 0 - 2
test/Vitorm.Sqlite.MsTest/CommonTest/Orm_Extensions_ExecuteUpdate_Test.cs

@@ -30,7 +30,6 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual("u_6__", userList.Last().name);
             }
 
-
             {
                 var query = from user in userQuery
                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
@@ -53,7 +52,6 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual("u2_6__", userList.Last().name);
             }
 
-
             {
                 var query = from user in userQuery
                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()

+ 235 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_AsyncMethods_Test.cs

@@ -0,0 +1,235 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_AsyncMethods_Test
+    {
+
+        [TestMethod]
+        public async Task Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = await userQuery.OrderBy(m => m.id).ToListAsync();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+            {
+                var userList = await userQuery.OrderBy(m => m.id).Select(u => u.id).ToListAsync();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = await query.CountAsync();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = await query.CountAsync();
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_TotalCount()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = await query.TotalCountAsync();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = await query.TotalCountAsync();
+                Assert.AreEqual(4, count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_ToListAndTotal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // ToListAndTotalCount
+            {
+                var query = dbContext.Query<User>().Where(user => user.id > 2).Skip(1).Take(2);
+                var (list, totalCount) = await query.ToListAndTotalCountAsync();
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual(4, totalCount);
+            }
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = await userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, id);
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstOrDefaultAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstOrDefaultAsync(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderByDescending(m => m.id).FirstOrDefaultAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_First()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).FirstAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = await userQuery.FirstAsync(user => user.id == 13);
+                    Assert.Fail("IQueryable.First should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+
+            }
+
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_LastOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = await userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, id);
+            }
+            {
+                var user = await userQuery.OrderBy(m => m.id).LastOrDefaultAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastOrDefaultAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastOrDefaultAsync(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderByDescending(m => m.id).LastOrDefaultAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_Last()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).LastAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = await userQuery.LastAsync(user => user.id == 13);
+                    Assert.Fail("IQueryable.Last should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+
+        }
+
+
+
+
+    }
+}

+ 42 - 6
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Test.cs

@@ -2,6 +2,8 @@
 
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 
+using Vit.Linq;
+
 namespace Vitorm.MsTest.CommonTest
 {
 
@@ -9,8 +11,6 @@ namespace Vitorm.MsTest.CommonTest
     public class Query_LinqMethods_Test
     {
 
-
-
         [TestMethod]
         public void Test_PlainQuery()
         {
@@ -180,7 +180,45 @@ namespace Vitorm.MsTest.CommonTest
             }
         }
 
+        [TestMethod]
+        public void Test_TotalCount()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
 
+            // TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = query.TotalCount();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = query.TotalCount();
+                Assert.AreEqual(4, count);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_ToListAndTotal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // ToListAndTotalCount
+            {
+                var query = dbContext.Query<User>().Where(user => user.id > 2).Skip(1).Take(2);
+                var (list, totalCount) = query.ToListAndTotalCount();
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual(4, totalCount);
+            }
+        }
 
 
 
@@ -237,12 +275,11 @@ namespace Vitorm.MsTest.CommonTest
                 try
                 {
                     var user = userQuery.First(user => user.id == 13);
-                    Assert.Fail("IQueryalbe.First should throw Exception");
+                    Assert.Fail("IQueryable.First should throw Exception");
                 }
                 catch (Exception ex) when (ex is not AssertFailedException)
                 {
                 }
-
             }
 
         }
@@ -301,7 +338,7 @@ namespace Vitorm.MsTest.CommonTest
                 try
                 {
                     var user = userQuery.Last(user => user.id == 13);
-                    Assert.Fail("IQueryalbe.First should throw Exception");
+                    Assert.Fail("IQueryable.Last should throw Exception");
                 }
                 catch (Exception ex) when (ex is not AssertFailedException)
                 {
@@ -325,7 +362,6 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual(6, userList.Last().id);
             }
 
-
             {
                 var userList = userQuery.OrderBy(m => m.id).Select(u => u.id).ToArray();
                 Assert.AreEqual(6, userList.Length);