瀏覽代碼

- [Vitorm] support Execute Procedure in SqlExecutor and SqlDbContext
- [Vitorm] IDataReader extension methods: ReadValue ReadTuple ReadEntity

Lith 4 月之前
父節點
當前提交
70f6a91e9d

+ 3 - 2
doc/ReleaseNotes.md

@@ -8,8 +8,9 @@
 - upgrade net to 8.0
 - [Vitorm] move SqlDataProvider to namespace Vitorm.Sql.DataProvider
 - [Vitorm] move IDbDataReader to namespace Vitorm.Sql.DataReader
-- [Vitorm] support StoredProcedure in SqlExecutor and SqlDbContext
-- [Vitorm] IDataReader extension method:  ReadEntity
+- [Vitorm] support Execute Procedure in SqlExecutor and SqlDbContext
+- [Vitorm] IDataReader extension methods: ReadValue ReadTuple ReadEntity
+
 
 -----------------------
 # 2.3.0

+ 64 - 0
src/Vitorm/Sql/Extensions/IDataReader_Extensions.EntityReader.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+
+namespace Vitorm
+{
+    public static partial class IDataReader_Extensions
+    {
+
+        class EntityReader
+        {
+            Type entityType;
+            (PropertyInfo property, int columnIndex, Type underlyingType)[] columns;
+
+            public EntityReader(Type entityType, List<string> columnNames) : this(entityType, columnNames.Select((m, i) => (m, i)).ToList())
+            { }
+
+            public EntityReader(Type entityType, List<(string columName, int index)> columnIndexes)
+            {
+                this.entityType = entityType;
+
+                var properties =
+                    entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                    .Select(property => (
+                        property: property,
+                        name: property.GetCustomAttribute<ColumnAttribute>(inherit: true)?.Name ?? property.Name
+                        )
+                    )
+                    .ToList();
+
+                columns = properties.Select(m =>
+                {
+                    var property = m.property;
+                    var columnIndex = columnIndexes.FirstOrDefault(col => m.name.Equals(col.columName, StringComparison.OrdinalIgnoreCase));
+                    if (columnIndex.columName == null) return default;
+
+                    var index = columnIndex.index;
+                    var underlyingType = TypeUtil.GetUnderlyingType(property.PropertyType);
+                    return (property, index, underlyingType);
+                })
+                .Where(m => m != default)
+                .ToArray();
+            }
+
+
+            public object Read(IDataReader reader)
+            {
+                var entity = Activator.CreateInstance(entityType);
+                foreach (var column in columns)
+                {
+                    var value = reader.GetValue(column.columnIndex);
+                    var convertedValue = TypeUtil.ConvertToUnderlyingType(value, column.underlyingType);
+                    column.property.SetValue(entity, convertedValue);
+                }
+                return entity;
+            }
+
+        }
+
+    }
+}

+ 67 - 0
src/Vitorm/Sql/Extensions/IDataReader_Extensions.ValueReader.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Data;
+
+namespace Vitorm
+{
+    public static partial class IDataReader_Extensions
+    {
+
+        interface IValueReader
+        {
+            Type valueType { get; }
+            object Read(IDataReader reader, int index = 0);
+        }
+
+
+
+        class ValueReader_String : IValueReader
+        {
+            public Type valueType => typeof(string);
+            public object Read(IDataReader reader, int index = 0) => reader.GetString(index);
+
+
+            public static ValueReader_String Instance = new ValueReader_String();
+        }
+
+
+
+        class ValueReader_Nullable : IValueReader
+        {
+            public ValueReader_Nullable(Type valueType)
+            {
+                this.valueType = valueType;
+                underlyingType = TypeUtil.GetUnderlyingType(valueType);
+            }
+            public Type valueType { get; private set; }
+            Type underlyingType;
+            public object Read(IDataReader reader, int index = 0)
+            {
+                return TypeUtil.ConvertToUnderlyingType(reader[index], underlyingType);
+            }
+        }
+
+
+
+
+        class ValueReader_Struct : IValueReader
+        {
+            public ValueReader_Struct(Type valueType)
+            {
+                this.valueType = valueType;
+                defaultValue = TypeUtil.GetDefaultValue(valueType);
+            }
+            public Type valueType { get; private set; }
+            object defaultValue;
+            public object Read(IDataReader reader, int index = 0)
+            {
+                var value = reader[index];
+                if (value == null || value == DBNull.Value) return defaultValue;
+                return TypeUtil.ConvertToUnderlyingType(value, valueType);
+            }
+        }
+
+
+
+
+    }
+}

