Lith 10 сар өмнө
parent
commit
a23c544150

+ 14 - 0
Vitorm.sln

@@ -26,6 +26,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Sqlite.MsTest", "tes
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.SqlServer.MsTest", "test\Vitorm.SqlServer.MsTest\Vitorm.SqlServer.MsTest.csproj", "{31CE5879-959E-4882-8FBC-0D763FD73A0C}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Data", "src\Vitorm.Data\Vitorm.Data.csproj", "{333BD5FC-1DFD-4F3C-8062-99573057C89F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Data.MsTest", "test\Vitorm.Data.MsTest\Vitorm.Data.MsTest.csproj", "{BF06299A-78A1-44C9-BCE6-4226BA326739}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -60,6 +64,14 @@ Global
 		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{333BD5FC-1DFD-4F3C-8062-99573057C89F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{333BD5FC-1DFD-4F3C-8062-99573057C89F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{333BD5FC-1DFD-4F3C-8062-99573057C89F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{333BD5FC-1DFD-4F3C-8062-99573057C89F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BF06299A-78A1-44C9-BCE6-4226BA326739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BF06299A-78A1-44C9-BCE6-4226BA326739}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BF06299A-78A1-44C9-BCE6-4226BA326739}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BF06299A-78A1-44C9-BCE6-4226BA326739}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -72,6 +84,8 @@ Global
 		{8DD2F4AD-F533-408D-A527-9737D675A5F0} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
 		{B036D75F-AF60-4329-ABBB-252BF47F9435} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
 		{31CE5879-959E-4882-8FBC-0D763FD73A0C} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+		{333BD5FC-1DFD-4F3C-8062-99573057C89F} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{BF06299A-78A1-44C9-BCE6-4226BA326739} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {C7DA16E3-9949-49FA-B0B4-F830636DE60F}

+ 110 - 0
src/Vitorm.Data/Data.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+using Vit.Core.Module.Log;
+using Vit.Core.Util.ConfigurationManager;
+using Vit.Core.Util.Reflection;
+using Vit.Extensions.Linq_Extensions;
+
+using Vitorm.DataProvider;
+
+namespace Vitorm
+{
+    public partial class Data
+    {
+
+        static Data()
+        {
+            var configs = Appsettings.json.GetByPath<List<Dictionary<string, object>>>("Vitorm.Data");
+            providerCache = configs?.Select(GetDataProvider).NotNull().ToList() ?? new();
+        }
+
+
+        #region LoadDataProvider
+        public static IDataProvider DataProvider(Type entityType)
+        {
+            return providerMap.GetOrAdd(entityType, GetDataProviderFromConfig);
+
+            IDataProvider GetDataProviderFromConfig(Type entityType)
+            {
+                var ns = entityType.Namespace;
+                return providerCache.FirstOrDefault(cache => ns.StartsWith(cache.@namespace))?.dataProvider
+                    ?? throw new NotImplementedException("can not find config for type: " + entityType.FullName);
+            }
+        }
+        public static IDataProvider DataProvider<Entity>() => DataProvider(typeof(Entity));
+
+
+        class DataProviderCache
+        {
+            public IDataProvider dataProvider;
+            public string @namespace;
+        }
+
+
+        static readonly ConcurrentDictionary<Type, IDataProvider> providerMap = new ConcurrentDictionary<Type, IDataProvider>();
+
+        static List<DataProviderCache> providerCache;
+
+
+        static DataProviderCache GetDataProvider(Dictionary<string, object> config)
+        {
+            config.TryGetValue("provider", out var provider);
+            config.TryGetValue("assemblyName", out var assemblyName);
+            config.TryGetValue("assemblyFile", out var assemblyFile);
+
+            Type type = ObjectLoader.GetType(className: config["provider"] as string, assemblyName: assemblyName as string, assemblyFile: assemblyFile as string);
+
+            var dataProvider = type?.GetConstructor(Type.EmptyTypes)?.Invoke(new object[] { }) as IDataProvider;
+            if (dataProvider == null) return null;
+
+            dataProvider.Init(config);
+
+            return new DataProviderCache
+            {
+                dataProvider = dataProvider,
+                @namespace = config["namespace"] as string,
+            };
+        }
+
+
+        #endregion
+
+
+
+
+        #region CRUD
+
+        // #0 Schema :  Create
+        public static void Create<Entity>() => DataProvider<Entity>().Create<Entity>();
+
+
+        // #1 Create :  Add AddRange
+        public static Entity Add<Entity>(Entity entity) => DataProvider<Entity>().Add<Entity>(entity);
+        public static void AddRange<Entity>(IEnumerable<Entity> entities) => DataProvider<Entity>().AddRange<Entity>(entities);
+
+        // #2 Retrieve : Get Query
+        public static Entity Get<Entity>(object keyValue) => DataProvider<Entity>().Get<Entity>(keyValue);
+        public static IQueryable<Entity> Query<Entity>() => DataProvider<Entity>().Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        public static int Update<Entity>(Entity entity) => DataProvider<Entity>().Update<Entity>(entity);
+        public static int UpdateRange<Entity>(IEnumerable<Entity> entities) => DataProvider<Entity>().UpdateRange<Entity>(entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public static int Delete<Entity>(Entity entity) => DataProvider<Entity>().Delete<Entity>(entity);
+        public static int DeleteRange<Entity>(IEnumerable<Entity> entities) => DataProvider<Entity>().DeleteRange<Entity>(entities);
+
+        public static int DeleteByKey<Entity>(object keyValue) => DataProvider<Entity>().DeleteByKey<Entity>(keyValue);
+        public static int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys) => DataProvider<Entity>().DeleteByKeys<Entity, Key>(keys);
+
+
+        #endregion
+
+    }
+}

+ 35 - 0
src/Vitorm.Data/Vitorm.Data.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <pack>nuget</pack>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.1</TargetFramework>
+        <Version>1.0.1</Version>
+        <LangVersion>9.0</LangVersion>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Authors>Lith</Authors>
+        <Description>Data access for Database</Description>
+        <PackageProjectUrl>https://github.com/VitormLib/Vitorm.ClickHouse</PackageProjectUrl>
+        <PackageIcon>vitorm_logo_v1.png</PackageIcon>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Include="..\..\doc\vitorm_logo_v1.png">
+            <Pack>True</Pack>
+            <PackagePath>\</PackagePath>
+        </None>
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Vit.Core" Version="2.1.21" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\..\Vitorm\src\Vitorm\Vitorm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 23 - 0
src/Vitorm.MySql/DataProvider.cs

@@ -0,0 +1,23 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+using Vitorm.DataProvider;
+using System.Collections.Generic;
+
+namespace Vitorm.MySql
+{
+
+    public class DataProvider : SqlDataProvider
+    {
+        protected Dictionary<string, object> config;
+        protected string connectionString;
+
+        public override SqlDbContext CreateDbContext() => new SqlDbContext().UseMySql(connectionString);
+
+        public override void Init(Dictionary<string, object> config)
+        {
+            this.config = config;
+            if (config.TryGetValue("connectionString", out var connStr))
+                this.connectionString = connStr as string;
+        }
+    }
+}

+ 1 - 1
src/Vitorm.MySql/DbContext_Extensions.cs

@@ -28,7 +28,7 @@ namespace Vit.Extensions
             Func<IDbConnection> createDbConnection = () => new MySqlConnector.MySqlConnection(ConnectionString);
 
 
-            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection);
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, dbHashCode: ConnectionString.GetHashCode().ToString());
 
             dbContext.createTransactionScope = (dbContext) => new Vitorm.MySql.SqlTransactionScope(dbContext);
             //dbContext.createTransactionScope = (dbContext) => new Vitorm.Mysql.SqlTransactionScope_Command(dbContext);

+ 23 - 0
src/Vitorm.SqlServer/DataProvider.cs

@@ -0,0 +1,23 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+using Vitorm.DataProvider;
+using System.Collections.Generic;
+
+namespace Vitorm.SqlServer
+{
+
+    public class DataProvider : SqlDataProvider
+    {
+        protected Dictionary<string, object> config;
+        protected string connectionString;
+
+        public override SqlDbContext CreateDbContext() => new SqlDbContext().UseSqlServer(connectionString);
+
+        public override void Init(Dictionary<string, object> config)
+        {
+            this.config = config;
+            if (config.TryGetValue("connectionString", out var connStr))
+                this.connectionString = connStr as string;
+        }
+    }
+}

+ 2 - 1
src/Vitorm.SqlServer/DbContext_Extensions.cs

@@ -14,7 +14,8 @@ namespace Vit.Extensions
 
             Func<IDbConnection> createDbConnection = () => new Microsoft.Data.SqlClient.SqlConnection(ConnectionString);
 
-            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection);
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, dbHashCode: ConnectionString.GetHashCode().ToString());
+
             dbContext.createTransactionScope = (dbContext) => new Vitorm.SqlServer.SqlTransactionScope(dbContext);
 
             return dbContext;

+ 23 - 0
src/Vitorm.Sqlite/DataProvider.cs