+ 216 - 41
src/Vitorm/Sql/Extensions/IDataReader_Extensions.cs

@@ -1,63 +1,238 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
 using System.Data;
 using System.Linq;
-using System.Reflection;
 
 namespace Vitorm
 {
-    public static class IDataReader_Extensions
+    public static partial class IDataReader_Extensions
     {
-        public static IEnumerable<Entity> ReadEntity<Entity>(this IDataReader reader) where Entity : class, new()
+
+        static IValueReader BuildValueReader(Type type)
+        {
+            if (type == typeof(string)) return ValueReader_String.Instance;
+            else if (TypeUtil.IsNullable(type)) return new ValueReader_Nullable(type);
+            return new ValueReader_Struct(type);
+        }
+
+
+        #region #1 ReadValue
+        public static IEnumerable<Value> ReadValue<Value>(this IDataReader reader, int index = 0)
         {
-            object[] values = null;
-            PropertyInfo[] properties = null;
-            Type[] underlyingTypes = null;
+            var valueReader = BuildValueReader(typeof(Value));
             while (reader.Read())
-            {
-                if (values == null)
+                yield return (Value)valueReader.Read(reader, index);
+        }
+
+        public static IEnumerable<Value> ReadValue<Value>(this IDataReader reader, string columnName)
+        {
+            var names = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+            int index = names.FindIndex(name => name.Equals(columnName, StringComparison.OrdinalIgnoreCase));
+
+            if (index < 0) throw new ArgumentException("can not find column " + columnName);
+
+            return ReadValue<Value>(reader, index);
+        }
+        #endregion
+
+
+
+
+        #region #2 ReadTuple
+
+        public static IEnumerable<(Column0, Column1)> ReadTuple<Column0, Column1>(this IDataReader reader, int[] indexes = null)
+        {
+            var valueReaders = new[] { typeof(Column0), typeof(Column1) }.Select(type => BuildValueReader(type)).ToArray();
+            indexes ??= new[] { 0, 1 };
+
+            while (reader.Read())
+                yield return ((Column0)valueReaders[0].Read(reader, indexes[0]), (Column1)valueReaders[1].Read(reader, indexes[1]));
+        }
+        public static IEnumerable<(Column0, Column1)> ReadTuple<Column0, Column1>(this IDataReader reader, string[] columnNames)
+        {
+            var names = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+            var indexes = columnNames.Select(
+                columnName =>
                 {
-                    var names = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToArray();
-                    values = new object[names.Length];
-                    properties = new PropertyInfo[names.Length];
-                    underlyingTypes = new Type[names.Length];
-
-                    var propertyInfos =
-                        typeof(Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance)
-                        .Select(property =>
-                            (property: property,
-                            name: property.GetCustomAttribute<ColumnAttribute>(inherit: true)?.Name ?? property.Name
-                            )
-                        ).ToList();
-
-                    for (var i = 0; i < names.Length; i++)
-                    {
-                        var name = names[i];
-                        var property = propertyInfos.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)).property;
-                        if (property != null)
-                        {
-                            properties[i] = property;
-                            underlyingTypes[i] = TypeUtil.GetUnderlyingType(property.PropertyType);
-                        }
-                    }
+                    var index = names.FindIndex(name => name.Equals(columnName, StringComparison.OrdinalIgnoreCase));
+                    if (index < 0) throw new ArgumentException("can not find column " + columnName);
+                    return index;
                 }
+            ).ToArray();
 
-                reader.GetValues(values);
+            return ReadTuple<Column0, Column1>(reader, indexes);
+        }
 
-                var entity = new Entity();
-                for (int i = 0; i < values.Length; i++)
+
+        public static IEnumerable<(Column0, Column1, Column2)> ReadTuple<Column0, Column1, Column2>(this IDataReader reader, int[] indexes = null)
+        {
+            var valueReaders = new[] { typeof(Column0), typeof(Column1), typeof(Column2) }.Select(type => BuildValueReader(type)).ToArray();
+            indexes ??= new[] { 0, 1, 2 };
+
+            while (reader.Read())
+                yield return ((Column0)valueReaders[0].Read(reader, indexes[0]), (Column1)valueReaders[1].Read(reader, indexes[1]), (Column2)valueReaders[2].Read(reader, indexes[2]));
+        }
+        public static IEnumerable<(Column0, Column1, Column2)> ReadTuple<Column0, Column1, Column2>(this IDataReader reader, string[] columnNames)
+        {
+            var names = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+            var indexes = columnNames.Select(
+                columnName =>
                 {
-                    var value = values[i];
-                    var property = properties[i];
-                    if (property == null) continue;
+                    var index = names.FindIndex(name => name.Equals(columnName, StringComparison.OrdinalIgnoreCase));
+                    if (index < 0) throw new ArgumentException("can not find column " + columnName);
+                    return index;
+                }
+            ).ToArray();
+
+            return ReadTuple<Column0, Column1, Column2>(reader, indexes);
+        }
+
+
+        public static IEnumerable<(Column0, Column1, Column2, Column3)> ReadTuple<Column0, Column1, Column2, Column3>(this IDataReader reader, int[] indexes = null)
+        {
+            var valueReaders = new[] { typeof(Column0), typeof(Column1), typeof(Column2), typeof(Column3) }.Select(type => BuildValueReader(type)).ToArray();
+            indexes ??= new[] { 0, 1, 2, 3 };
 
-                    var convertedValue = TypeUtil.ConvertToUnderlyingType(value, underlyingTypes[i]);
-                    property.SetValue(entity, convertedValue);
+            while (reader.Read())
+                yield return ((Column0)valueReaders[0].Read(reader, indexes[0]), (Column1)valueReaders[1].Read(reader, indexes[1]), (Column2)valueReaders[2].Read(reader, indexes[2]), (Column3)valueReaders[3].Read(reader, indexes[3]));
+        }
+        public static IEnumerable<(Column0, Column1, Column2, Column3)> ReadTuple<Column0, Column1, Column2, Column3>(this IDataReader reader, string[] columnNames)
+        {
+            var names = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+            var indexes = columnNames.Select(
+                columnName =>
+                {
+                    var index = names.FindIndex(name => name.Equals(columnName, StringComparison.OrdinalIgnoreCase));
+                    if (index < 0) throw new ArgumentException("can not find column " + columnName);
+                    return index;
                 }
-                yield return entity;
+            ).ToArray();
+
+            return ReadTuple<Column0, Column1, Column2, Column3>(reader, indexes);
+        }
+        #endregion
+
+
+
+
+
+
+
+
+        #region #3 ReadEntity
+        public static IEnumerable<Entity> ReadEntity<Entity>(this IDataReader reader) where Entity : class, new()
+        {
+            var fieldNames = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+
+            var entityReader = new EntityReader(typeof(Entity), fieldNames);
+
+            while (reader.Read())
+            {
+                yield return (Entity)entityReader.Read(reader);
             }
+        }
+
+
+        #region ReadEntity by Delegate
 
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <param name="func"></param>
+        /// <param name="splitIndexes">if splitIndex is valid, will try populate all column to all entity</param>
+        /// <param name="types"></param>
+        /// <param name="fieldNames"></param>
+        /// <returns></returns>
+        static IEnumerable<object> ReadEntity(this IDataReader reader, Delegate func, int[] splitIndexes, Type[] types = null, List<string> fieldNames = null)
+        {
+            types ??= func.Method.GetParameters().Select(p => p.ParameterType).ToArray();
+            fieldNames ??= Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+
+            // splitIndexes  :   1, 5 , -1, 10
+            splitIndexes ??= Enumerable.Repeat(-1, types.Length - 1).ToArray();
+
+            var splitRangeStart = new[] { 0 }.Concat(splitIndexes.Select(i => i >= 0 ? i : 0)).ToArray();
+            var splitRangeEnd = splitIndexes.Select(i => i >= 0 ? i - 1 : int.MaxValue).Concat(new[] { int.MaxValue }).ToArray();
+
+            var fieldIndexes = fieldNames.Select((fieldName, index) => (fieldName: fieldName, index: index)).ToList();
+            var entityReaders = types.Select((type, index) => new EntityReader(type, fieldIndexes.Where(field => splitRangeStart[index] <= field.index && field.index <= splitRangeEnd[index]).ToList())).ToList();
+
+            while (reader.Read())
+            {
+                yield return func.DynamicInvoke(entityReaders.Select(entityReader => entityReader.Read(reader)).ToArray());
+            }
         }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <param name="func"></param>
+        /// <param name="splitOns">if can not find splitOn column, will try populate all column to all entity</param>
+        /// <returns></returns>
+        static IEnumerable<object> ReadEntity(this IDataReader reader, Delegate func, string[] splitOns)
+        {
+            var types = func.Method.GetParameters().Select(p => p.ParameterType).ToArray();
+            var fieldNames = Enumerable.Range(0, reader.FieldCount).Select(i => reader.GetName(i)).ToList();
+
+            splitOns ??= new string[types.Length - 1];
+
+            // splitIndexes  :   1, 5 , -1, 10
+            var splitIndexes = splitOns.Select(splitOn => fieldNames.FindIndex(name => name.Equals(splitOn, StringComparison.OrdinalIgnoreCase))).Concat(new[] { fieldNames.Count }).ToArray();
+
+            return ReadEntity(reader, func, splitIndexes, types, fieldNames);
+        }
+
+
+
+        static IEnumerable<TReturn> ReadEntity<TReturn>(this IDataReader reader, Delegate func, string[] splitOns)
+            => ReadEntity(reader, func, splitOns).Select(m => (TReturn)m);
+
+        static IEnumerable<TReturn> ReadEntity<TReturn>(this IDataReader reader, Delegate func, int[] splitIndexes = null)
+            => ReadEntity(reader, func, splitIndexes).Select(m => (TReturn)m);
+        #endregion
+
+
+        #region ReadEntity2
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TReturn> func)
+            => ReadEntity<TReturn>(reader, func);
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TReturn> func, string splitOn)
+            => ReadEntity<TReturn>(reader, func, new[] { splitOn });
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TReturn> func, string[] splitOns)
+            => ReadEntity<TReturn>(reader, func, splitOns);
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TReturn> func, int splitIndex)
+            => ReadEntity<TReturn>(reader, func, new[] { splitIndex });
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TReturn> func, int[] splitIndexes)
+            => ReadEntity<TReturn>(reader, func, splitIndexes);
+
+        #endregion
+
+        #region ReadEntity3
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TThird, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TThird, TReturn> func)
+            => ReadEntity<TReturn>(reader, func);
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TThird, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TThird, TReturn> func, string splitOn0, string splitOn1)
+            => ReadEntity<TReturn>(reader, func, new[] { splitOn0, splitOn1 });
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TThird, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TThird, TReturn> func, string[] splitOns)
+            => ReadEntity<TReturn>(reader, func, splitOns);
+
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TThird, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TThird, TReturn> func, int splitIndex0, int splitIndex1)
+            => ReadEntity<TReturn>(reader, func, new[] { splitIndex0, splitIndex1 });
+        public static IEnumerable<TReturn> ReadEntity<TFirst, TSecond, TThird, TReturn>(this IDataReader reader, Func<TFirst, TSecond, TThird, TReturn> func, int[] splitIndexes)
+            => ReadEntity<TReturn>(reader, func, splitIndexes);
+
+        #endregion
+
+
+        #endregion
+
+
+
+
     }
 }