@@ -0,0 +1,23 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+using Vitorm.DataProvider;
+using System.Collections.Generic;
+
+namespace Vitorm.Sqlite
+{
+
+    public class DataProvider : SqlDataProvider
+    {
+        protected Dictionary<string, object> config;
+        protected string connectionString;
+
+        public override SqlDbContext CreateDbContext() => new SqlDbContext().UseSqlite(connectionString);
+
+        public override void Init(Dictionary<string, object> config)
+        {
+            this.config = config;
+            if (config.TryGetValue("connectionString", out var connStr))
+                this.connectionString = connStr as string;
+        }
+    }
+}

+ 1 - 1
src/Vitorm.Sqlite/DbContext_Extensions.cs

@@ -15,7 +15,7 @@ namespace Vit.Extensions
             Func<IDbConnection> createDbConnection = () => new Microsoft.Data.Sqlite.SqliteConnection(ConnectionString);
 
 
-            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection);
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, dbHashCode: ConnectionString.GetHashCode().ToString());
 
             dbContext.createTransactionScope = (dbContext) => new Vitorm.Sqlite.SqlTransactionScope(dbContext);
 

+ 9 - 0
src/Vitorm/DataProvider/IDataProvider.cs

@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Vitorm.DataProvider
+{
+    public interface IDataProvider : IDbContext
+    {
+        void Init(Dictionary<string, object> config);
+    }
+}

+ 55 - 0
src/Vitorm/DataProvider/SqlDataProvider.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vitorm.Sql;
+
+namespace Vitorm.DataProvider
+{
+    public abstract class SqlDataProvider : IDataProvider
+    {
+        public abstract SqlDbContext CreateDbContext();
+
+        public abstract void Init(Dictionary<string, object> config);
+
+
+        public virtual T InvokeInDb<T>(Func<SqlDbContext, T> func)
+        {
+            using var dbContext = CreateDbContext();
+            return func(dbContext);
+        }
+
+        public virtual void InvokeInDb(Action<SqlDbContext> func)
+        {
+            using var dbContext = CreateDbContext();
+            func(dbContext);
+        }
+
+
+        // #0 Schema :  Create
+        public virtual void Create<Entity>() => InvokeInDb(db => db.Create<Entity>());
+
+
+        // #1 Create :  Add AddRange
+        public virtual Entity Add<Entity>(Entity entity) => InvokeInDb(db => db.Add<Entity>(entity));
+        public virtual void AddRange<Entity>(IEnumerable<Entity> entities) => InvokeInDb(db => db.AddRange<Entity>(entities));
+
+        // #2 Retrieve : Get Query
+        public virtual Entity Get<Entity>(object keyValue) => InvokeInDb(db => db.Get<Entity>(keyValue));
+        public virtual IQueryable<Entity> Query<Entity>() => CreateDbContext().AutoDisposeAfterQuery().Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        public virtual int Update<Entity>(Entity entity) => InvokeInDb(db => db.Update<Entity>(entity));
+        public virtual int UpdateRange<Entity>(IEnumerable<Entity> entities) => InvokeInDb(db => db.UpdateRange<Entity>(entities));
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        public virtual int Delete<Entity>(Entity entity) => InvokeInDb(db => db.Delete<Entity>(entity));
+        public virtual int DeleteRange<Entity>(IEnumerable<Entity> entities) => InvokeInDb(db => db.DeleteRange<Entity>(entities));
+
+        public virtual int DeleteByKey<Entity>(object keyValue) => InvokeInDb(db => db.DeleteByKey<Entity>(keyValue));
+        public virtual int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys) => InvokeInDb(db => db.DeleteByKeys<Entity, Key>(keys));
+
+    }
+}

+ 2 - 2
src/Vitorm/DbContext.cs

@@ -9,7 +9,7 @@ using Vitorm.Entity.DataAnnotations;
 
 namespace Vitorm
 {
-    public class DbContext : IDisposable
+    public class DbContext : IDbContext, IDisposable
     {
         public DbContext()
         {
@@ -54,7 +54,7 @@ namespace Vitorm
 
 
 
-        // #1 Schema :  Create
+        // #0 Schema :  Create
         public virtual void Create<Entity>() => throw new NotImplementedException();
 
 

+ 33 - 0
src/Vitorm/IDbContext.cs

@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Vitorm
+{
+    public interface IDbContext
+    {
+        // #0 Schema :  Create
+        void Create<Entity>();
+
+
+        // #1 Create :  Add AddRange
+        Entity Add<Entity>(Entity entity);
+        void AddRange<Entity>(IEnumerable<Entity> entities);
+
+        // #2 Retrieve : Get Query
+        Entity Get<Entity>(object keyValue);
+        IQueryable<Entity> Query<Entity>();
+
+
+        // #3 Update: Update UpdateRange
+        int Update<Entity>(Entity entity);
+        int UpdateRange<Entity>(IEnumerable<Entity> entities);
+
+
+        // #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+        int Delete<Entity>(Entity entity);
+        int DeleteRange<Entity>(IEnumerable<Entity> entities);
+
+        int DeleteByKey<Entity>(object keyValue);
+        int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys);
+    }
+}

+ 149 - 99
src/Vitorm/Sql/SqlDbContext.cs

@@ -18,24 +18,50 @@ namespace Vitorm.Sql
         protected IDbConnection _dbConnection;
         public override void Dispose()
         {
-            base.Dispose();
-
-            transactionScope?.Dispose();
-            transactionScope = null;
+            try
+            {
+                base.Dispose();
+            }
+            finally
+            {
+                try
+                {
+                    transactionScope?.Dispose();
+                }
+                finally
+                {
+                    transactionScope = null;
 
-            _dbConnection?.Dispose();
-            _dbConnection = null;
+                    _dbConnection?.Dispose();
+                    _dbConnection = null;
+                }
+            }
         }
         public virtual IDbConnection dbConnection => _dbConnection ??= createDbConnection();
 
 
         public virtual ISqlTranslateService sqlTranslateService { get; private set; }
 
-        public virtual void Init(ISqlTranslateService sqlTranslateService, Func<IDbConnection> createDbConnection, SqlExecutor sqlExecutor=null)
+
+
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="sqlTranslateService"></param>
+        /// <param name="createDbConnection"></param>
+        /// <param name="sqlExecutor"></param>
+        /// <param name="dbHashCode"> to identify whether contexts are from the same database </param>
+        public virtual void Init(ISqlTranslateService sqlTranslateService, Func<IDbConnection> createDbConnection, SqlExecutor sqlExecutor = null, string dbHashCode = null)
         {
             this.sqlTranslateService = sqlTranslateService;
             this.createDbConnection = createDbConnection;
             this.sqlExecutor = sqlExecutor ?? SqlExecutor.Instance;
+
+            if (string.IsNullOrEmpty(dbHashCode))
+                dbHashCode = GetHashCode().ToString();
+
+            dbGroupName = "SqlDbSet_" + dbHashCode;
         }
 
 
@@ -162,110 +188,134 @@ namespace Vitorm.Sql
         }
 
 
-        public override IQueryable<Entity> Query<Entity>()
+        /// <summary>
+        /// to identify whether contexts are from the same database
+        /// </summary>
+        protected string dbGroupName { get; set; }
+        protected bool QueryIsFromSameDb(object query, Type elementType)
         {
-            var dbContextId = "SqlDbSet_" + GetHashCode();
+            return dbGroupName == QueryableBuilder.GetQueryConfig(query as IQueryable) as string;
+        }
+        public Action<SqlDbContext, Expression, Type, object> AfterQuery;
+        protected object QueryExecutor(Expression expression, Type type)
+        {
+            object result = null;
+            try
+            {
+                return result = ExecuteQuery(expression, type);
+            }
+            finally
+            {
+                AfterQuery?.Invoke(this, expression, type, result);
+            }
+        }
+        public virtual SqlDbContext AutoDisposeAfterQuery()
+        {
+            AfterQuery += (_, _, _, _) => Dispose();
+            return this;
+        }
+
+        protected object ExecuteQuery(Expression expression, Type type)
+        {
+            // #1 convert to ExpressionNode 
+            ExpressionNode node = convertService.ConvertToData(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
+            //var strNode = Json.Serialize(node);
+
 
-            Func<Expression, Type, object> QueryExecutor = (expression, type) =>
+            // #2 convert to Stream
+            var stream = StreamReader.ReadNode(node);
+            //var strStream = Json.Serialize(stream);
+
+
+            // #3.1 ExecuteUpdate
+            if (stream is StreamToUpdate streamToUpdate)
             {
-                // #1 convert to ExpressionNode
-                var isArgument = QueryableBuilder.QueryTypeNameCompare(dbContextId);
-                ExpressionNode node = convertService.ConvertToData(expression, autoReduce: true, isArgument: isArgument);
-                //var strNode = Json.Serialize(node);
+                // get arg
+                var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
+                var arg = new QueryTranslateArgument(this, resultEntityType);
 
+                (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
 
-                // #2 convert to Stream
-                var stream = StreamReader.ReadNode(node);
-                //var strStream = Json.Serialize(stream);
+                return Execute(sql: sql, param: sqlParam);
+            }
 
 
-                // #3.1 ExecuteUpdate
-                if (stream is StreamToUpdate streamToUpdate)
-                {
-                    // get arg
-                    var resultEntityType = streamToUpdate.fieldsToUpdate.New_GetType();
-                    var arg = new QueryTranslateArgument(this, resultEntityType);
+            // #3.3 Query
+            // #3.3.1
+            var combinedStream = stream as CombinedStream;
+            if (combinedStream == null) combinedStream = new CombinedStream("tmp") { source = stream };
 
-                    (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
+            // #3.3.2 execute and read result
+            switch (combinedStream.method)
+            {
+                case nameof(Orm_Extensions.ToExecuteString):
+                    {
+                        // ToExecuteString
 
-                    return Execute(sql: sql, param: sqlParam);
-                }
+                        // get arg
+                        var arg = new QueryTranslateArgument(this, null);
 
+                        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+                        return sql;
+                    }
+                case "Count":
+                    {
+                        // Count
 
-                // #3.3 Query
-                // #3.3.1
-                var combinedStream = stream as CombinedStream;
-                if (combinedStream == null) combinedStream = new CombinedStream("tmp") { source = stream };
+                        // get arg
+                        var arg = new QueryTranslateArgument(this, null);
 
-                // #3.3.2 execute and read result
-                switch (combinedStream.method)
-                {
-                    case nameof(Orm_Extensions.ToExecuteString):
-                        {
-                            // ToExecuteString
-
-                            // get arg
-                            var arg = new QueryTranslateArgument(this, null);
-
-                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
-                            return sql;
-                        }
-                    case "Count":
-                        {
-                            // Count
-
-                            // get arg
-                            var arg = new QueryTranslateArgument(this, null);
-
-                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
-
-                            var count = ExecuteScalar(sql: sql, param: sqlParam);
-                            return Convert.ToInt32(count);
-                        }
-                    case nameof(Orm_Extensions.ExecuteDelete):
-                        {
-                            // ExecuteDelete
-
-                            // get arg
-                            var resultEntityType = (combinedStream.source as SourceStream)?.GetEntityType();
-                            var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                            (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
-
-                            var count = Execute(sql: sql, param: sqlParam);
-                            return count;
-                        }
-                    case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
-                        {
-                            // get arg
-                            var resultEntityType = expression.Type;
-                            var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
-
-                            using var reader = ExecuteReader(sql: sql, param: sqlParam);
-                            return dataReader.ReadData(reader);
-                        }
-                    case "ToList":
-                    case "":
-                    case null:
-                        {
-                            // ToList
-
-                            // get arg
-                            var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault();
-                            var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
-
-                            using var reader = ExecuteReader(sql: sql, param: sqlParam);
-                            return dataReader.ReadData(reader);
-                        }
-                }
-                throw new NotSupportedException("not supported query type: " + combinedStream.method);
-            };
-            return QueryableBuilder.Build<Entity>(QueryExecutor, dbContextId);
+                        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
 
+                        var count = ExecuteScalar(sql: sql, param: sqlParam);
+                        return Convert.ToInt32(count);
+                    }
+                case nameof(Orm_Extensions.ExecuteDelete):
+                    {
+                        // ExecuteDelete
+
+                        // get arg
+                        var resultEntityType = (combinedStream.source as SourceStream)?.GetEntityType();
+                        var arg = new QueryTranslateArgument(this, resultEntityType);
+
+                        (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
+
+                        var count = Execute(sql: sql, param: sqlParam);
+                        return count;
+                    }
+                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                    {
+                        // get arg
+                        var resultEntityType = expression.Type;
+                        var arg = new QueryTranslateArgument(this, resultEntityType);
+
+                        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+                        using var reader = ExecuteReader(sql: sql, param: sqlParam);
+                        return dataReader.ReadData(reader);
+                    }
+                case "ToList":
+                case "":
+                case null:
+                    {
+                        // ToList
+
+                        // get arg
+                        var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault();
+                        var arg = new QueryTranslateArgument(this, resultEntityType);
+
+                        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+                        using var reader = ExecuteReader(sql: sql, param: sqlParam);
+                        return dataReader.ReadData(reader);
+                    }
+            }
+            throw new NotSupportedException("not supported query type: " + combinedStream.method);
+        }
+
+        public override IQueryable<Entity> Query<Entity>()
+        {
+            return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
         }
 
         #endregion
@@ -401,7 +451,7 @@ namespace Vitorm.Sql
         {
             var transaction = GetCurrentTransaction();
             commandTimeout ??= this.commandTimeout;
-            return sqlExecutor.Execute(dbConnection,sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
+            return sqlExecutor.Execute(dbConnection, sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
         }
 
         public virtual IDataReader ExecuteReader(string sql, IDictionary<string, object> param = null, int? commandTimeout = null)

+ 10 - 4
src/Vitorm/Sql/TypeUtil.cs → src/Vitorm/TypeUtil.cs

@@ -1,10 +1,9 @@
 using System;
-using System.Collections.Generic;
-using System.Text;
 
-namespace Vitorm.Sql
+
+namespace Vitorm
 {
-    public class TypeUtil
+    public static class TypeUtil
     {
         public static Type GetUnderlyingType(Type type)
         {
@@ -47,5 +46,12 @@ namespace Vitorm.Sql
         }
 
 
+        public static object DefaultValue(Type type)
+        {
+            if (null == type || !type.IsValueType) return null;
+            return Activator.CreateInstance(type);
+        }
+
+
     }
 }

+ 63 - 0
test/Vitorm.Data.MsTest/CommonTest/Sqlite_Test.cs

@@ -0,0 +1,63 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vitorm.Sql;
+using Vit.Extensions;
+using UserEntity = Vitorm.MsTest.Sqlite.User;
+
+namespace Vitorm.MsTest.Sqlite
+{
+    public class User : Vitorm.MsTest.User { }
+}
+
+
+namespace Vitorm.MsTest
+{
+
+    [TestClass]
+    public partial class Sqlite_Test: UserTest<UserEntity>
+    {
+
+        [TestMethod]
+        public void Test()
+        {
+            Init();
+
+            base.Test();
+        }
+
+        public override UserEntity NewUser(int id) => new UserEntity { id = id, name = "testUser" + id };
+
+
+
+        public static void Init()
+        {
+
+            var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"sqlite.db");
+            if (File.Exists(filePath)) File.Delete(filePath);
+            File.WriteAllBytes(filePath, new byte[0]);
+
+
+            var connectionString = $"data source=sqlite.db";
+
+            using var dbContext = new SqlDbContext();
+            dbContext.UseSqlite(connectionString);
+
+            var dbSet = dbContext.DbSet<User>();
+            dbSet.Create();
+
+            var users = new List<User> {
+                    new User { id=1, name="u146", fatherId=4, motherId=6 },
+                    new User { id=2, name="u246", fatherId=4, motherId=6 },
+                    new User { id=3, name="u356", fatherId=5, motherId=6 },
+                    new User { id=4, name="u400" },
+                    new User { id=5, name="u500" },
+                    new User { id=6, name="u600" },
+                };
+            users.ForEach(user => { user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id); });
+
+            dbContext.AddRange(users);
+
+        }
+
+
+    }
+}

+ 207 - 0
test/Vitorm.Data.MsTest/CommonTest/UserTest.cs

@@ -0,0 +1,207 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Extensions.Vitorm_Extensions;
+
+using Vitorm.DataProvider;
+
+
+namespace Vitorm.MsTest
+{
+    public abstract class UserTest<User> where User : Vitorm.MsTest.User, new()
+    {
+
+        public abstract User NewUser(int id);
+
+        public virtual List<User> NewUsers(int startId, int count = 1)
+        {
+            return Enumerable.Range(startId, count).Select(NewUser).ToList();
+        }
+
+
+        public void Test()
+        {
+            #region #1 Get
+            {
+                var user = Data.Get<User>(1);
+                Assert.AreEqual(1, user?.id);
+            }
+            #endregion
+
+            #region #2 Query
+            {
+                var query =
+                    from user in Data.Query<User>()
+                    from father in Data.Query<User>().Where(father => user.fatherId == father.id)
+                    where user.id > 2
+                    select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+
+            }
+            #endregion
+
+            #region #3 ExecuteUpdate
+            {
+                var query = Data.Query<User>();
+
+                var count = query.ExecuteUpdate(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 = query.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);
+            }
+            #endregion
+
+            #region #4 ExecuteDelete
+            {
+                var query = Data.Query<User>();
+
+                var count = query.Where(u => u.id == 6).ExecuteDelete();
+
+                Assert.AreEqual(1, count);
+
+                var userList = query.ToList();
+                Assert.AreEqual(5, userList.Count());
+            }
+            #endregion
+
+
+
+            #region #5 Create :  Add AddRange
+            {
+                var newUserList = NewUsers(7, 4);
+
+                // #1 Add
+                Data.Add<User>(newUserList[0]);
+
+                // #2 AddRange
+                Data.AddRange<User>(newUserList.Skip(1));
+
+
+                Thread.Sleep(1000);
+
+                // assert
+                {
+                    var userList = Data.Query<User>().Where(user => user.id >= 7).ToList();
+                    Assert.AreEqual(newUserList.Count, userList.Count());
+                    Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                    Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+                }
+
+                try
+                {
+                    Data.Add<User>(newUserList[0]);
+                    Assert.Fail("should not be able to add same key twice");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+            #endregion
+
+
+            #region #6 Update: Update UpdateRange
+            {
+                var ids = Data.Query<User>().OrderBy(u => u.id).Select(u => u.id).ToArray()[^2..];
+            
+                var newUserList = ids.Select(NewUser).Append(NewUser(ids.Last()+1)).ToList();
+
+                // Update
+                {
+                    var rowCount = Data.Update(newUserList[0]);
+                    Assert.AreEqual(1, rowCount);
+                }
+
+                // UpdateRange
+                {
+                    var rowCount = Data.UpdateRange(newUserList.Skip(1));
+                    Assert.AreEqual(1, rowCount);
+                }
+
+                Thread.Sleep(1000);
+
+                // assert
+                {
+                    var userList = Data.Query<User>().Where(m => ids.Contains(m.id)).ToList();
+                    Assert.AreEqual(2, userList.Count());
+                    Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                    Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+                }
+
+            }
+            #endregion
+
+
+
+            #region #7 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+            {
+                // #1 Delete
+                {
+                    var rowCount = Data.Delete(NewUser(1));
+                    Assert.AreEqual(1, rowCount);
+                }
+
+                // #2 DeleteRange
+                {
+                    var rowCount = Data.DeleteRange(NewUsers(2, 2));
+                    Assert.AreEqual(2, rowCount);
+                }
+
+                // #3 DeleteByKey
+                {
+                    using var dbContext = (Data.DataProvider<User>() as SqlDataProvider)?.CreateDbContext();
+                    var entityDescriptor = dbContext.GetEntityDescriptor(typeof(User));
+                    var key = entityDescriptor.key;
+
+                    var user = NewUser(4);
+                    var keyValue = key.GetValue(user);
+                    var rowCount = Data.DeleteByKey<User>(keyValue);
+                    Assert.AreEqual(1, rowCount);
+                }
+
+                // #4 DeleteByKeys
+                {
+                    using var dbContext = (Data.DataProvider<User>() as SqlDataProvider)?.CreateDbContext();
+                    var entityDescriptor = dbContext.GetEntityDescriptor(typeof(User));
+                    var key = entityDescriptor.key;
+
+                    var users = Data.Query<User>().ToList();
+                    var keyValues = users.Select(user => key.GetValue(user));
+                    var rowCount = Data.DeleteByKeys<User, object>(keyValues);
+                    Assert.AreEqual(users.Count, rowCount);
+                }
+
+                Thread.Sleep(1000);
+
+                // assert
+                {
+                    var userList = Data.Query<User>().ToList();
+                    Assert.AreEqual(0, userList.Count());
+                }
+            }
+            #endregion
+
+
+
+            #region #8 get DbContext and entityDescriptor
+            {
+                using var dbContext = (Data.DataProvider<User>() as SqlDataProvider)?.CreateDbContext();
+                var entityDescriptor = dbContext.GetEntityDescriptor(typeof(User));
+                Assert.IsNotNull(entityDescriptor);
+            }
+            #endregion
+        }
+    }
+}

+ 23 - 0
test/Vitorm.Data.MsTest/User.cs

@@ -0,0 +1,23 @@
+
+namespace Vitorm.MsTest
+{
+
+    [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+    public class User
+    {
+        [System.ComponentModel.DataAnnotations.Key]
+        public int id { get; set; }
+        public string name { get; set; }
+        public DateTime? birth { get; set; }
+
+        public int? fatherId { get; set; }
+        public int? motherId { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
+        public string test { get; set; }
+
+
+      
+    }
+
+}

+ 34 - 0
test/Vitorm.Data.MsTest/Vitorm.Data.MsTest.csproj

@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
+
+        <PackageReference Include="Vit.Core" Version="2.1.21" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\..\Vitorm\src\Vitorm.MySql\Vitorm.MySql.csproj" />
+      <ProjectReference Include="..\..\..\Vitorm\src\Vitorm.Sqlite\Vitorm.Sqlite.csproj" />
+      <ProjectReference Include="..\..\..\Vitorm\src\Vitorm.SqlServer\Vitorm.SqlServer.csproj" />
+      <ProjectReference Include="..\..\src\Vitorm.Data\Vitorm.Data.csproj" />
+    </ItemGroup>
+
+ 
+    <ItemGroup>
+        <None Update="appsettings.json">
+            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+</Project>

+ 1 - 17
test/Vitorm.Sqlite.MsTest/CommonTest/CRUD_Test.cs

@@ -44,7 +44,7 @@ namespace Vitorm.MsTest.CommonTest
                 dbContext.Add(newUserList[0]);
                 Assert.Fail("should not be able to add same key twice");
             }
-            catch (Exception ex)
+            catch (Exception ex) when (ex is not AssertFailedException)
             {
             }
 
@@ -83,26 +83,10 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
             }
 
-            try
-            {
-                var newUser = User.NewUser(4);
-                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
-                key.SetValue(newUser, null);
-                dbContext.Update(newUser);
-                Assert.Fail("should not be able to update entity with null key");
-            }
-            catch (Exception ex)
-            {
-            }
-
-
         }
         #endregion
 
 
-
-
-
         #region #4 Delete
 
 

+ 19 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/DbContext_Test.cs

@@ -0,0 +1,19 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class DbContext_Test
+    {
+        [TestMethod]
+        public void EntityDescriptor_Test()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(User));
+            var key = entityDescriptor.key;
+
+            Assert.AreEqual("id", key.name);
+        }
+
+    }
+}

+ 2 - 2
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Test.cs

@@ -199,7 +199,7 @@ namespace Vitorm.MsTest.CommonTest
                     var user = userQuery.First(user => user.id == 13);
                     Assert.Fail("IQueryalbe.First should throw Exception");
                 }
-                catch (Exception ex)
+                catch (Exception ex) when (ex is not AssertFailedException)
                 {
                 }
 
@@ -263,7 +263,7 @@ namespace Vitorm.MsTest.CommonTest
                     var user = userQuery.Last(user => user.id == 13);
                     Assert.Fail("IQueryalbe.First should throw Exception");
                 }
-                catch (Exception ex)
+                catch (Exception ex) when (ex is not AssertFailedException)
                 {
                 }