+ 14 - 14
src/Vitorm/Sql/SqlDbContext.Execute.cs

@@ -98,12 +98,12 @@ namespace Vitorm.Sql
 
 
         #region Sync Method
-        public virtual int ExecuteWithTransaction(string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null)
+        public virtual int ExecuteWithTransaction(string sql, Dictionary<string, object> parameters = null, IDbTransaction transaction = null, bool isProcedure = false)
         {
             transaction ??= GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return sqlExecutor.Execute(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.Execute(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
         public virtual int Execute(ExecuteArgument arg, bool useReadOnly = false)
@@ -114,7 +114,7 @@ namespace Vitorm.Sql
 
             return sqlExecutor.Execute(arg);
         }
-        public virtual int Execute(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual int Execute(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             this.Event_OnExecuting(sql, parameters);
 
@@ -122,7 +122,7 @@ namespace Vitorm.Sql
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return sqlExecutor.Execute(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.Execute(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
 
@@ -136,7 +136,7 @@ namespace Vitorm.Sql
 
             return sqlExecutor.ExecuteReader(arg);
         }
-        public virtual IDataReader ExecuteReader(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual IDataReader ExecuteReader(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             this.Event_OnExecuting(sql, parameters);
 
@@ -144,7 +144,7 @@ namespace Vitorm.Sql
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return sqlExecutor.ExecuteReader(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.ExecuteReader(dbConnection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
 
@@ -156,7 +156,7 @@ namespace Vitorm.Sql
 
             return sqlExecutor.ExecuteScalar(arg);
         }
-        public virtual object ExecuteScalar(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual object ExecuteScalar(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             this.Event_OnExecuting(sql, parameters);
 
@@ -164,7 +164,7 @@ namespace Vitorm.Sql
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return sqlExecutor.ExecuteScalar(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.ExecuteScalar(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
         #endregion
@@ -182,13 +182,13 @@ namespace Vitorm.Sql
             return sqlExecutor.ExecuteAsync(arg);
         }
 
-        public virtual async Task<int> ExecuteAsync(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual Task<int> ExecuteAsync(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             var connection = useReadOnly ? readOnlyDbConnection : dbConnection;
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return await sqlExecutor.ExecuteAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.ExecuteAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
 
@@ -200,13 +200,13 @@ namespace Vitorm.Sql
 
             return sqlExecutor.ExecuteReaderAsync(arg);
         }
-        public virtual async Task<IDataReader> ExecuteReaderAsync(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual Task<IDataReader> ExecuteReaderAsync(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             var connection = useReadOnly ? readOnlyDbConnection : dbConnection;
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return await sqlExecutor.ExecuteReaderAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.ExecuteReaderAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
 
@@ -218,13 +218,13 @@ namespace Vitorm.Sql
 
             return sqlExecutor.ExecuteScalarAsync(arg);
         }
-        public virtual async Task<object> ExecuteScalarAsync(string sql, IDictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false)
+        public virtual Task<object> ExecuteScalarAsync(string sql, Dictionary<string, object> parameters = null, int? commandTimeout = null, bool useReadOnly = false, bool isProcedure = false)
         {
             var connection = useReadOnly ? readOnlyDbConnection : dbConnection;
             var transaction = GetDbTransaction();
             commandTimeout ??= this.commandTimeout ?? defaultCommandTimeout;
 
-            return await sqlExecutor.ExecuteScalarAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.ExecuteScalarAsync(connection, sql, parameters: parameters, transaction: transaction, commandTimeout: commandTimeout, isProcedure: isProcedure);
         }
 
         #endregion

+ 11 - 7
src/Vitorm/Sql/SqlExecute/SqlExecutor.Async.cs

@@ -14,8 +14,8 @@ namespace Vitorm.Sql.SqlExecute
 
         public Func<DbConnection, Task> CloseAsync = (DbConnection conn) => { conn.Close(); return Task.CompletedTask; };
 
-        public virtual Task<int> ExecuteAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-         => ExecuteAsync(new(connection, sql, parameters, transaction, commandTimeout));
+        public virtual Task<int> ExecuteAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+         => ExecuteAsync(new(connection, sql, parameters, transaction, commandTimeout, commandType: isProcedure ? CommandType.StoredProcedure : null));
 
         public virtual async Task<int> ExecuteAsync(ExecuteArgument arg)
         {
@@ -50,8 +50,8 @@ namespace Vitorm.Sql.SqlExecute
             return await Task.Run(() => Execute(arg));
         }
 
-        public virtual Task<object> ExecuteScalarAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-            => ExecuteScalarAsync(new(connection, sql, parameters, transaction, commandTimeout));
+        public virtual Task<object> ExecuteScalarAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+            => ExecuteScalarAsync(new(connection, sql, parameters, transaction, commandTimeout, commandType: isProcedure ? CommandType.StoredProcedure : null));
         public virtual async Task<object> ExecuteScalarAsync(ExecuteArgument arg)
         {
             if (arg.connection is DbConnection connection)
@@ -85,8 +85,8 @@ namespace Vitorm.Sql.SqlExecute
             return await Task.Run(() => ExecuteScalar(arg));
         }
 
-        public Task<IDataReader> ExecuteReaderAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-             => ExecuteReaderAsync(new(connection, sql, parameters, transaction, commandTimeout));
+        public Task<IDataReader> ExecuteReaderAsync(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+             => ExecuteReaderAsync(new(connection, sql, parameters, transaction, commandTimeout, commandType: isProcedure ? CommandType.StoredProcedure : null));
         public virtual async Task<IDataReader> ExecuteReaderAsync(ExecuteArgument arg)
         {
             if (arg.connection is DbConnection connection)
@@ -98,10 +98,14 @@ namespace Vitorm.Sql.SqlExecute
                 {
                     // #1 setup command
                     cmd = connection.CreateCommand();
+                    cmd.Connection = connection;
+
                     if (arg.transaction != null) cmd.Transaction = (DbTransaction)arg.transaction;
                     if (arg.commandTimeout.HasValue) cmd.CommandTimeout = arg.commandTimeout.Value;
-                    cmd.Connection = connection;
+
+                    if (arg.commandType.HasValue) cmd.CommandType = arg.commandType.Value;
                     cmd.CommandText = arg.text;
+
                     AddParameters(cmd, arg.parameters);
 
                     // #2 execute

+ 6 - 6
src/Vitorm/Sql/SqlExecute/SqlExecutor.cs

@@ -9,8 +9,8 @@ namespace Vitorm.Sql.SqlExecute
         public readonly static SqlExecutor Instance = new();
 
 
-        public virtual int Execute(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-            => Execute(new(connection, sql, parameters, transaction, commandTimeout));
+        public virtual int Execute(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+            => Execute(new(connection, sql, parameters, transaction, commandTimeout, commandType: isProcedure ? CommandType.StoredProcedure : null));
 
         public virtual int Execute(ExecuteArgument arg)
         {
@@ -42,8 +42,8 @@ namespace Vitorm.Sql.SqlExecute
 
 
 
-        public virtual object ExecuteScalar(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-            => ExecuteScalar(new(connection, sql, parameters, transaction, commandTimeout));
+        public virtual object ExecuteScalar(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+            => ExecuteScalar(new(connection, sql, parameters, transaction, commandType: isProcedure ? CommandType.StoredProcedure : null));
 
         public virtual object ExecuteScalar(ExecuteArgument arg)
         {
@@ -74,8 +74,8 @@ namespace Vitorm.Sql.SqlExecute
 
 
 
-        public virtual IDataReader ExecuteReader(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
-            => ExecuteReader(new(connection, sql, parameters, transaction, commandTimeout));
+        public virtual IDataReader ExecuteReader(IDbConnection connection, string sql, IDictionary<string, object> parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, bool isProcedure = false)
+            => ExecuteReader(new(connection, sql, parameters, transaction, commandTimeout, commandType: isProcedure ? CommandType.StoredProcedure : null));
 
         public virtual IDataReader ExecuteReader(ExecuteArgument arg)
         {

+ 141 - 0
test/Vitorm.MySql.MsTest/CustomTest/Procedure_Test.cs

@@ -0,0 +1,141 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CustomTest
+{
+
+    [TestClass]
+    public class Procedure_Test
+    {
+
+        [TestMethod]
+        public void Procedure()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // create procedure
+            {
+                dbContext.Execute(@"
+                    DROP PROCEDURE IF EXISTS GetUser;
+
+                    CREATE PROCEDURE GetUser(IN uid INT)
+                    BEGIN
+                        SELECT * FROM `User` WHERE userId = uid order by userId;
+                        SELECT * FROM `User` WHERE userId != uid order by userId;
+                    END
+                    ");
+            }
+
+
+            // ExecuteReader
+            {
+                using var reader = dbContext.ExecuteReader("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+
+                var userList = reader.ReadEntity<User>().ToList();
+                Assert.AreEqual("1", String.Join(",", userList.Select(u => u.id)));
+            }
+
+            // ExecuteReader
+            {
+                using var reader = dbContext.ExecuteReader("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("1", String.Join(",", userList.Select(u => u.id)));
+                }
+                reader.NextResult();
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("2,3,4,5,6", String.Join(",", userList.Select(u => u.id)));
+                }
+            }
+
+            // ExecuteReader
+            {
+                using var reader = dbContext.ExecuteReader("GetUser", new() { ["uid"] = 0 }, isProcedure: true);
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("", String.Join(",", userList.Select(u => u.id)));
+                }
+                reader.NextResult();
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("1,2,3,4,5,6", String.Join(",", userList.Select(u => u.id)));
+                }
+            }
+
+            // ExecuteReader
+            {
+                var userId = dbContext.ExecuteScalar("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+                Assert.AreEqual(1, userId);
+            }
+        }
+
+
+
+        [TestMethod]
+        public async Task ProcedureAsync()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // create procedure
+            {
+                await dbContext.ExecuteAsync(@"
+                    DROP PROCEDURE IF EXISTS GetUser;
+
+                    CREATE PROCEDURE GetUser(IN uid INT)
+                    BEGIN
+                        SELECT * FROM `User` WHERE userId = uid order by userId;
+                        SELECT * FROM `User` WHERE userId != uid order by userId;
+                    END
+                    ");
+            }
+
+
+            // ExecuteReader
+            {
+                using var reader = await dbContext.ExecuteReaderAsync("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+
+                var userList = reader.ReadEntity<User>().ToList();
+                Assert.AreEqual("1", String.Join(",", userList.Select(u => u.id)));
+            }
+
+            // ExecuteReader
+            {
+                using var reader = await dbContext.ExecuteReaderAsync("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("1", String.Join(",", userList.Select(u => u.id)));
+                }
+                reader.NextResult();
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("2,3,4,5,6", String.Join(",", userList.Select(u => u.id)));
+                }
+            }
+
+            // ExecuteReader
+            {
+                using var reader = await dbContext.ExecuteReaderAsync("GetUser", new() { ["uid"] = 0 }, isProcedure: true);
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("", String.Join(",", userList.Select(u => u.id)));
+                }
+                reader.NextResult();
+                {
+                    var userList = reader.ReadEntity<User>().ToList();
+                    Assert.AreEqual("1,2,3,4,5,6", String.Join(",", userList.Select(u => u.id)));
+                }
+            }
+
+            // ExecuteReader
+            {
+                var userId = await dbContext.ExecuteScalarAsync("GetUser", new() { ["uid"] = 1 }, isProcedure: true);
+                Assert.AreEqual(1, userId);
+            }
+        }
+
+
+
+    }
+}

+ 322 - 0
test/Vitorm.Sqlite.MsTest/CustomTest/ReadEntity_Test.cs

@@ -0,0 +1,322 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CustomTest
+{
+
+    [TestClass]
+    public class ReadEntity_Test
+    {
+
+        [TestMethod]
+        public void ReadValue()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            // ReadValue
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var ids = reader.ReadValue<int>().ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", ids));
+            }
+
+            // ReadValue
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var names = reader.ReadValue<string>(1).ToList();
+
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", names));
+            }
+
+            // ReadValue
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var ids = reader.ReadValue<int?>("UserFatherId").ToList();
+
+                Assert.AreEqual("4,4,5,", String.Join(",", ids));
+            }
+
+        }
+
+
+
+        [TestMethod]
+        public void ReadTuple()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadTuple
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, string name)> userList = reader.ReadTuple<int, string>().ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+            }
+
+            // ReadTuple by indexes
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId)> userList = reader.ReadTuple<int, int?>([0, 3]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+            }
+
+
+            // ReadTuple by columnNames
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId)> userList = reader.ReadTuple<int, int?>(["userId", "userFatherId"]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+            }
+        }
+
+
+        [TestMethod]
+        public void ReadTuple3()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadTuple
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, string name, DateTime? birth)> userList = reader.ReadTuple<int, string, DateTime?>().ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+            }
+
+            // ReadTuple by indexes
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId, int? motherId)> userList = reader.ReadTuple<int, int?, int?>([0, 3, 4]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+
+            // ReadTuple by columnNames
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId, int? motherId)> userList = reader.ReadTuple<int, int?, int?>(["userId", "userFatherId", "UserMotherId"]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+        }
+
+
+        [TestMethod]
+        public void ReadTuple4()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadTuple
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, string name, DateTime? birth, int? fatherId)> userList = reader.ReadTuple<int, string, DateTime?, int?>().ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+            }
+
+            // ReadTuple by indexes
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId, int? motherId, string name)> userList = reader.ReadTuple<int, int?, int?, string>([0, 3, 4, 1]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+            }
+
+
+            // ReadTuple by columnNames
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                List<(int id, int? fatherId, int? motherId, string name)> userList = reader.ReadTuple<int, int?, int?, string>(["userId", "userFatherId", "UserMotherId", "userName"]).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+            }
+        }
+
+
+
+        [TestMethod]
+        public void ReadEntity()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadEntity
+            {
+                using var reader = dbContext.ExecuteReader("select * from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User>().ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+            }
+        }
+
+
+        [TestMethod]
+        public void ReadEntity2()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadEntity :  try read all columns for each entities
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1) => (u0.id, u0.name, u1.fatherId, u1.motherId)).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitOn column not exist 
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1) => (u0.id, u0.name, u1.fatherId, u1.motherId), "dummyField").ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitOn
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1) => (u0.id, u0.name, u1.fatherId, u1.motherId), "userFatherId").ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitIndex
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1) => (u0.id, u0.name, u1.fatherId, u1.motherId), 3).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+        }
+
+
+        [TestMethod]
+        public void ReadEntity3()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ReadEntity :  try read all columns for each entities
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1, u2) => (u0.id, u0.name, u1.fatherId, u2.motherId)).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitOn column not exist 
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1, u2) => (u0.id, u0.name, u1.fatherId, u2.motherId), "dummyField", "dummyField").ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitOns
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1, u2) => (u0.id, u0.name, u1.fatherId, u2.motherId), "userFatherId", "userMotherId").ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+
+            // ReadEntity splitOns
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,   userFatherId,userId,  userMotherId,userId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1, u2) => (u0.id, u0.name, u1.fatherId, u2.motherId), "userFatherId", "userMotherId").ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+
+            // ReadEntity splitIndex
+            {
+                using var reader = dbContext.ExecuteReader("select userId,userName,userBirth,userFatherId,userMotherId from `User`  where userId<=4 order by userId");
+
+                var userList = reader.ReadEntity<User, User, User, (int id, string name, int? fatherId, int? motherId)>((u0, u1, u2) => (u0.id, u0.name, u1.fatherId, u2.motherId), 3, 4).ToList();
+
+                Assert.AreEqual("1,2,3,4", String.Join(",", userList.Select(u => u.id)));
+                Assert.AreEqual("u146,u246,u356,u400", String.Join(",", userList.Select(u => u.name)));
+                Assert.AreEqual("4,4,5,", String.Join(",", userList.Select(u => u.fatherId)));
+                Assert.AreEqual("6,6,6,", String.Join(",", userList.Select(u => u.motherId)));
+            }
+        }
+
+
+
+
+    }
+}